diff --git a/mypy/constraints.py b/mypy/constraints.py index 293618556203..5245a21826a9 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -273,9 +273,35 @@ def infer_constraints_for_callable( if any(isinstance(v, ParamSpecType) for v in callee.variables): # As a perf optimization filter imprecise constraints only when we can have them. constraints = filter_imprecise_kinds(constraints) + + if any(isinstance(get_proper_type(c.target), TypedDictType) for c in constraints): + constraints = _omit_dict_constraints_if_typeddict_found(constraints) return constraints +def _omit_dict_constraints_if_typeddict_found(constraints: list[Constraint]) -> list[Constraint]: + """If a type variable has any `TypedDict` constraints, exclude its `dict` constraints.""" + + # This allows inferring types generic in typeddicts when a literal *and* explicit type + # are present among arguments - see testInferLiteralTypedDict. + cmap: dict[TypeVarId, list[Constraint]] = {} + for con in constraints: + cmap.setdefault(con.type_var, []).append(con) + res = [] + for group in cmap.values(): + if not any(isinstance(get_proper_type(c.target), TypedDictType) for c in group): + res.extend(group) + continue + for c in group: + proper_target = get_proper_type(c.target) + if ( + not isinstance(proper_target, Instance) + or proper_target.type.fullname != "builtins.dict" + ): + res.append(c) + return res + + def infer_constraints( template: Type, actual: Type, direction: int, skip_neg_op: bool = False ) -> list[Constraint]: diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index b563eef0f8aa..b1ebb777d906 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -4149,3 +4149,27 @@ class Foo: else: self.qux = {} # E: Need type annotation for "qux" (hint: "qux: dict[, ] = ...") [builtins fixtures/dict.pyi] + +[case testInferLiteralTypedDict] +from typing import Generic, TypeVar, TypedDict + +T = TypeVar("T") + +class D(TypedDict): + x: int + +class A(Generic[T]): ... + + +def f(a: A[T], t: T) -> T: ... +def g(a: T, t: A[T]) -> T: ... + +def check(obj: A[D]) -> None: + reveal_type(f(obj, {"x": 1})) # N: Revealed type is "TypedDict('__main__.D', {'x': builtins.int})" + reveal_type(f(obj, {"x": ''})) # N: Revealed type is "TypedDict('__main__.D', {'x': builtins.int})" \ + # E: Incompatible types (expression has type "str", TypedDict item "x" has type "int") + reveal_type(g({"x": 1}, obj)) # N: Revealed type is "TypedDict('__main__.D', {'x': builtins.int})" + reveal_type(g({"x": ''}, obj)) # N: Revealed type is "TypedDict('__main__.D', {'x': builtins.int})" \ + # E: Incompatible types (expression has type "str", TypedDict item "x" has type "int") +[builtins fixtures/dict.pyi] +[typing fixtures/typing-typeddict.pyi]