Skip to content

Commit 1af1861

Browse files
committed
Ensure subtyping works with erased typevartuples in callables
1 parent 7b4f862 commit 1af1861

File tree

2 files changed

+44
-4
lines changed

2 files changed

+44
-4
lines changed

mypy/subtypes.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1620,6 +1620,18 @@ def are_parameters_compatible(
16201620
return True
16211621
trivial_suffix = is_trivial_suffix(right) and not is_proper_subtype
16221622

1623+
# def _(*a: Unpack[tuple[Any, ...]]) allows any number of arguments, not just infinite.
1624+
if right_star and isinstance(right_star.typ, UnpackType):
1625+
right_star_inner_type = get_proper_type(right_star.typ.type)
1626+
trivial_varargs = (
1627+
isinstance(right_star_inner_type, Instance)
1628+
and right_star_inner_type.type.fullname == "builtins.tuple"
1629+
and len(right_star_inner_type.args) == 1
1630+
and isinstance(get_proper_type(right_star_inner_type.args[0]), AnyType)
1631+
)
1632+
else:
1633+
trivial_varargs = False
1634+
16231635
if (
16241636
right.arg_kinds == [ARG_STAR]
16251637
and isinstance(get_proper_type(right.arg_types[0]), AnyType)
@@ -1657,14 +1669,16 @@ def are_parameters_compatible(
16571669
# Furthermore, if we're checking for compatibility in all cases,
16581670
# we confirm that if R accepts an infinite number of arguments,
16591671
# L must accept the same.
1660-
def _incompatible(left_arg: FormalArgument | None, right_arg: FormalArgument | None) -> bool:
1672+
def _incompatible(
1673+
left_arg: FormalArgument | None, right_arg: FormalArgument | None, varargs: bool
1674+
) -> bool:
16611675
if right_arg is None:
16621676
return False
16631677
if left_arg is None:
1664-
return not allow_partial_overlap and not trivial_suffix
1678+
return not (allow_partial_overlap or trivial_suffix or (varargs and trivial_varargs))
16651679
return not is_compat(right_arg.typ, left_arg.typ)
16661680

1667-
if _incompatible(left_star, right_star) or _incompatible(left_star2, right_star2):
1681+
if _incompatible(left_star, right_star, True) or _incompatible(left_star2, right_star2, False):
16681682
return False
16691683

16701684
# Phase 1b: Check non-star args: for every arg right can accept, left must
@@ -1690,7 +1704,7 @@ def _incompatible(left_arg: FormalArgument | None, right_arg: FormalArgument | N
16901704
# arguments. Get all further positional args of left, and make sure
16911705
# they're more general than the corresponding member in right.
16921706
# TODO: are we handling UnpackType correctly here?
1693-
if right_star is not None and not trivial_suffix:
1707+
if right_star is not None and not trivial_suffix and not trivial_varargs:
16941708
# Synthesize an anonymous formal argument for the right
16951709
right_by_position = right.try_synthesizing_arg_from_vararg(None)
16961710
assert right_by_position is not None

test-data/unit/check-generics.test

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3548,3 +3548,29 @@ def foo(x: T):
35483548
reveal_type(C) # N: Revealed type is "Overload(def [T, S] (x: builtins.int, y: S`-1) -> __main__.C[__main__.Int[S`-1]], def [T, S] (x: builtins.str, y: S`-1) -> __main__.C[__main__.Str[S`-1]])"
35493549
reveal_type(C(0, x)) # N: Revealed type is "__main__.C[__main__.Int[T`-1]]"
35503550
reveal_type(C("yes", x)) # N: Revealed type is "__main__.C[__main__.Str[T`-1]]"
3551+
3552+
[case testTypeVarTupleInCallableInUnion]
3553+
from typing import TypeVarTuple, Unpack, Generic, Union
3554+
from collections.abc import Callable
3555+
3556+
Args = TypeVarTuple("Args")
3557+
3558+
class Built(Generic[Unpack[Args]]):
3559+
pass
3560+
3561+
def example(n: Union[Built[Unpack[Args]], Callable[[Unpack[Args]], None]]) -> Built[Unpack[Args]]: ...
3562+
3563+
reveal_type(example) # N: Revealed type is "def [Args] (n: Union[__main__.Built[Unpack[Args`-1]], def (*Unpack[Args`-1])]) -> __main__.Built[Unpack[Args`-1]]"
3564+
3565+
@example
3566+
def command1() -> None:
3567+
return
3568+
3569+
reveal_type(command1) # N: Revealed type is "__main__.Built[()]"
3570+
3571+
@example
3572+
def command2(a: int) -> None:
3573+
return
3574+
3575+
reveal_type(command2) # N: Revealed type is "__main__.Built[builtins.int]"
3576+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)