From 200cfdcf4219dcfb1c88016e144e46c1476f8508 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 27 Oct 2023 15:31:10 +0100 Subject: [PATCH] Use upper bound as inference fallback more consistently --- mypy/checkexpr.py | 4 +++- mypy/infer.py | 8 ++++++-- mypy/solve.py | 5 ++++- test-data/unit/check-inference.test | 19 +++++++++++++++++++ 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 18c1c570ba91..ddcaa6ee30c9 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1989,7 +1989,9 @@ def infer_function_type_arguments_using_context( # in this case external context is almost everything we have. if not is_generic_instance(ctx) and not is_literal_type_like(ctx): return callable.copy_modified() - args = infer_type_arguments(callable.variables, ret_type, erased_ctx) + args = infer_type_arguments( + callable.variables, ret_type, erased_ctx, skip_unsatisfied=True + ) # Only substitute non-Uninhabited and non-erased types. new_args: list[Type | None] = [] for arg in args: diff --git a/mypy/infer.py b/mypy/infer.py index ba4a1d2bc9b1..bcf0c95808ab 100644 --- a/mypy/infer.py +++ b/mypy/infer.py @@ -63,9 +63,13 @@ def infer_function_type_arguments( def infer_type_arguments( - type_vars: Sequence[TypeVarLikeType], template: Type, actual: Type, is_supertype: bool = False + type_vars: Sequence[TypeVarLikeType], + template: Type, + actual: Type, + is_supertype: bool = False, + skip_unsatisfied: bool = False, ) -> list[Type | None]: # Like infer_function_type_arguments, but only match a single type # against a generic type. constraints = infer_constraints(template, actual, SUPERTYPE_OF if is_supertype else SUBTYPE_OF) - return solve_constraints(type_vars, constraints)[0] + return solve_constraints(type_vars, constraints, skip_unsatisfied=skip_unsatisfied)[0] diff --git a/mypy/solve.py b/mypy/solve.py index 4d0ca6b7af24..efe8e487c506 100644 --- a/mypy/solve.py +++ b/mypy/solve.py @@ -43,6 +43,7 @@ def solve_constraints( constraints: list[Constraint], strict: bool = True, allow_polymorphic: bool = False, + skip_unsatisfied: bool = False, ) -> tuple[list[Type | None], list[TypeVarLikeType]]: """Solve type constraints. @@ -54,6 +55,8 @@ def solve_constraints( If allow_polymorphic=True, then use the full algorithm that can potentially return free type variables in solutions (these require special care when applying). Otherwise, use a simplified algorithm that just solves each type variable individually if possible. + + The skip_unsatisfied flag matches the same one in applytype.apply_generic_arguments(). """ vars = [tv.id for tv in original_vars] if not vars: @@ -110,7 +113,7 @@ def solve_constraints( candidate = AnyType(TypeOfAny.special_form) res.append(candidate) - if not free_vars: + if not free_vars and not skip_unsatisfied: # Most of the validation for solutions is done in applytype.py, but here we can # quickly test solutions w.r.t. to upper bounds, and use the latter (if possible), # if solutions are actually not valid (due to poor inference context). diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 0a95ffdd50cf..0d162238450a 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -3748,3 +3748,22 @@ empty: Dict[NoReturn, NoReturn] def bar() -> Union[Dict[str, Any], Dict[int, Any]]: return empty [builtins fixtures/dict.pyi] + +[case testUpperBoundInferenceFallbackNotOverused] +from typing import TypeVar, Protocol, List + +S = TypeVar("S", covariant=True) +class Foo(Protocol[S]): + def foo(self) -> S: ... +def foo(x: Foo[S]) -> S: ... + +T = TypeVar("T", bound="Base") +class Base: + def foo(self: T) -> T: ... +class C(Base): + pass + +def f(values: List[T]) -> T: ... +x = foo(f([C()])) +reveal_type(x) # N: Revealed type is "__main__.C" +[builtins fixtures/list.pyi]