From 1a2a5b876d14f8a3b0aaeb58ce9a3087f85abb8c Mon Sep 17 00:00:00 2001 From: Randolf Scholz Date: Thu, 21 Aug 2025 11:51:37 +0200 Subject: [PATCH 1/3] fix crash with nested tuple --- mypy/typeanal.py | 4 ++-- test-data/unit/check-python312.test | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 204d3061c734..d3d7687046c6 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -1964,7 +1964,7 @@ def check_unpacks_in_list(self, items: list[Type]) -> list[Type]: new_items: list[Type] = [] num_unpacks = 0 final_unpack = None - for item in items: + for item in flatten_nested_tuples(items): # TODO: handle forward references here, they appear as Unpack[Any]. if isinstance(item, UnpackType) and not isinstance( get_proper_type(item.type), TupleType @@ -1978,7 +1978,7 @@ def check_unpacks_in_list(self, items: list[Type]) -> list[Type]: if num_unpacks > 1: assert final_unpack is not None - self.fail("More than one Unpack in a type is not allowed", final_unpack.type) + self.fail("More than one variable Unpack in a type is not allowed", final_unpack.type) return new_items def tuple_type(self, items: list[Type], line: int, column: int) -> TupleType: diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index 817184dc561c..70b8571ade49 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -2039,6 +2039,7 @@ class Z: ... # E: Name "Z" already defined on line 2 class A[*Ts]: ... A[*tuple[int, ...], *tuple[int, ...]] # E: More than one Unpack in a type is not allowed +A[*tuple[*tuple[int, ...]], *tuple[*tuple[int, ...]]] # E: More than one Unpack in a type is not allowed a: A[*tuple[int, ...], *tuple[int, ...]] # E: More than one Unpack in a type is not allowed def foo(a: A[*tuple[int, ...], *tuple[int, ...]]): ... # E: More than one Unpack in a type is not allowed From f5820a10f1a71b3317289a84a55f25fb8f5f2f09 Mon Sep 17 00:00:00 2001 From: Randolf Scholz Date: Thu, 21 Aug 2025 14:14:06 +0200 Subject: [PATCH 2/3] flatten types before application --- mypy/semanal.py | 5 ++++- mypy/typeanal.py | 2 +- test-data/unit/check-python312.test | 12 ++++++------ test-data/unit/check-typevar-tuple.test | 18 +++++++++--------- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 77e6b0c005e2..fa5d9fdc82c4 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -295,6 +295,7 @@ UnboundType, UnionType, UnpackType, + flatten_nested_tuples, get_proper_type, get_proper_types, has_type_vars, @@ -6093,7 +6094,9 @@ def analyze_type_application_args(self, expr: IndexExpr) -> list[Type] | None: types.append(analyzed) if allow_unpack: - types = self.type_analyzer().check_unpacks_in_list(types) + # need to flatten away harmless unpacks like Unpack[tuple[int]] + flattened_items = flatten_nested_tuples(types) + types = self.type_analyzer().check_unpacks_in_list(flattened_items) if has_param_spec and num_args == 1 and types: first_arg = get_proper_type(types[0]) single_any = len(types) == 1 and isinstance(first_arg, AnyType) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index d3d7687046c6..97c6601901e8 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -1964,7 +1964,7 @@ def check_unpacks_in_list(self, items: list[Type]) -> list[Type]: new_items: list[Type] = [] num_unpacks = 0 final_unpack = None - for item in flatten_nested_tuples(items): + for item in items: # TODO: handle forward references here, they appear as Unpack[Any]. if isinstance(item, UnpackType) and not isinstance( get_proper_type(item.type), TupleType diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index 70b8571ade49..07d2648f4051 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -2038,13 +2038,13 @@ class Z: ... # E: Name "Z" already defined on line 2 # https://github.com/python/mypy/issues/18856 class A[*Ts]: ... -A[*tuple[int, ...], *tuple[int, ...]] # E: More than one Unpack in a type is not allowed -A[*tuple[*tuple[int, ...]], *tuple[*tuple[int, ...]]] # E: More than one Unpack in a type is not allowed -a: A[*tuple[int, ...], *tuple[int, ...]] # E: More than one Unpack in a type is not allowed -def foo(a: A[*tuple[int, ...], *tuple[int, ...]]): ... # E: More than one Unpack in a type is not allowed +A[*tuple[int, ...], *tuple[int, ...]] # E: More than one variable Unpack in a type is not allowed +A[*tuple[*tuple[int, ...]], *tuple[*tuple[int, ...]]] # E: More than one variable Unpack in a type is not allowed +a: A[*tuple[int, ...], *tuple[int, ...]] # E: More than one variable Unpack in a type is not allowed +def foo(a: A[*tuple[int, ...], *tuple[int, ...]]): ... # E: More than one variable Unpack in a type is not allowed -tuple[*tuple[int, ...], *tuple[int, ...]] # E: More than one Unpack in a type is not allowed -b: tuple[*tuple[int, ...], *tuple[int, ...]] # E: More than one Unpack in a type is not allowed +tuple[*tuple[int, ...], *tuple[int, ...]] # E: More than one variable Unpack in a type is not allowed +b: tuple[*tuple[int, ...], *tuple[int, ...]] # E: More than one variable Unpack in a type is not allowed [builtins fixtures/tuple.pyi] [typing fixtures/typing-full.pyi] diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index 2de2e45f0a96..b8bd57affde7 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -123,7 +123,7 @@ reveal_type(empty) # N: Revealed type is "__main__.Variadic[()]" omitted: Variadic reveal_type(omitted) # N: Revealed type is "__main__.Variadic[Unpack[builtins.tuple[Any, ...]]]" -bad: Variadic[Unpack[Tuple[int, ...]], str, Unpack[Tuple[bool, ...]]] # E: More than one Unpack in a type is not allowed +bad: Variadic[Unpack[Tuple[int, ...]], str, Unpack[Tuple[bool, ...]]] # E: More than one variable Unpack in a type is not allowed reveal_type(bad) # N: Revealed type is "__main__.Variadic[Unpack[builtins.tuple[builtins.int, ...]], builtins.str]" bad2: Unpack[Tuple[int, ...]] # E: Unpack is only valid in a variadic position @@ -353,12 +353,12 @@ expect_variadic_array_2(u) Ts = TypeVarTuple("Ts") Ts2 = TypeVarTuple("Ts2") -def bad(x: Tuple[int, Unpack[Ts], str, Unpack[Ts2]]) -> None: # E: More than one Unpack in a type is not allowed +def bad(x: Tuple[int, Unpack[Ts], str, Unpack[Ts2]]) -> None: # E: More than one variable Unpack in a type is not allowed ... reveal_type(bad) # N: Revealed type is "def [Ts, Ts2] (x: tuple[builtins.int, Unpack[Ts`-1], builtins.str])" -def bad2(x: Tuple[int, Unpack[Tuple[int, ...]], str, Unpack[Tuple[str, ...]]]) -> None: # E: More than one Unpack in a type is not allowed +def bad2(x: Tuple[int, Unpack[Tuple[int, ...]], str, Unpack[Tuple[str, ...]]]) -> None: # E: More than one variable Unpack in a type is not allowed ... reveal_type(bad2) # N: Revealed type is "def (x: tuple[builtins.int, Unpack[builtins.tuple[builtins.int, ...]], builtins.str])" [builtins fixtures/tuple.pyi] @@ -571,7 +571,7 @@ from typing_extensions import Unpack, TypeVarTuple Ts = TypeVarTuple("Ts") Us = TypeVarTuple("Us") -a: Callable[[Unpack[Ts], Unpack[Us]], int] # E: More than one Unpack in a type is not allowed +a: Callable[[Unpack[Ts], Unpack[Us]], int] # E: More than one variable Unpack in a type is not allowed reveal_type(a) # N: Revealed type is "def [Ts, Us] (*Unpack[Ts`-1]) -> builtins.int" b: Callable[[Unpack], int] # E: Unpack[...] requires exactly one type argument reveal_type(b) # N: Revealed type is "def (*Any) -> builtins.int" @@ -725,15 +725,15 @@ Ts = TypeVarTuple("Ts") Us = TypeVarTuple("Us") class G(Generic[Unpack[Ts]]): ... -A = Tuple[Unpack[Ts], Unpack[Us]] # E: More than one Unpack in a type is not allowed +A = Tuple[Unpack[Ts], Unpack[Us]] # E: More than one variable Unpack in a type is not allowed x: A[int, str] reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.str]" -B = Callable[[Unpack[Ts], Unpack[Us]], int] # E: More than one Unpack in a type is not allowed +B = Callable[[Unpack[Ts], Unpack[Us]], int] # E: More than one variable Unpack in a type is not allowed y: B[int, str] reveal_type(y) # N: Revealed type is "def (builtins.int, builtins.str) -> builtins.int" -C = G[Unpack[Ts], Unpack[Us]] # E: More than one Unpack in a type is not allowed +C = G[Unpack[Ts], Unpack[Us]] # E: More than one variable Unpack in a type is not allowed z: C[int, str] reveal_type(z) # N: Revealed type is "__main__.G[builtins.int, builtins.str]" [builtins fixtures/tuple.pyi] @@ -2223,12 +2223,12 @@ cb2(1, 2, 3, a="a", b="b") cb2(1, a="a", b="b") # E: Too few arguments cb2(1, 2, 3, a="a") # E: Missing named argument "b" -bad1: Callable[[Unpack[Ints], Unpack[Ints]], None] # E: More than one Unpack in a type is not allowed +bad1: Callable[[Unpack[Ints], Unpack[Ints]], None] # E: More than one variable Unpack in a type is not allowed reveal_type(bad1) # N: Revealed type is "def (*builtins.int)" bad2: Callable[[Unpack[Keywords], Unpack[Keywords]], None] # E: "Keywords" cannot be unpacked (must be tuple or TypeVarTuple) reveal_type(bad2) # N: Revealed type is "def (*Any, **Unpack[TypedDict('__main__.Keywords', {'a': builtins.str, 'b': builtins.str})])" bad3: Callable[[Unpack[Keywords], Unpack[Ints]], None] # E: "Keywords" cannot be unpacked (must be tuple or TypeVarTuple) \ - # E: More than one Unpack in a type is not allowed + # E: More than one variable Unpack in a type is not allowed reveal_type(bad3) # N: Revealed type is "def (*Any)" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] From 4a56aaa5474ae36bd482461fecda7de6b3d59f9b Mon Sep 17 00:00:00 2001 From: Randolf Scholz Date: Thu, 28 Aug 2025 13:26:58 +0200 Subject: [PATCH 3/3] rename 'variable unpack' -> 'variadic unpack' --- mypy/typeanal.py | 2 +- test-data/unit/check-python312.test | 12 ++++++------ test-data/unit/check-typevar-tuple.test | 18 +++++++++--------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 97c6601901e8..d44b13880cbb 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -1978,7 +1978,7 @@ def check_unpacks_in_list(self, items: list[Type]) -> list[Type]: if num_unpacks > 1: assert final_unpack is not None - self.fail("More than one variable Unpack in a type is not allowed", final_unpack.type) + self.fail("More than one variadic Unpack in a type is not allowed", final_unpack.type) return new_items def tuple_type(self, items: list[Type], line: int, column: int) -> TupleType: diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index 07d2648f4051..01364bdfa32a 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -2038,13 +2038,13 @@ class Z: ... # E: Name "Z" already defined on line 2 # https://github.com/python/mypy/issues/18856 class A[*Ts]: ... -A[*tuple[int, ...], *tuple[int, ...]] # E: More than one variable Unpack in a type is not allowed -A[*tuple[*tuple[int, ...]], *tuple[*tuple[int, ...]]] # E: More than one variable Unpack in a type is not allowed -a: A[*tuple[int, ...], *tuple[int, ...]] # E: More than one variable Unpack in a type is not allowed -def foo(a: A[*tuple[int, ...], *tuple[int, ...]]): ... # E: More than one variable Unpack in a type is not allowed +A[*tuple[int, ...], *tuple[int, ...]] # E: More than one variadic Unpack in a type is not allowed +A[*tuple[*tuple[int, ...]], *tuple[*tuple[int, ...]]] # E: More than one variadic Unpack in a type is not allowed +a: A[*tuple[int, ...], *tuple[int, ...]] # E: More than one variadic Unpack in a type is not allowed +def foo(a: A[*tuple[int, ...], *tuple[int, ...]]): ... # E: More than one variadic Unpack in a type is not allowed -tuple[*tuple[int, ...], *tuple[int, ...]] # E: More than one variable Unpack in a type is not allowed -b: tuple[*tuple[int, ...], *tuple[int, ...]] # E: More than one variable Unpack in a type is not allowed +tuple[*tuple[int, ...], *tuple[int, ...]] # E: More than one variadic Unpack in a type is not allowed +b: tuple[*tuple[int, ...], *tuple[int, ...]] # E: More than one variadic Unpack in a type is not allowed [builtins fixtures/tuple.pyi] [typing fixtures/typing-full.pyi] diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index b8bd57affde7..c668f14eaa50 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -123,7 +123,7 @@ reveal_type(empty) # N: Revealed type is "__main__.Variadic[()]" omitted: Variadic reveal_type(omitted) # N: Revealed type is "__main__.Variadic[Unpack[builtins.tuple[Any, ...]]]" -bad: Variadic[Unpack[Tuple[int, ...]], str, Unpack[Tuple[bool, ...]]] # E: More than one variable Unpack in a type is not allowed +bad: Variadic[Unpack[Tuple[int, ...]], str, Unpack[Tuple[bool, ...]]] # E: More than one variadic Unpack in a type is not allowed reveal_type(bad) # N: Revealed type is "__main__.Variadic[Unpack[builtins.tuple[builtins.int, ...]], builtins.str]" bad2: Unpack[Tuple[int, ...]] # E: Unpack is only valid in a variadic position @@ -353,12 +353,12 @@ expect_variadic_array_2(u) Ts = TypeVarTuple("Ts") Ts2 = TypeVarTuple("Ts2") -def bad(x: Tuple[int, Unpack[Ts], str, Unpack[Ts2]]) -> None: # E: More than one variable Unpack in a type is not allowed +def bad(x: Tuple[int, Unpack[Ts], str, Unpack[Ts2]]) -> None: # E: More than one variadic Unpack in a type is not allowed ... reveal_type(bad) # N: Revealed type is "def [Ts, Ts2] (x: tuple[builtins.int, Unpack[Ts`-1], builtins.str])" -def bad2(x: Tuple[int, Unpack[Tuple[int, ...]], str, Unpack[Tuple[str, ...]]]) -> None: # E: More than one variable Unpack in a type is not allowed +def bad2(x: Tuple[int, Unpack[Tuple[int, ...]], str, Unpack[Tuple[str, ...]]]) -> None: # E: More than one variadic Unpack in a type is not allowed ... reveal_type(bad2) # N: Revealed type is "def (x: tuple[builtins.int, Unpack[builtins.tuple[builtins.int, ...]], builtins.str])" [builtins fixtures/tuple.pyi] @@ -571,7 +571,7 @@ from typing_extensions import Unpack, TypeVarTuple Ts = TypeVarTuple("Ts") Us = TypeVarTuple("Us") -a: Callable[[Unpack[Ts], Unpack[Us]], int] # E: More than one variable Unpack in a type is not allowed +a: Callable[[Unpack[Ts], Unpack[Us]], int] # E: More than one variadic Unpack in a type is not allowed reveal_type(a) # N: Revealed type is "def [Ts, Us] (*Unpack[Ts`-1]) -> builtins.int" b: Callable[[Unpack], int] # E: Unpack[...] requires exactly one type argument reveal_type(b) # N: Revealed type is "def (*Any) -> builtins.int" @@ -725,15 +725,15 @@ Ts = TypeVarTuple("Ts") Us = TypeVarTuple("Us") class G(Generic[Unpack[Ts]]): ... -A = Tuple[Unpack[Ts], Unpack[Us]] # E: More than one variable Unpack in a type is not allowed +A = Tuple[Unpack[Ts], Unpack[Us]] # E: More than one variadic Unpack in a type is not allowed x: A[int, str] reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.str]" -B = Callable[[Unpack[Ts], Unpack[Us]], int] # E: More than one variable Unpack in a type is not allowed +B = Callable[[Unpack[Ts], Unpack[Us]], int] # E: More than one variadic Unpack in a type is not allowed y: B[int, str] reveal_type(y) # N: Revealed type is "def (builtins.int, builtins.str) -> builtins.int" -C = G[Unpack[Ts], Unpack[Us]] # E: More than one variable Unpack in a type is not allowed +C = G[Unpack[Ts], Unpack[Us]] # E: More than one variadic Unpack in a type is not allowed z: C[int, str] reveal_type(z) # N: Revealed type is "__main__.G[builtins.int, builtins.str]" [builtins fixtures/tuple.pyi] @@ -2223,12 +2223,12 @@ cb2(1, 2, 3, a="a", b="b") cb2(1, a="a", b="b") # E: Too few arguments cb2(1, 2, 3, a="a") # E: Missing named argument "b" -bad1: Callable[[Unpack[Ints], Unpack[Ints]], None] # E: More than one variable Unpack in a type is not allowed +bad1: Callable[[Unpack[Ints], Unpack[Ints]], None] # E: More than one variadic Unpack in a type is not allowed reveal_type(bad1) # N: Revealed type is "def (*builtins.int)" bad2: Callable[[Unpack[Keywords], Unpack[Keywords]], None] # E: "Keywords" cannot be unpacked (must be tuple or TypeVarTuple) reveal_type(bad2) # N: Revealed type is "def (*Any, **Unpack[TypedDict('__main__.Keywords', {'a': builtins.str, 'b': builtins.str})])" bad3: Callable[[Unpack[Keywords], Unpack[Ints]], None] # E: "Keywords" cannot be unpacked (must be tuple or TypeVarTuple) \ - # E: More than one variable Unpack in a type is not allowed + # E: More than one variadic Unpack in a type is not allowed reveal_type(bad3) # N: Revealed type is "def (*Any)" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi]