diff --git a/mypy/subtypes.py b/mypy/subtypes.py index ceb9b7f0298a..a5b255b3a78f 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1620,6 +1620,18 @@ def are_parameters_compatible( return True trivial_suffix = is_trivial_suffix(right) and not is_proper_subtype + # def _(*a: Unpack[tuple[Any, ...]]) allows any number of arguments, not just infinite. + if right_star and isinstance(right_star.typ, UnpackType): + right_star_inner_type = get_proper_type(right_star.typ.type) + trivial_varargs = ( + isinstance(right_star_inner_type, Instance) + and right_star_inner_type.type.fullname == "builtins.tuple" + and len(right_star_inner_type.args) == 1 + and isinstance(get_proper_type(right_star_inner_type.args[0]), AnyType) + ) + else: + trivial_varargs = False + if ( right.arg_kinds == [ARG_STAR] and isinstance(get_proper_type(right.arg_types[0]), AnyType) @@ -1657,14 +1669,16 @@ def are_parameters_compatible( # Furthermore, if we're checking for compatibility in all cases, # we confirm that if R accepts an infinite number of arguments, # L must accept the same. - def _incompatible(left_arg: FormalArgument | None, right_arg: FormalArgument | None) -> bool: + def _incompatible( + left_arg: FormalArgument | None, right_arg: FormalArgument | None, varargs: bool + ) -> bool: if right_arg is None: return False if left_arg is None: - return not allow_partial_overlap and not trivial_suffix + return not (allow_partial_overlap or trivial_suffix or (varargs and trivial_varargs)) return not is_compat(right_arg.typ, left_arg.typ) - if _incompatible(left_star, right_star) or _incompatible(left_star2, right_star2): + if _incompatible(left_star, right_star, True) or _incompatible(left_star2, right_star2, False): return False # 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 # arguments. Get all further positional args of left, and make sure # they're more general than the corresponding member in right. # TODO: are we handling UnpackType correctly here? - if right_star is not None and not trivial_suffix: + if right_star is not None and not trivial_suffix and not trivial_varargs: # Synthesize an anonymous formal argument for the right right_by_position = right.try_synthesizing_arg_from_vararg(None) assert right_by_position is not None diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 5d6ad8e19631..dc5de3bf7bf8 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -3548,3 +3548,29 @@ def foo(x: T): 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]])" reveal_type(C(0, x)) # N: Revealed type is "__main__.C[__main__.Int[T`-1]]" reveal_type(C("yes", x)) # N: Revealed type is "__main__.C[__main__.Str[T`-1]]" + +[case testTypeVarTupleInCallableInUnion] +from typing import TypeVarTuple, Unpack, Generic, Union +from collections.abc import Callable + +Args = TypeVarTuple("Args") + +class Built(Generic[Unpack[Args]]): + pass + +def example(n: Union[Built[Unpack[Args]], Callable[[Unpack[Args]], None]]) -> Built[Unpack[Args]]: ... + +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]]" + +@example +def command1() -> None: + return + +reveal_type(command1) # N: Revealed type is "__main__.Built[()]" + +@example +def command2(a: int) -> None: + return + +reveal_type(command2) # N: Revealed type is "__main__.Built[builtins.int]" +[builtins fixtures/tuple.pyi]