Skip to content

Commit 1b524a5

Browse files
committed
Fix variadic ParamSpec expansion crash
Expand unpacked TypeVarTuple parameters while preserving aligned argument types, kinds, and names. Fixes #21778.
1 parent b4b8d74 commit 1b524a5

2 files changed

Lines changed: 49 additions & 1 deletion

File tree

mypy/expandtype.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,29 @@ def expand_unpack(self, t: UnpackType) -> list[Type]:
400400
raise RuntimeError(f"Invalid type replacement to expand: {repl}")
401401

402402
def visit_parameters(self, t: Parameters) -> Type:
403-
return t.copy_modified(arg_types=self.expand_types(t.arg_types))
403+
arg_types: list[Type] = []
404+
arg_kinds: list[ArgKind] = []
405+
arg_names: list[str | None] = []
406+
for arg_type, arg_kind, arg_name in zip(t.arg_types, t.arg_kinds, t.arg_names):
407+
if (
408+
arg_kind == ARG_STAR
409+
and isinstance(arg_type, UnpackType)
410+
and isinstance(arg_type.type, TypeVarTupleType)
411+
):
412+
expanded = self.expand_unpack(arg_type)
413+
for item in expanded:
414+
arg_types.append(item)
415+
if isinstance(item, UnpackType):
416+
arg_kinds.append(ARG_STAR)
417+
arg_names.append(arg_name)
418+
else:
419+
arg_kinds.append(ArgKind.ARG_POS)
420+
arg_names.append(None)
421+
else:
422+
arg_types.append(arg_type.accept(self))
423+
arg_kinds.append(arg_kind)
424+
arg_names.append(arg_name)
425+
return t.copy_modified(arg_types=arg_types, arg_kinds=arg_kinds, arg_names=arg_names)
404426

405427
def interpolate_args_for_unpack(self, t: CallableType, var_arg: UnpackType) -> list[Type]:
406428
star_index = t.arg_kinds.index(ARG_STAR)

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,32 @@ reveal_type(whatever) # N: Revealed type is "def (x: builtins.int) -> builtins.
141141
reveal_type(whatever(217)) # N: Revealed type is "builtins.list[builtins.int]"
142142
[builtins fixtures/paramspec.pyi]
143143

144+
[case testParamSpecVariadicContextManager]
145+
from typing import Callable, Generic, TypeVar, TypeVarTuple, Unpack
146+
from typing_extensions import ParamSpec
147+
148+
P = ParamSpec("P")
149+
R = TypeVar("R")
150+
Ts = TypeVarTuple("Ts")
151+
152+
class contextmanager(Generic[P, R]):
153+
def __init__(self, func: Callable[P, R]) -> None: ...
154+
155+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> "_contextmanager_cls[P, R]": ...
156+
157+
class _contextmanager_cls(Generic[P, R]):
158+
def __enter__(self) -> R: ...
159+
def __exit__(self, *args: object) -> bool: ...
160+
161+
@contextmanager
162+
def print_args(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]: ...
163+
164+
with print_args(2, "x") as value:
165+
reveal_type(value) # N: Revealed type is "tuple[builtins.int, builtins.str]"
166+
167+
reveal_type(print_args(2, "x")) # N: Revealed type is "__main__._contextmanager_cls[[Literal[2]?, Literal['x']?], tuple[Literal[2]?, Literal['x']?]]"
168+
[builtins fixtures/tuple.pyi]
169+
144170
[case testInvalidParamSpecType]
145171
from typing import ParamSpec
146172

0 commit comments

Comments
 (0)