Skip to content
Merged
19 changes: 17 additions & 2 deletions mypy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -547,14 +547,19 @@ 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.
filtered_options = [filter_satisfiable(o) for o in options]
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.
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 []
Expand All @@ -569,6 +574,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:
Expand All @@ -583,6 +589,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):
Expand Down
15 changes: 15 additions & 0 deletions test-data/unit/check-inference.test
Original file line number Diff line number Diff line change
Expand Up @@ -3963,3 +3963,18 @@ 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("", "")
ok2: Union[_T, str] = mapping.get("", "")
[builtins fixtures/tuple.pyi]