diff --git a/mypy/constraints.py b/mypy/constraints.py index a8fa114e6029..cfb627e9f2b5 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -816,7 +816,7 @@ def visit_instance(self, template: Instance) -> list[Constraint]: if isinstance(actual, Overloaded) and actual.fallback is not None: actual = actual.fallback if isinstance(actual, TypedDictType): - actual = actual.as_anonymous().fallback + actual = actual.create_anonymous_fallback() if isinstance(actual, LiteralType): actual = actual.fallback if isinstance(actual, Instance): diff --git a/mypy/meet.py b/mypy/meet.py index c11d7d5d1aaa..365544d4584f 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -1263,7 +1263,7 @@ def typed_dict_mapping_overlap( key_type, value_type = get_proper_types(other.args) # TODO: is there a cleaner way to get str_type here? - fallback = typed.as_anonymous().fallback + fallback = typed.create_anonymous_fallback() str_type = fallback.type.bases[0].args[0] # typing._TypedDict inherits Mapping[str, object] # Special case: a TypedDict with no required keys overlaps with an empty dict. diff --git a/mypy/plugins/default.py b/mypy/plugins/default.py index 47387530de30..a994a3a80066 100644 --- a/mypy/plugins/default.py +++ b/mypy/plugins/default.py @@ -479,8 +479,9 @@ def typed_dict_update_signature_callback(ctx: MethodSigContext) -> CallableType: arg_type = get_proper_type(signature.arg_types[0]) if not isinstance(arg_type, TypedDictType): return signature - arg_type = arg_type.as_anonymous() - arg_type = arg_type.copy_modified(required_keys=set()) + arg_type = ctx.type.copy_modified( + fallback=arg_type.create_anonymous_fallback(), required_keys=set() + ) if ctx.args and ctx.args[0]: if signature.name in _TP_DICT_MUTATING_METHODS: # If we want to mutate this object in place, we need to set this flag, diff --git a/mypy/types.py b/mypy/types.py index 207e87984bed..494d65cd00c3 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -3080,11 +3080,11 @@ def is_final(self) -> bool: def is_anonymous(self) -> bool: return self.fallback.type.fullname in TPDICT_FB_NAMES - def as_anonymous(self) -> TypedDictType: + def create_anonymous_fallback(self) -> Instance: if self.is_anonymous(): - return self + return self.fallback assert self.fallback.type.typeddict_type is not None - return self.fallback.type.typeddict_type.as_anonymous() + return self.fallback.type.typeddict_type.create_anonymous_fallback() def copy_modified( self, @@ -3110,10 +3110,6 @@ def copy_modified( required_keys &= set(item_names) return TypedDictType(items, required_keys, readonly_keys, fallback, self.line, self.column) - def create_anonymous_fallback(self) -> Instance: - anonymous = self.as_anonymous() - return anonymous.fallback - def names_are_wider_than(self, other: TypedDictType) -> bool: return len(other.items.keys() - self.items.keys()) == 0 diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 67a66e18001d..1683fa40ed0b 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -3011,6 +3011,39 @@ reveal_type(s3) # N: Revealed type is "TypedDict('__main__.Sub3', {'x': Any, 'y [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] +[case testGenericTypedDictUpdate] +from typing import TypedDict, Generic, TypeVar + +T = TypeVar("T") + +class Group(TypedDict, Generic[T]): + a: T + +value: Group[int] = {"a": 1} + +def func(value2: Group[int]) -> None: + value.update(value2) + value.update({"a": 2}) + value.update({"a": "string"}) # E: Incompatible types (expression has type "str", TypedDict item "a" has type "int") + +S = TypeVar("S") + +class MultiField(TypedDict, Generic[T, S]): + x: T + y: S + +mf1: MultiField[int, str] = {"x": 1, "y": "a"} +mf2: MultiField[int, str] = {"x": 2, "y": "b"} +mf1.update(mf2) + +mf3: MultiField[str, str] = {"x": "test", "y": "c"} +mf1.update(mf3) # E: Argument 1 to "update" of "TypedDict" has incompatible type "MultiField[str, str]"; expected "TypedDict({'x': int, 'y': str})" + +mf1.update({"x": 3}) +mf1.update({"y": "d"}) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi] + [case testTypedDictAttributeOnClassObject] from typing import TypedDict