Skip to content

Commit 6f32ef9

Browse files
authored
Add missing TypedDict special case to checkmember.py (#18604)
Fixes #18600
1 parent 88d6890 commit 6f32ef9

File tree

3 files changed

+62
-16
lines changed

3 files changed

+62
-16
lines changed

mypy/checkexpr.py

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515
import mypy.errorcodes as codes
1616
from mypy import applytype, erasetype, join, message_registry, nodes, operators, types
1717
from mypy.argmap import ArgTypeExpander, map_actuals_to_formals, map_formals_to_actuals
18-
from mypy.checkmember import analyze_member_access, freeze_all_type_vars, type_object_type
18+
from mypy.checkmember import (
19+
analyze_member_access,
20+
freeze_all_type_vars,
21+
type_object_type,
22+
typeddict_callable,
23+
)
1924
from mypy.checkstrformat import StringFormatterChecker
2025
from mypy.erasetype import erase_type, remove_instance_last_known_values, replace_meta_vars
2126
from mypy.errors import ErrorWatcher, report_internal_error
@@ -955,20 +960,7 @@ def typeddict_callable(self, info: TypeInfo) -> CallableType:
955960
Note it is not safe to move this to type_object_type() since it will crash
956961
on plugin-generated TypedDicts, that may not have the special_alias.
957962
"""
958-
assert info.special_alias is not None
959-
target = info.special_alias.target
960-
assert isinstance(target, ProperType) and isinstance(target, TypedDictType)
961-
expected_types = list(target.items.values())
962-
kinds = [ArgKind.ARG_NAMED] * len(expected_types)
963-
names = list(target.items.keys())
964-
return CallableType(
965-
expected_types,
966-
kinds,
967-
names,
968-
target,
969-
self.named_type("builtins.type"),
970-
variables=info.defn.type_vars,
971-
)
963+
return typeddict_callable(info, self.named_type)
972964

973965
def typeddict_callable_from_context(self, callee: TypedDictType) -> CallableType:
974966
return CallableType(

mypy/checkmember.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
ARG_STAR2,
2121
EXCLUDED_ENUM_ATTRIBUTES,
2222
SYMBOL_FUNCBASE_TYPES,
23+
ArgKind,
2324
Context,
2425
Decorator,
2526
FuncBase,
@@ -1148,8 +1149,16 @@ def analyze_class_attribute_access(
11481149
)
11491150
return AnyType(TypeOfAny.from_error)
11501151

1152+
# TODO: some logic below duplicates analyze_ref_expr in checkexpr.py
11511153
if isinstance(node.node, TypeInfo):
1152-
return type_object_type(node.node, mx.named_type)
1154+
if node.node.typeddict_type:
1155+
# We special-case TypedDict, because they don't define any constructor.
1156+
return typeddict_callable(node.node, mx.named_type)
1157+
elif node.node.fullname == "types.NoneType":
1158+
# We special case NoneType, because its stub definition is not related to None.
1159+
return TypeType(NoneType())
1160+
else:
1161+
return type_object_type(node.node, mx.named_type)
11531162

11541163
if isinstance(node.node, MypyFile):
11551164
# Reference to a module object.
@@ -1330,6 +1339,31 @@ class B(A[str]): pass
13301339
return t
13311340

13321341

1342+
def typeddict_callable(info: TypeInfo, named_type: Callable[[str], Instance]) -> CallableType:
1343+
"""Construct a reasonable type for a TypedDict type in runtime context.
1344+
1345+
If it appears as a callee, it will be special-cased anyway, e.g. it is
1346+
also allowed to accept a single positional argument if it is a dict literal.
1347+
1348+
Note it is not safe to move this to type_object_type() since it will crash
1349+
on plugin-generated TypedDicts, that may not have the special_alias.
1350+
"""
1351+
assert info.special_alias is not None
1352+
target = info.special_alias.target
1353+
assert isinstance(target, ProperType) and isinstance(target, TypedDictType)
1354+
expected_types = list(target.items.values())
1355+
kinds = [ArgKind.ARG_NAMED] * len(expected_types)
1356+
names = list(target.items.keys())
1357+
return CallableType(
1358+
expected_types,
1359+
kinds,
1360+
names,
1361+
target,
1362+
named_type("builtins.type"),
1363+
variables=info.defn.type_vars,
1364+
)
1365+
1366+
13331367
def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> ProperType:
13341368
"""Return the type of a type object.
13351369

test-data/unit/check-typeddict.test

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4118,3 +4118,23 @@ Func = TypedDict('Func', {
41184118
})
41194119
[builtins fixtures/dict.pyi]
41204120
[typing fixtures/typing-typeddict.pyi]
4121+
4122+
[case testTypedDictNestedInClassAndInherited]
4123+
from typing_extensions import TypedDict
4124+
4125+
class Base:
4126+
class Params(TypedDict):
4127+
name: str
4128+
4129+
class Derived(Base):
4130+
pass
4131+
4132+
class DerivedOverride(Base):
4133+
class Params(Base.Params):
4134+
pass
4135+
4136+
Base.Params(name="Robert")
4137+
Derived.Params(name="Robert")
4138+
DerivedOverride.Params(name="Robert")
4139+
[builtins fixtures/dict.pyi]
4140+
[typing fixtures/typing-typeddict.pyi]

0 commit comments

Comments
 (0)