From 0af3fd56dae8a1002071acc37e71e9c79361342d Mon Sep 17 00:00:00 2001 From: aviralgarg05 Date: Fri, 9 Jan 2026 23:42:40 +0530 Subject: [PATCH 1/4] Report error when type alias has multiple TypeVarTuples (PEP 695) When defining a type alias using the PEP 695 syntax with multiple TypeVarTuple-based type parameters (e.g., `type TA[*Ts1, *Ts2] = ...`), mypy now reports an error at definition time rather than only when the alias is used. This is consistent with how mypy handles classes with multiple TypeVarTuples and matches the behavior of other type checkers like pyright and pyrefly. Fixes #20544. --- mypy/semanal.py | 9 +++++++++ test-data/unit/check-python312.test | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/mypy/semanal.py b/mypy/semanal.py index 11f0156372bf..5a929db74707 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -5641,6 +5641,15 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None: return all_type_params_names = [p.name for p in s.type_args] + # Check for multiple TypeVarTuples in type alias definition + has_type_var_tuple = False + for p in s.type_args: + if p.kind == TYPE_VAR_TUPLE_KIND: + if has_type_var_tuple: + self.fail("Can only use one TypeVarTuple in a type alias", s) + break + has_type_var_tuple = True + try: existing = self.current_symbol_table().get(s.name.name) if existing and not ( diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index 0521722b47c1..f7a567fbd7ba 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -2237,3 +2237,13 @@ class D[*Ts](Generic[Unpack[Us]]): # E: Generic[...] base class is redundant \ # E: Can only use one type var tuple in a class def pass [builtins fixtures/tuple.pyi] + +[case testPEP695MultipleTypeVarTuplesTypeAlias] +type TA1[*Ts1, *Ts2] = tuple[*Ts1] | tuple[*Ts2] # E: Can only use one TypeVarTuple in a type alias +type TA2[T, *Ts1, *Ts2] = tuple[T, *Ts1, *Ts2] # E: Can only use one TypeVarTuple in a type alias \ + # E: More than one variadic Unpack in a type is not allowed +type TA3[*Ts1, T, *Ts2] = tuple[*Ts1, T, *Ts2] # E: Can only use one TypeVarTuple in a type alias \ + # E: More than one variadic Unpack in a type is not allowed +type TA4[*Ts] = tuple[*Ts] # OK - single TypeVarTuple is fine +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] From 37c4edea6b36469aeb354260a5a873de66faf13f Mon Sep 17 00:00:00 2001 From: aviralgarg05 Date: Tue, 13 Jan 2026 11:38:42 +0530 Subject: [PATCH 2/4] Support multiple TypeVarTuples check for old-style type aliases --- mypy/semanal.py | 2 ++ test-data/unit/check-python312.test | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/mypy/semanal.py b/mypy/semanal.py index 5a929db74707..039242da52c4 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -4020,6 +4020,8 @@ def analyze_alias( for td in tvar_defs: if isinstance(td, TypeVarTupleType): if variadic: + if not python_3_12_type_alias: + self.fail("Can only use one TypeVarTuple in a type alias", rvalue) continue variadic = True new_tvar_defs.append(td) diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index f7a567fbd7ba..b09693091d1f 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -2247,3 +2247,16 @@ type TA3[*Ts1, T, *Ts2] = tuple[*Ts1, T, *Ts2] # E: Can only use one TypeVarTup type TA4[*Ts] = tuple[*Ts] # OK - single TypeVarTuple is fine [builtins fixtures/tuple.pyi] [typing fixtures/typing-full.pyi] + + +[case testMultipleTypeVarTuplesOldStyle] +# flags: --python-version 3.12 +from typing import Union +from typing_extensions import TypeVarTuple, Unpack + +Ts1 = TypeVarTuple("Ts1") +Ts2 = TypeVarTuple("Ts2") + +TA = Union[tuple[*Ts1], tuple[*Ts2]] # E: Can only use one TypeVarTuple in a type alias +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] From 6b0d86dbdc5fbfefbdfb5affb19415ead018b2d5 Mon Sep 17 00:00:00 2001 From: aviralgarg05 Date: Tue, 13 Jan 2026 13:28:09 +0530 Subject: [PATCH 3/4] Consolidate multiple TypeVarTuples check in analyze_alias --- mypy/semanal.py | 13 ++----------- test-data/unit/check-typevar-tuple.test | 9 ++++++--- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 039242da52c4..5bf5e3a63b06 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -4014,14 +4014,13 @@ def analyze_alias( python_3_12_type_alias=python_3_12_type_alias, ) - # There can be only one variadic variable at most, the error is reported elsewhere. + # Check for multiple TypeVarTuples in type alias definition new_tvar_defs = [] variadic = False for td in tvar_defs: if isinstance(td, TypeVarTupleType): if variadic: - if not python_3_12_type_alias: - self.fail("Can only use one TypeVarTuple in a type alias", rvalue) + self.fail("Can only use one TypeVarTuple in a type alias", rvalue) continue variadic = True new_tvar_defs.append(td) @@ -5643,14 +5642,6 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None: return all_type_params_names = [p.name for p in s.type_args] - # Check for multiple TypeVarTuples in type alias definition - has_type_var_tuple = False - for p in s.type_args: - if p.kind == TYPE_VAR_TUPLE_KIND: - if has_type_var_tuple: - self.fail("Can only use one TypeVarTuple in a type alias", s) - break - has_type_var_tuple = True try: existing = self.current_symbol_table().get(s.name.name) diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index f6d9de8b0c1a..f9192560cf52 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -725,15 +725,18 @@ Ts = TypeVarTuple("Ts") Us = TypeVarTuple("Us") class G(Generic[Unpack[Ts]]): ... -A = Tuple[Unpack[Ts], Unpack[Us]] # E: More than one variadic Unpack in a type is not allowed +A = Tuple[Unpack[Ts], Unpack[Us]] # E: Can only use one TypeVarTuple in a type alias \ + # 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 variadic Unpack in a type is not allowed +B = Callable[[Unpack[Ts], Unpack[Us]], int] # E: Can only use one TypeVarTuple in a type alias \ + # 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 variadic Unpack in a type is not allowed +C = G[Unpack[Ts], Unpack[Us]] # E: Can only use one TypeVarTuple in a type alias \ + # 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] From 49e13ffb5b6e5435ee9ee69374dff18f77eb15ec Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 08:00:22 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/semanal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 5bf5e3a63b06..560d4eeb74e9 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -5642,7 +5642,6 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None: return all_type_params_names = [p.name for p in s.type_args] - try: existing = self.current_symbol_table().get(s.name.name) if existing and not (