Skip to content

Commit

Permalink
Use type variable bound when it appears as actual during inference (#…
Browse files Browse the repository at this point in the history
…16178)

This should help with re-enabling the use of `ParamSpec` in
`functools.wraps` (as it looks like some of the new errors in
AlexWaygood#11 are caused by not handling
this).

---------

Co-authored-by: hauntsaninja <hauntsaninja@gmail.com>
Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 27, 2023
1 parent 5f6961b commit d25d680
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 0 deletions.
12 changes: 12 additions & 0 deletions mypy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,18 @@ def _infer_constraints(
if isinstance(template, TypeVarType):
return [Constraint(template, direction, actual)]

if (
isinstance(actual, TypeVarType)
and not actual.id.is_meta_var()
and direction == SUPERTYPE_OF
):
# Unless template is also a type variable (or a union that contains one), using the upper
# bound for inference will usually give better result for actual that is a type variable.
if not isinstance(template, UnionType) or not any(
isinstance(t, TypeVarType) for t in template.items
):
actual = get_proper_type(actual.upper_bound)

# Now handle the case of either template or actual being a Union.
# For a Union to be a subtype of another type, every item of the Union
# must be a subtype of that type, so concatenate the constraints.
Expand Down
30 changes: 30 additions & 0 deletions test-data/unit/check-inference.test
Original file line number Diff line number Diff line change
Expand Up @@ -3695,6 +3695,36 @@ reveal_type(f(g)) # N: Revealed type is "Tuple[Never, Never]" \
# E: Argument 1 to "f" has incompatible type "Callable[[VarArg(str)], None]"; expected "Call[Never]"
[builtins fixtures/list.pyi]

[case testInferenceAgainstTypeVarActualBound]
from typing import Callable, TypeVar

T = TypeVar("T")
S = TypeVar("S")
def test(f: Callable[[T], S]) -> Callable[[T], S]: ...

F = TypeVar("F", bound=Callable[..., object])
def dec(f: F) -> F:
reveal_type(test(f)) # N: Revealed type is "def (Any) -> builtins.object"
return f

[case testInferenceAgainstTypeVarActualUnionBound]
from typing import Protocol, TypeVar, Union

T_co = TypeVar("T_co", covariant=True)
class SupportsFoo(Protocol[T_co]):
def foo(self) -> T_co: ...

class A:
def foo(self) -> A: ...
class B:
def foo(self) -> B: ...

def foo(f: SupportsFoo[T_co]) -> T_co: ...

ABT = TypeVar("ABT", bound=Union[A, B])
def simpler(k: ABT):
foo(k)

[case testInferenceWorksWithEmptyCollectionsNested]
from typing import List, TypeVar, NoReturn
T = TypeVar('T')
Expand Down

0 comments on commit d25d680

Please sign in to comment.