Skip to content

Commit ab2bdd7

Browse files
committed
Fix IndexError when unpacking empty tuple in type alias
When expanding type arguments for a builtins.tuple instance, the normalization code accessed args[0] without checking if args was non-empty. This caused an IndexError when Unpack[tuple[()]] was used in a type alias, since the empty tuple expansion produces zero args. Fixes #20913
1 parent 1d8ddd5 commit ab2bdd7

File tree

2 files changed

+36
-2
lines changed

2 files changed

+36
-2
lines changed

mypy/expandtype.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,10 @@ def visit_instance(self, t: Instance) -> Type:
224224
return t.copy_modified(args=args)
225225

226226
if t.type.fullname == "builtins.tuple":
227+
if not args:
228+
# After expansion, args is empty (e.g. Unpack[tuple[()]] expanded
229+
# to nothing). Return the empty fixed-length tuple tuple[()].
230+
return TupleType([], fallback=t.copy_modified(args=[]))
227231
# Normalize Tuple[*Tuple[X, ...], ...] -> Tuple[X, ...]
228232
arg = args[0]
229233
if isinstance(arg, UnpackType):
@@ -519,7 +523,15 @@ def expand_type_list_with_unpack(self, typs: list[Type]) -> list[Type]:
519523
if isinstance(item, UnpackType) and isinstance(item.type, TypeVarTupleType):
520524
items.extend(self.expand_unpack(item))
521525
else:
522-
items.append(item.accept(self))
526+
expanded = item.accept(self)
527+
if isinstance(expanded, UnpackType) and isinstance(
528+
expanded.type, TupleType
529+
):
530+
# Inline Unpack[tuple[X, Y]] -> X, Y
531+
# This also handles Unpack[tuple[()]] -> nothing
532+
items.extend(expanded.type.items)
533+
continue
534+
items.append(expanded)
523535
return items
524536

525537
def expand_type_tuple_with_unpack(self, typs: tuple[Type, ...]) -> list[Type]:
@@ -530,7 +542,15 @@ def expand_type_tuple_with_unpack(self, typs: tuple[Type, ...]) -> list[Type]:
530542
if isinstance(item, UnpackType) and isinstance(item.type, TypeVarTupleType):
531543
items.extend(self.expand_unpack(item))
532544
else:
533-
items.append(item.accept(self))
545+
expanded = item.accept(self)
546+
if isinstance(expanded, UnpackType) and isinstance(
547+
expanded.type, TupleType
548+
):
549+
# Inline Unpack[tuple[X, Y]] -> X, Y
550+
# This also handles Unpack[tuple[()]] -> nothing
551+
items.extend(expanded.type.items)
552+
continue
553+
items.append(expanded)
534554
return items
535555

536556
def visit_tuple_type(self, t: TupleType) -> Type:

test-data/unit/check-python312.test

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2237,3 +2237,17 @@ class D[*Ts](Generic[Unpack[Us]]): # E: Generic[...] base class is redundant \
22372237
# E: Can only use one type var tuple in a class def
22382238
pass
22392239
[builtins fixtures/tuple.pyi]
2240+
2241+
[case testUnpackEmptyTupleInTypeAliasNoCrash]
2242+
# https://github.com/python/mypy/issues/20913
2243+
from typing import Unpack
2244+
2245+
class C[*Ts]:
2246+
pass
2247+
2248+
type T[T, *Ts] = C[*Ts]
2249+
2250+
x: T[bool, Unpack[tuple[()]]]
2251+
reveal_type(x) # N: Revealed type is "__main__.C[()]"
2252+
[builtins fixtures/tuple.pyi]
2253+
[typing fixtures/typing-full.pyi]

0 commit comments

Comments
 (0)