Skip to content

Commit 6f80fe0

Browse files
authored
Forbid unpacking multiple times in tuples (#13888)
This covers some cases from the PEP646 documentation which says we should error when there are multiple unpacks: x: Tuple[int, *Ts, str, *Ts2] # Error y: Tuple[int, *Tuple[int, ...], str, *Tuple[str, ...]] # Error We handle it gracefully and include only one of the unpacks so that type checking can still continue somewhat.
1 parent c810a9c commit 6f80fe0

File tree

2 files changed

+41
-2
lines changed

2 files changed

+41
-2
lines changed

mypy/typeanal.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1389,7 +1389,7 @@ def anal_array(
13891389
res: list[Type] = []
13901390
for t in a:
13911391
res.append(self.anal_type(t, nested, allow_param_spec=allow_param_spec))
1392-
return res
1392+
return self.check_unpacks_in_list(res)
13931393

13941394
def anal_type(self, t: Type, nested: bool = True, *, allow_param_spec: bool = False) -> Type:
13951395
if nested:
@@ -1448,10 +1448,30 @@ def named_type(
14481448
node = self.lookup_fqn_func(fully_qualified_name)
14491449
assert isinstance(node.node, TypeInfo)
14501450
any_type = AnyType(TypeOfAny.special_form)
1451+
if args is not None:
1452+
args = self.check_unpacks_in_list(args)
14511453
return Instance(
14521454
node.node, args or [any_type] * len(node.node.defn.type_vars), line=line, column=column
14531455
)
14541456

1457+
def check_unpacks_in_list(self, items: list[Type]) -> list[Type]:
1458+
new_items: list[Type] = []
1459+
num_unpacks = 0
1460+
final_unpack = None
1461+
for item in items:
1462+
if isinstance(item, UnpackType):
1463+
if not num_unpacks:
1464+
new_items.append(item)
1465+
num_unpacks += 1
1466+
final_unpack = item
1467+
else:
1468+
new_items.append(item)
1469+
1470+
if num_unpacks > 1:
1471+
assert final_unpack is not None
1472+
self.fail("More than one Unpack in a type is not allowed", final_unpack)
1473+
return new_items
1474+
14551475
def tuple_type(self, items: list[Type]) -> TupleType:
14561476
any_type = AnyType(TypeOfAny.special_form)
14571477
return TupleType(items, fallback=self.named_type("builtins.tuple", [any_type]))

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ reveal_type(h(args)) # N: Revealed type is "Tuple[builtins.str, builtins.str, b
9595
[builtins fixtures/tuple.pyi]
9696

9797
[case testTypeVarTupleGenericClassDefn]
98-
from typing import Generic, TypeVar, Tuple
98+
from typing import Generic, TypeVar, Tuple, Union
9999
from typing_extensions import TypeVarTuple, Unpack
100100

101101
T = TypeVar("T")
@@ -120,6 +120,13 @@ empty: Variadic[()]
120120
# TODO: fix pretty printer to be better.
121121
reveal_type(empty) # N: Revealed type is "__main__.Variadic"
122122

123+
bad: Variadic[Unpack[Tuple[int, ...]], str, Unpack[Tuple[bool, ...]]] # E: More than one Unpack in a type is not allowed
124+
reveal_type(bad) # N: Revealed type is "__main__.Variadic[Unpack[builtins.tuple[builtins.int, ...]], builtins.str]"
125+
126+
# TODO: This is tricky to fix because we need typeanal to know whether the current
127+
# location is valid for an Unpack or not.
128+
# bad2: Unpack[Tuple[int, ...]]
129+
123130
m1: Mixed1[int, str, bool]
124131
reveal_type(m1) # N: Revealed type is "__main__.Mixed1[builtins.int, builtins.str, builtins.bool]"
125132

@@ -345,6 +352,18 @@ def expect_variadic_array_2(
345352
expect_variadic_array(u)
346353
expect_variadic_array_2(u)
347354

355+
Ts = TypeVarTuple("Ts")
356+
Ts2 = TypeVarTuple("Ts2")
357+
358+
def bad(x: Tuple[int, Unpack[Ts], str, Unpack[Ts2]]) -> None: # E: More than one Unpack in a type is not allowed
359+
360+
...
361+
reveal_type(bad) # N: Revealed type is "def [Ts, Ts2] (x: Tuple[builtins.int, Unpack[Ts`-1], builtins.str])"
362+
363+
def bad2(x: Tuple[int, Unpack[Tuple[int, ...]], str, Unpack[Tuple[str, ...]]]) -> None: # E: More than one Unpack in a type is not allowed
364+
...
365+
reveal_type(bad2) # N: Revealed type is "def (x: Tuple[builtins.int, Unpack[builtins.tuple[builtins.int, ...]], builtins.str])"
366+
348367

349368
[builtins fixtures/tuple.pyi]
350369

0 commit comments

Comments
 (0)