Skip to content

Commit

Permalink
Infer precise signatures for TypedDict types
Browse files Browse the repository at this point in the history
  • Loading branch information
JelleZijlstra committed Mar 2, 2022
1 parent 2cad48c commit f1e2794
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

- Infer precise call signatures for `TypedDict` types (#487)
- Add mechanism to prevent crashes on objects
with unusual `__getattr__` methods (#486)
- Infer callable signatures for objects with a
Expand Down
16 changes: 16 additions & 0 deletions pyanalyze/arg_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
Extension,
GenericBases,
KVPair,
TypedDictValue,
TypedValue,
GenericValue,
NewTypeValue,
Expand Down Expand Up @@ -77,6 +78,7 @@
Tuple,
Union,
)
from typing_extensions import is_typeddict
import typing_inspect
from unittest import mock

Expand Down Expand Up @@ -645,6 +647,20 @@ def _uncached_get_argspec(
if argspec is not None:
return argspec

if is_typeddict(obj):
td_type = type_from_runtime(obj)
if isinstance(td_type, TypedDictValue):
params = [
SigParameter(
key,
ParameterKind.KEYWORD_ONLY,
default=None if required else KnownValue(...),
annotation=value,
)
for key, (required, value) in td_type.items.items()
]
return Signature.make(params, td_type)

if is_newtype(obj):
assert hasattr(obj, "__supertype__")
return Signature.make(
Expand Down
37 changes: 37 additions & 0 deletions pyanalyze/test_typeddict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# static analysis: ignore
from .implementation import assert_is_value
from .value import SubclassValue, TypedDictValue, TypedValue
from .test_name_check_visitor import TestNameCheckVisitorBase
from .test_node_visitor import assert_passes


class TestTypedDict(TestNameCheckVisitorBase):
@assert_passes()
def test_constructor(self):
from typing_extensions import TypedDict, NotRequired

class Capybara(TypedDict):
x: int
y: str

class MaybeCapybara(TypedDict):
x: int
y: NotRequired[str]

def capybara():
cap = Capybara(x=1, y="2")
assert_is_value(
cap,
TypedDictValue(
{"x": (True, TypedValue(int)), "y": (True, TypedValue(str))}
),
)
Capybara(x=1) # E: incompatible_call

maybe_cap = MaybeCapybara(x=1)
assert_is_value(
maybe_cap,
TypedDictValue(
{"x": (True, TypedValue(int)), "y": (False, TypedValue(str))}
),
)

0 comments on commit f1e2794

Please sign in to comment.