Skip to content

Commit 2cf5cd7

Browse files
committed
TypedDict get tweaks
Some of the tests are adapted from #2620 by @rowillia.
1 parent b025991 commit 2cf5cd7

File tree

3 files changed

+37
-8
lines changed

3 files changed

+37
-8
lines changed

mypy/checkexpr.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1848,13 +1848,14 @@ def visit_dict_expr(self, e: DictExpr) -> Type:
18481848
# an error, but returns the TypedDict type that matches the literal it found
18491849
# that would cause a second error when that TypedDict type is returned upstream
18501850
# to avoid the second error, we always return TypedDict type that was requested
1851-
if isinstance(self.type_context[-1], TypedDictType):
1851+
typeddict_context = self.find_typeddict_context(self.type_context[-1])
1852+
if typeddict_context:
18521853
self.check_typeddict_call_with_dict(
1853-
callee=self.type_context[-1],
1854+
callee=typeddict_context,
18541855
kwargs=e,
18551856
context=e
18561857
)
1857-
return self.type_context[-1].copy_modified()
1858+
return typeddict_context.copy_modified()
18581859

18591860
# Collect function arguments, watching out for **expr.
18601861
args = [] # type: List[Expression] # Regular "key: value"
@@ -1905,6 +1906,19 @@ def visit_dict_expr(self, e: DictExpr) -> Type:
19051906
self.check_call(method, [arg], [nodes.ARG_POS], arg)
19061907
return rv
19071908

1909+
def find_typeddict_context(self, context: Type) -> Optional[TypedDictType]:
1910+
if isinstance(context, TypedDictType):
1911+
return context
1912+
elif isinstance(context, UnionType):
1913+
items = []
1914+
for item in context.items:
1915+
item_context = self.find_typeddict_context(item)
1916+
if item_context:
1917+
items.append(item_context)
1918+
if len(items) == 1:
1919+
return items[0]
1920+
return None
1921+
19081922
def visit_lambda_expr(self, e: LambdaExpr) -> Type:
19091923
"""Type check lambda expression."""
19101924
inferred_type, type_override = self.infer_lambda_type_using_context(e)

mypy/plugin.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,6 @@ def typed_dict_get_callback(
224224
else:
225225
context.msg.typeddict_item_name_not_found(object_type, key, context.context)
226226
return AnyType()
227-
else:
228-
context.msg.typeddict_item_name_must_be_string_literal(object_type, context.context)
229-
return AnyType()
230227
return inferred_return_type
231228

232229

test-data/unit/check-typeddict.test

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -784,8 +784,8 @@ d.get('x', 1, 2) # E: No overload variant of "get" of "Mapping" matches argument
784784
x = d.get('z') # E: 'z' is not a valid item name; expected one of ['x', 'y']
785785
reveal_type(x) # E: Revealed type is 'Any'
786786
s = ''
787-
y = d.get(s) # E: Cannot prove expression is a valid item name; expected one of ['x', 'y']
788-
reveal_type(y) # E: Revealed type is 'Any'
787+
y = d.get(s)
788+
reveal_type(y) # E: Revealed type is 'builtins.object*'
789789
[builtins fixtures/dict.pyi]
790790
[typing fixtures/typing-full.pyi]
791791

@@ -795,3 +795,21 @@ D = TypedDict('D', {'x': int, 'y': str})
795795
d: D
796796
d.bad(1) # E: "D" has no attribute "bad"
797797
[builtins fixtures/dict.pyi]
798+
799+
[case testTypedDictChainedGetMethodWithDictFallback]
800+
from mypy_extensions import TypedDict
801+
D = TypedDict('D', {'x': int, 'y': str})
802+
E = TypedDict('E', {'d': D})
803+
p = E(d=D(x=0, y=''))
804+
reveal_type(p.get('d', {'x': 1, 'y': ''})) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.str, _fallback=__main__.D)'
805+
p.get('d', {}) # E: Expected items ['x', 'y'] but found [].
806+
[builtins fixtures/dict.pyi]
807+
[typing fixtures/typing-full.pyi]
808+
809+
[case testTypedDictGetDefaultParameterStillTypeChecked]
810+
from mypy_extensions import TypedDict
811+
TaggedPoint = TypedDict('TaggedPoint', {'type': str, 'x': int, 'y': int})
812+
p = TaggedPoint(type='2d', x=42, y=1337)
813+
p.get('x', 1 + 'y') # E: Unsupported operand types for + ("int" and "str")
814+
[builtins fixtures/dict.pyi]
815+
[typing fixtures/typing-full.pyi]

0 commit comments

Comments
 (0)