Skip to content

Commit 088cc2d

Browse files
committed
Preserve nested variadic ParamSpec arguments
Keep a residual unpack and its suffix together as one vararg when expanding Parameters. This avoids positional arguments after *args and preserves nested TypeVarTuple call semantics.
1 parent 1b524a5 commit 088cc2d

2 files changed

Lines changed: 29 additions & 8 deletions

File tree

mypy/expandtype.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -410,14 +410,29 @@ def visit_parameters(self, t: Parameters) -> Type:
410410
and isinstance(arg_type.type, TypeVarTupleType)
411411
):
412412
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)
413+
# Keep a residual unpack and its suffix together as one vararg. Otherwise
414+
# the suffix would become positional arguments placed after *args.
415+
unpack_index = next(
416+
(i for i, item in enumerate(expanded) if isinstance(item, UnpackType)), None
417+
)
418+
if unpack_index is not None:
419+
arg_types.extend(expanded[:unpack_index])
420+
arg_kinds.extend([ArgKind.ARG_POS] * unpack_index)
421+
arg_names.extend([None] * unpack_index)
422+
423+
unpack = expanded[unpack_index]
424+
assert isinstance(unpack, UnpackType)
425+
if unpack_index < len(expanded) - 1:
426+
unpack = UnpackType(
427+
TupleType(expanded[unpack_index:], arg_type.type.tuple_fallback)
428+
)
429+
arg_types.append(unpack)
430+
arg_kinds.append(ARG_STAR)
431+
arg_names.append(arg_name)
432+
else:
433+
arg_types.extend(expanded)
434+
arg_kinds.extend([ArgKind.ARG_POS] * len(expanded))
435+
arg_names.extend([None] * len(expanded))
421436
else:
422437
arg_types.append(arg_type.accept(self))
423438
arg_kinds.append(arg_kind)

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ from typing_extensions import ParamSpec
148148
P = ParamSpec("P")
149149
R = TypeVar("R")
150150
Ts = TypeVarTuple("Ts")
151+
Us = TypeVarTuple("Us")
151152

152153
class contextmanager(Generic[P, R]):
153154
def __init__(self, func: Callable[P, R]) -> None: ...
@@ -157,6 +158,7 @@ class contextmanager(Generic[P, R]):
157158
class _contextmanager_cls(Generic[P, R]):
158159
def __enter__(self) -> R: ...
159160
def __exit__(self, *args: object) -> bool: ...
161+
def invoke(self, *args: P.args, **kwargs: P.kwargs) -> R: ...
160162

161163
@contextmanager
162164
def print_args(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]: ...
@@ -165,6 +167,10 @@ with print_args(2, "x") as value:
165167
reveal_type(value) # N: Revealed type is "tuple[builtins.int, builtins.str]"
166168

167169
reveal_type(print_args(2, "x")) # N: Revealed type is "__main__._contextmanager_cls[[Literal[2]?, Literal['x']?], tuple[Literal[2]?, Literal['x']?]]"
170+
171+
def forward(*args: Unpack[Us]) -> None:
172+
manager = print_args(0, *args, "end")
173+
reveal_type(manager.invoke(0, *args, "end")) # N: Revealed type is "tuple[builtins.int, Unpack[Us`-1], builtins.str]"
168174
[builtins fixtures/tuple.pyi]
169175

170176
[case testInvalidParamSpecType]

0 commit comments

Comments
 (0)