Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 7 additions & 15 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
36 changes: 35 additions & 1 deletion mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
ARG_STAR2,
EXCLUDED_ENUM_ATTRIBUTES,
SYMBOL_FUNCBASE_TYPES,
ArgKind,
Context,
Decorator,
FuncBase,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.

Expand Down
20 changes: 20 additions & 0 deletions test-data/unit/check-typeddict.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]