Skip to content

Only consider meta variables in ambiguous "any of" constraints #18986

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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]