Skip to content

Commit 238a8f6

Browse files
committed
Fix crash involving Unpack-ed TypeVarTuple
Fixes #20093
1 parent 1a6ff59 commit 238a8f6

File tree

4 files changed

+60
-12
lines changed

4 files changed

+60
-12
lines changed

mypy/typeops.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ def erase_to_bound(t: Type) -> Type:
508508
def callable_corresponding_argument(
509509
typ: NormalizedCallableType | Parameters, model: FormalArgument
510510
) -> FormalArgument | None:
511-
"""Return the argument a function that corresponds to `model`"""
511+
"""Return the argument of a function that corresponds to `model`"""
512512

513513
by_name = typ.argument_by_name(model.name)
514514
by_pos = typ.argument_by_position(model.pos)
@@ -522,7 +522,7 @@ def callable_corresponding_argument(
522522
# taking both *args and **args, or a pair of functions like so:
523523

524524
# def right(a: int = ...) -> None: ...
525-
# def left(__a: int = ..., *, a: int = ...) -> None: ...
525+
# def left(x: int = ..., /, *, a: int = ...) -> None: ...
526526
from mypy.meet import meet_types
527527

528528
if (
@@ -533,6 +533,8 @@ def callable_corresponding_argument(
533533
return FormalArgument(
534534
by_name.name, by_pos.pos, meet_types(by_name.typ, by_pos.typ), False
535535
)
536+
return by_name
537+
536538
return by_name if by_name is not None else by_pos
537539

538540

mypy/types.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1923,6 +1923,25 @@ def __hash__(self) -> int:
19231923
return hash((self.name, self.pos, self.typ, self.required))
19241924

19251925

1926+
def _synthesize_arg_from_vararg(vararg: FormalArgument | None, position: int | None) -> FormalArgument | None:
1927+
if vararg is None:
1928+
return None
1929+
typ = vararg.typ
1930+
if isinstance(typ, UnpackType):
1931+
# Similar to logic in ExpressionChecker.visit_tuple_index_helper
1932+
unpacked = get_proper_type(typ.type)
1933+
if isinstance(unpacked, TypeVarTupleType):
1934+
bound = get_proper_type(unpacked.upper_bound)
1935+
assert isinstance(bound, Instance)
1936+
assert bound.type.fullname == "builtins.tuple"
1937+
typ = bound.args[0]
1938+
else:
1939+
assert isinstance(unpacked, Instance)
1940+
assert unpacked.type.fullname == "builtins.tuple"
1941+
typ = unpacked.args[0]
1942+
return FormalArgument(None, position, typ, False)
1943+
1944+
19261945
class Parameters(ProperType):
19271946
"""Type that represents the parameters to a function.
19281947
@@ -2069,11 +2088,7 @@ def try_synthesizing_arg_from_kwarg(self, name: str | None) -> FormalArgument |
20692088
return None
20702089

20712090
def try_synthesizing_arg_from_vararg(self, position: int | None) -> FormalArgument | None:
2072-
var_arg = self.var_arg()
2073-
if var_arg is not None:
2074-
return FormalArgument(None, position, var_arg.typ, False)
2075-
else:
2076-
return None
2091+
return _synthesize_arg_from_vararg(self.var_arg(), position)
20772092

20782093
def accept(self, visitor: TypeVisitor[T]) -> T:
20792094
return visitor.visit_parameters(self)
@@ -2418,11 +2433,7 @@ def try_synthesizing_arg_from_kwarg(self, name: str | None) -> FormalArgument |
24182433
return None
24192434

24202435
def try_synthesizing_arg_from_vararg(self, position: int | None) -> FormalArgument | None:
2421-
var_arg = self.var_arg()
2422-
if var_arg is not None:
2423-
return FormalArgument(None, position, var_arg.typ, False)
2424-
else:
2425-
return None
2436+
return _synthesize_arg_from_vararg(self.var_arg(), position)
24262437

24272438
@property
24282439
def items(self) -> list[CallableType]:

test-data/unit/check-overloading.test

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,19 @@ def foo(*args: int | str, **kw: int | Foo) -> None:
263263
pass
264264
[builtins fixtures/tuple.pyi]
265265

266+
267+
[case testTypeCheckOverloadImplOverlapVarArgsAndKwargsNever]
268+
from __future__ import annotations
269+
from typing import overload
270+
271+
@overload # E: Single overload definition, multiple required
272+
def foo(x: int) -> None: ...
273+
274+
def foo(*args: int, **kw: str) -> None: # E: Overloaded function implementation does not accept all possible arguments of signature 1
275+
pass
276+
[builtins fixtures/tuple.pyi]
277+
278+
266279
[case testTypeCheckOverloadWithImplTooSpecificRetType]
267280
from typing import overload, Any
268281

test-data/unit/check-typevar-tuple.test

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2716,3 +2716,25 @@ class MyTuple(tuple[Unpack[Union[int, str]]], Generic[Unpack[Ts]]): # E: "Union
27162716
x: MyTuple[int, str]
27172717
reveal_type(x[0]) # N: Revealed type is "Any"
27182718
[builtins fixtures/tuple.pyi]
2719+
2720+
[case testHigherOrderFunctionUnpackTypeVarTupleViaParamSpec]
2721+
from typing import Callable, ParamSpec, TypeVar, TypeVarTuple, Unpack
2722+
2723+
P = ParamSpec("P")
2724+
T = TypeVar("T")
2725+
Ts = TypeVarTuple("Ts")
2726+
2727+
def call(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
2728+
return func(*args, **kwargs)
2729+
2730+
2731+
def run(func: Callable[[Unpack[Ts]], T], *args: Unpack[Ts], some_kwarg: str = "asdf") -> T:
2732+
raise
2733+
2734+
2735+
def foo() -> str:
2736+
return "hello"
2737+
2738+
2739+
call(run, foo, some_kwarg="a") # E: Argument 1 to "call" has incompatible type "def [Ts`-1, T] run(func: def (*Unpack[Ts]) -> T, *args: Unpack[Ts], some_kwarg: str = ...) -> T"; expected "Callable[[Callable[[], str], str], str]"
2740+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)