Skip to content

Commit 25f3dbc

Browse files
✨ We can now set an item and get the correct error code
1 parent eda0c8b commit 25f3dbc

File tree

4 files changed

+18
-6
lines changed

4 files changed

+18
-6
lines changed

mypy/checkexpr.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3740,7 +3740,9 @@ def nonliteral_tuple_index_helper(self, left_type: TupleType, index: Expression)
37403740
return self.chk.named_generic_type("builtins.tuple", [union])
37413741
return union
37423742

3743-
def visit_typeddict_index_expr(self, td_type: TypedDictType, index: Expression) -> Type:
3743+
def visit_typeddict_index_expr(
3744+
self, td_type: TypedDictType, index: Expression, setitem: bool = False
3745+
) -> Type:
37443746
if isinstance(index, StrExpr):
37453747
key_names = [index.value]
37463748
else:
@@ -3769,7 +3771,7 @@ def visit_typeddict_index_expr(self, td_type: TypedDictType, index: Expression)
37693771
for key_name in key_names:
37703772
value_type = td_type.items.get(key_name)
37713773
if value_type is None:
3772-
self.msg.typeddict_key_not_found(td_type, key_name, index)
3774+
self.msg.typeddict_key_not_found(td_type, key_name, index, setitem)
37733775
return AnyType(TypeOfAny.from_error)
37743776
else:
37753777
value_types.append(value_type)

mypy/checkmember.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1068,7 +1068,9 @@ def analyze_typeddict_access(
10681068
if isinstance(mx.context, IndexExpr):
10691069
# Since we can get this during `a['key'] = ...`
10701070
# it is safe to assume that the context is `IndexExpr`.
1071-
item_type = mx.chk.expr_checker.visit_typeddict_index_expr(typ, mx.context.index)
1071+
item_type = mx.chk.expr_checker.visit_typeddict_index_expr(
1072+
typ, mx.context.index, setitem=True
1073+
)
10721074
else:
10731075
# It can also be `a.__setitem__(...)` direct call.
10741076
# In this case `item_type` can be `Any`,

mypy/messages.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,8 +1653,16 @@ def typeddict_key_must_be_string_literal(self, typ: TypedDictType, context: Cont
16531653
)
16541654

16551655
def typeddict_key_not_found(
1656-
self, typ: TypedDictType, item_name: str, context: Context
1656+
self, typ: TypedDictType, item_name: str, context: Context, setitem: bool = False
16571657
) -> None:
1658+
"""
1659+
Handles error messages.
1660+
1661+
Note, that we differentiate in between reading a value and setting
1662+
a value.
1663+
Setting a value on a TypedDict is an 'unknown-key' error,
1664+
whereas reading it is the more serious/general 'item' error.
1665+
"""
16581666
if typ.is_anonymous():
16591667
self.fail(
16601668
'"{}" is not a valid TypedDict key; expected one of {}'.format(
@@ -1666,7 +1674,7 @@ def typeddict_key_not_found(
16661674
self.fail(
16671675
f'TypedDict {format_type(typ)} has no key "{item_name}"',
16681676
context,
1669-
code=codes.TYPPEDICT_UNKNOWN_KEY,
1677+
code=codes.TYPPEDICT_UNKNOWN_KEY if setitem else codes.TYPEDDICT_ITEM,
16701678
)
16711679
matches = best_matches(item_name, typ.items.keys())
16721680
if matches:

test-data/unit/check-errorcodes.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ d: D = {'x': '', 'y': 1} # E: Extra key "y" for TypedDict "D" [typeddict-unkno
465465

466466
a['y'] = 1 # E: TypedDict "D" has no key "y" [typeddict-unknown-key]
467467
a['x'] = 'x' # E: Value of "x" has incompatible type "str"; expected "int" [typeddict-item]
468-
a['y'] # E: TypedDict "D" has no key "y" [typeddict-unknown-key]
468+
a['y'] # E: TypedDict "D" has no key "y" [typeddict-item]
469469
[builtins fixtures/dict.pyi]
470470
[typing fixtures/typing-typeddict.pyi]
471471

0 commit comments

Comments
 (0)