diff --git a/mypy/constraints.py b/mypy/constraints.py index 079f6536ee20..8e7a30e05ffb 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -512,7 +512,7 @@ def handle_recursive_union(template: UnionType, actual: Type, direction: int) -> ) or infer_constraints(UnionType.make_union(type_var_items), actual, direction) -def any_constraints(options: list[list[Constraint] | None], eager: bool) -> list[Constraint]: +def any_constraints(options: list[list[Constraint] | None], *, eager: bool) -> list[Constraint]: """Deduce what we can from a collection of constraint lists. It's a given that at least one of the lists must be satisfied. A @@ -547,7 +547,7 @@ def any_constraints(options: list[list[Constraint] | None], eager: bool) -> list if option in trivial_options: continue merged_options.append([merge_with_any(c) for c in option]) - return any_constraints(list(merged_options), eager) + return any_constraints(list(merged_options), eager=eager) # If normal logic didn't work, try excluding trivially unsatisfiable constraint (due to # upper bounds) from each option, and comparing them again. @@ -555,6 +555,14 @@ def any_constraints(options: list[list[Constraint] | None], eager: bool) -> list if filtered_options != options: return any_constraints(filtered_options, eager=eager) + # Try harder: if that didn't work, try to strip typevars that aren't meta vars. + # Note this is what we would always do, but unfortunately some callers may not + # set the meta var status correctly (for historical reasons), so we use this as + # a fallback only. + filtered_options = [exclude_non_meta_vars(o) for o in options] + if filtered_options != options: + return any_constraints(filtered_options, eager=eager) + # Otherwise, there are either no valid options or multiple, inconsistent valid # options. Give up and deduce nothing. return [] @@ -569,6 +577,7 @@ def filter_satisfiable(option: list[Constraint] | None) -> list[Constraint] | No """ if not option: return option + satisfiable = [] for c in option: if isinstance(c.origin_type_var, TypeVarType) and c.origin_type_var.values: @@ -583,6 +592,15 @@ def filter_satisfiable(option: list[Constraint] | None) -> list[Constraint] | No return satisfiable +def exclude_non_meta_vars(option: list[Constraint] | None) -> list[Constraint] | None: + # If we had an empty list, keep it intact + if not option: + return option + # However, if none of the options actually references meta vars, better remove + # this constraint entirely. + return [c for c in option if c.type_var.is_meta_var()] or None + + def is_same_constraints(x: list[Constraint], y: list[Constraint]) -> bool: for c1 in x: if not any(is_same_constraint(c1, c2) for c2 in y): diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 42b5a05ab39a..25565946158e 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -3963,3 +3963,19 @@ def f() -> None: # The type below should not be Any. reveal_type(x) # N: Revealed type is "builtins.int" + +[case testInferenceMappingTypeVarGet] +from typing import Generic, TypeVar, Union + +_T = TypeVar("_T") +_K = TypeVar("_K") +_V = TypeVar("_V") + +class Mapping(Generic[_K, _V]): + def get(self, key: _K, default: Union[_V, _T]) -> Union[_V, _T]: ... + +def check(mapping: Mapping[str, _T]) -> None: + ok1 = mapping.get("", "") + reveal_type(ok1) # N: Revealed type is "Union[_T`-1, builtins.str]" + ok2: Union[_T, str] = mapping.get("", "") +[builtins fixtures/tuple.pyi]