diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 4b7e39d2042a..286ef0dab6ae 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -15,7 +15,12 @@ import mypy.errorcodes as codes from mypy import applytype, erasetype, join, message_registry, nodes, operators, types from mypy.argmap import ArgTypeExpander, map_actuals_to_formals, map_formals_to_actuals -from mypy.checkmember import analyze_member_access, freeze_all_type_vars, type_object_type +from mypy.checkmember import ( + analyze_member_access, + freeze_all_type_vars, + type_object_type, + typeddict_callable, +) from mypy.checkstrformat import StringFormatterChecker from mypy.erasetype import erase_type, remove_instance_last_known_values, replace_meta_vars from mypy.errors import ErrorWatcher, report_internal_error @@ -955,20 +960,7 @@ def typeddict_callable(self, info: TypeInfo) -> CallableType: Note it is not safe to move this to type_object_type() since it will crash on plugin-generated TypedDicts, that may not have the special_alias. """ - assert info.special_alias is not None - target = info.special_alias.target - assert isinstance(target, ProperType) and isinstance(target, TypedDictType) - expected_types = list(target.items.values()) - kinds = [ArgKind.ARG_NAMED] * len(expected_types) - names = list(target.items.keys()) - return CallableType( - expected_types, - kinds, - names, - target, - self.named_type("builtins.type"), - variables=info.defn.type_vars, - ) + return typeddict_callable(info, self.named_type) def typeddict_callable_from_context(self, callee: TypedDictType) -> CallableType: return CallableType( diff --git a/mypy/checkmember.py b/mypy/checkmember.py index f6b5e6be2c53..515f0c12c5b9 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -20,6 +20,7 @@ ARG_STAR2, EXCLUDED_ENUM_ATTRIBUTES, SYMBOL_FUNCBASE_TYPES, + ArgKind, Context, Decorator, FuncBase, @@ -1148,8 +1149,16 @@ def analyze_class_attribute_access( ) return AnyType(TypeOfAny.from_error) + # TODO: some logic below duplicates analyze_ref_expr in checkexpr.py if isinstance(node.node, TypeInfo): - return type_object_type(node.node, mx.named_type) + if node.node.typeddict_type: + # We special-case TypedDict, because they don't define any constructor. + return typeddict_callable(node.node, mx.named_type) + elif node.node.fullname == "types.NoneType": + # We special case NoneType, because its stub definition is not related to None. + return TypeType(NoneType()) + else: + return type_object_type(node.node, mx.named_type) if isinstance(node.node, MypyFile): # Reference to a module object. @@ -1330,6 +1339,31 @@ class B(A[str]): pass return t +def typeddict_callable(info: TypeInfo, named_type: Callable[[str], Instance]) -> CallableType: + """Construct a reasonable type for a TypedDict type in runtime context. + + If it appears as a callee, it will be special-cased anyway, e.g. it is + also allowed to accept a single positional argument if it is a dict literal. + + Note it is not safe to move this to type_object_type() since it will crash + on plugin-generated TypedDicts, that may not have the special_alias. + """ + assert info.special_alias is not None + target = info.special_alias.target + assert isinstance(target, ProperType) and isinstance(target, TypedDictType) + expected_types = list(target.items.values()) + kinds = [ArgKind.ARG_NAMED] * len(expected_types) + names = list(target.items.keys()) + return CallableType( + expected_types, + kinds, + names, + target, + named_type("builtins.type"), + variables=info.defn.type_vars, + ) + + def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> ProperType: """Return the type of a type object. diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 22e9963944a2..feea5e2dff0f 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -4118,3 +4118,23 @@ Func = TypedDict('Func', { }) [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] + +[case testTypedDictNestedInClassAndInherited] +from typing_extensions import TypedDict + +class Base: + class Params(TypedDict): + name: str + +class Derived(Base): + pass + +class DerivedOverride(Base): + class Params(Base.Params): + pass + +Base.Params(name="Robert") +Derived.Params(name="Robert") +DerivedOverride.Params(name="Robert") +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi]