Skip to content

Commit 0ffc31a

Browse files
Use safe_meet() for callable argument merging to handle UnpackType
Fixes #20093 The callable_corresponding_argument() function in typeops.py was calling meet_types() directly, which doesn't handle UnpackType. This caused crashes when type-checking code combining ParamSpec and TypeVarTuple with Unpack. Changed to use safe_meet() instead, which properly handles UnpackType along with other variadic types. The safe_meet() function already exists in join.py and is designed specifically for handling variadic types like UnpackType. This approach is preferred over implementing visit_unpack_type() in meet.py because safe_meet() already contains the necessary logic for handling different kinds of unpacks (TypeVarTuple, Tuple, etc.) with proper fallback types. Added regression test in check-parameter-specification.test.
1 parent b266dd1 commit 0ffc31a

File tree

3 files changed

+27
-3
lines changed

3 files changed

+27
-3
lines changed

mypy/meet.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -830,7 +830,11 @@ def visit_type_var_tuple(self, t: TypeVarTupleType) -> ProperType:
830830
return self.default(self.s)
831831

832832
def visit_unpack_type(self, t: UnpackType) -> ProperType:
833-
raise NotImplementedError
833+
if isinstance(self.s, UnpackType):
834+
return UnpackType(self.meet(t.type, self.s.type))
835+
if isinstance(self.s, Instance) and self.s.type.fullname == "builtins.object":
836+
return t
837+
return self.default(self.s)
834838

835839
def visit_parameters(self, t: Parameters) -> ProperType:
836840
if isinstance(self.s, Parameters):

mypy/typeops.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -523,15 +523,15 @@ def callable_corresponding_argument(
523523

524524
# def right(a: int = ...) -> None: ...
525525
# def left(__a: int = ..., *, a: int = ...) -> None: ...
526-
from mypy.meet import meet_types
526+
from mypy.join import safe_meet
527527

528528
if (
529529
not (by_name.required or by_pos.required)
530530
and by_pos.name is None
531531
and by_name.pos is None
532532
):
533533
return FormalArgument(
534-
by_name.name, by_pos.pos, meet_types(by_name.typ, by_pos.typ), False
534+
by_name.name, by_pos.pos, safe_meet(by_name.typ, by_pos.typ), False
535535
)
536536
return by_name if by_name is not None else by_pos
537537

test-data/unit/check-parameter-specification.test

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2599,3 +2599,23 @@ def run3(predicate: Callable[Concatenate[int, str, _P], None], *args: _P.args, *
25992599
# E: Argument 1 has incompatible type "*tuple[Union[int, str], ...]"; expected "str" \
26002600
# E: Argument 1 has incompatible type "*tuple[Union[int, str], ...]"; expected "_P.args"
26012601
[builtins fixtures/paramspec.pyi]
2602+
2603+
[case testParamSpecWithTypeVarTupleUnpack]
2604+
# Regression test for crash with ParamSpec and TypeVarTuple Unpack in meet.py
2605+
from typing import Callable, ParamSpec, TypeVar, TypeVarTuple, Unpack
2606+
2607+
P = ParamSpec("P")
2608+
T = TypeVar("T")
2609+
Ts = TypeVarTuple("Ts")
2610+
2611+
def call(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
2612+
return func(*args, **kwargs)
2613+
2614+
def run(func: Callable[[Unpack[Ts]], T], *args: Unpack[Ts], some_kwarg: str = "asdf") -> T:
2615+
...
2616+
2617+
def foo() -> str:
2618+
...
2619+
2620+
call(run, foo, some_kwarg="a") # E: Argument 1 to "call" has incompatible type "Callable[[Callable[[VarArg(Unpack[Ts])], T], VarArg(Unpack[Ts]), DefaultNamedArg(str, 'some_kwarg')], T]"; expected "Callable[[Callable[[], str], str], str]"
2621+
[builtins fixtures/paramspec.pyi]

0 commit comments

Comments
 (0)