Skip to content

Commit 351a61f

Browse files
authored
Unify multiple-TypeVarTuple error handling (#22216)
Fixes #20527 Currently we already have three places where we handle `Can only use one type var tuple ...`, but as the crash shows, we still didn't cover all cases. I refactored this so that: * All such validation is handled by one helper * There are just two call sites for it (one for classes, and one for type aliases) * Error locations are more robust/consistent I also slightly changed the error wording and logic so that it is as similar as possible for old vs new syntax, and for classes vs type aliases.
1 parent 0233192 commit 351a61f

7 files changed

Lines changed: 100 additions & 38 deletions

File tree

‎mypy/exprtotype.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,8 @@ def expr_to_unanalyzed_type(
253253
expr.expr, options, allow_new_syntax, lookup_qualified=lookup_qualified
254254
),
255255
from_star_syntax=True,
256+
line=expr.line,
257+
column=expr.column,
256258
)
257259
elif isinstance(expr, DictExpr):
258260
if not expr.items:

‎mypy/semanal.py‎

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2338,17 +2338,10 @@ class Foo(Bar, Generic[T]): ...
23382338
removed: list[int] = []
23392339
declared_tvars: TypeVarLikeList = []
23402340
is_protocol = False
2341-
has_type_var_tuple = False
23422341
if defn.type_args is not None:
23432342
for p in defn.type_args:
23442343
node = self.get_defined_type_param(p.name)
2345-
assert node is not None
23462344
assert isinstance(node, TypeVarLikeExpr)
2347-
if isinstance(node, TypeVarTupleExpr):
2348-
if has_type_var_tuple:
2349-
self.fail("Can only use one type var tuple in a class def", context)
2350-
continue
2351-
has_type_var_tuple = True
23522345
declared_tvars.append((p.name, node))
23532346

23542347
for i, base_expr in enumerate(base_type_exprs):
@@ -2361,7 +2354,7 @@ class Foo(Bar, Generic[T]): ...
23612354
except TypeTranslationError:
23622355
# This error will be caught later.
23632356
continue
2364-
result = self.analyze_class_typevar_declaration(base, has_type_var_tuple)
2357+
result = self.analyze_class_typevar_declaration(base)
23652358
if result is not None:
23662359
tvars = result[0]
23672360
is_protocol |= result[1]
@@ -2412,12 +2405,11 @@ class Foo(Bar, Generic[T]): ...
24122405
# grained incremental mode.
24132406
defn.removed_base_type_exprs.append(defn.base_type_exprs[i])
24142407
del base_type_exprs[i]
2408+
declared_tvars = self.pre_validate_declared_tvars(declared_tvars, context, "class")
24152409
tvar_defs = self.tvar_defs_from_tvars(declared_tvars, context)
24162410
return base_type_exprs, tvar_defs, is_protocol, declared_tvars
24172411

2418-
def analyze_class_typevar_declaration(
2419-
self, base: Type, has_type_var_tuple: bool
2420-
) -> tuple[TypeVarLikeList, bool] | None:
2412+
def analyze_class_typevar_declaration(self, base: Type) -> tuple[TypeVarLikeList, bool] | None:
24212413
"""Analyze type variables declared using Generic[...] or Protocol[...].
24222414
24232415
Args:
@@ -2443,11 +2435,6 @@ def analyze_class_typevar_declaration(
24432435
tag = self.track_incomplete_refs()
24442436
tvar = self.analyze_unbound_tvar(arg)
24452437
if tvar:
2446-
if isinstance(tvar[1], TypeVarTupleExpr):
2447-
if has_type_var_tuple:
2448-
self.fail("Can only use one type var tuple in a class def", base)
2449-
continue
2450-
has_type_var_tuple = True
24512438
tvars.append(tvar)
24522439
elif not self.found_incomplete_ref(tag):
24532440
self.fail("Free type variable expected in %s[...]" % sym.node.name, base)
@@ -4083,6 +4070,24 @@ def analyze_simple_literal_type(self, rvalue: Expression, is_final: bool) -> Typ
40834070
return typ.copy_modified(last_known_value=LiteralType(value=value, fallback=typ))
40844071
return typ
40854072

4073+
def pre_validate_declared_tvars(
4074+
self, declared_tvars: TypeVarLikeList, ctx: Context, kind: str
4075+
) -> TypeVarLikeList:
4076+
has_type_var_tuple = False
4077+
valid_declared_tvars = []
4078+
for name, node in declared_tvars:
4079+
if isinstance(node, TypeVarTupleExpr):
4080+
if has_type_var_tuple:
4081+
self.fail(
4082+
f"Can only use one TypeVarTuple in a {kind} definition",
4083+
ctx,
4084+
code=codes.TYPE_VAR,
4085+
)
4086+
continue
4087+
has_type_var_tuple = True
4088+
valid_declared_tvars.append((name, node))
4089+
return valid_declared_tvars
4090+
40864091
def analyze_alias(
40874092
self,
40884093
name: str,
@@ -4119,6 +4124,7 @@ def analyze_alias(
41194124
found_type_vars = self.find_type_var_likes(typ)
41204125
namespace = self.qualified_name(name)
41214126
alias_type_vars = found_type_vars if declared_type_vars is None else declared_type_vars
4127+
alias_type_vars = self.pre_validate_declared_tvars(alias_type_vars, rvalue, "type alias")
41224128
with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)):
41234129
tvar_defs = self.tvar_defs_from_tvars(alias_type_vars, typ)
41244130

@@ -4426,7 +4432,6 @@ def analyze_type_alias_type_params(
44264432

44274433
declared_tvars: TypeVarLikeList = []
44284434
all_declared_tvar_names: list[str] = [] # includes bound type variables
4429-
have_type_var_tuple = False
44304435
for tp_expr in type_params:
44314436
if isinstance(tp_expr, StarExpr):
44324437
tp_expr.valid = False
@@ -4440,18 +4445,7 @@ def analyze_type_alias_type_params(
44404445

44414446
tag = self.track_incomplete_refs()
44424447
tvar = self.analyze_unbound_tvar_impl(base, is_typealias_param=True)
4443-
if tvar:
4444-
if isinstance(tvar[1], TypeVarTupleExpr):
4445-
if have_type_var_tuple:
4446-
self.fail(
4447-
"Can only use one TypeVarTuple in type_params argument to TypeAliasType",
4448-
base,
4449-
code=codes.TYPE_VAR,
4450-
)
4451-
have_type_var_tuple = True
4452-
continue
4453-
have_type_var_tuple = True
4454-
elif not self.found_incomplete_ref(tag):
4448+
if not tvar and not self.found_incomplete_ref(tag):
44554449
sym = self.lookup_qualified(base.name, base)
44564450
if sym and isinstance(sym.node, TypeVarLikeExpr):
44574451
all_declared_tvar_names.append(sym.node.name) # Error will be reported later

‎mypy/typeanal.py‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,7 +1180,12 @@ def visit_unpack_type(self, t: UnpackType) -> Type:
11801180
self.fail(message_registry.INVALID_UNPACK_POSITION, t.type, code=codes.VALID_TYPE)
11811181
return AnyType(TypeOfAny.from_error)
11821182
self.allow_type_var_tuple = self.nesting_level + 1
1183-
result = UnpackType(self.anal_type(t.type), from_star_syntax=t.from_star_syntax)
1183+
result = UnpackType(
1184+
self.anal_type(t.type),
1185+
from_star_syntax=t.from_star_syntax,
1186+
line=t.line,
1187+
column=t.column,
1188+
)
11841189
self.allow_type_var_tuple = -1
11851190
return result
11861191

@@ -2078,7 +2083,7 @@ def check_unpacks_in_list(self, items: list[Type]) -> list[Type]:
20782083

20792084
if num_unpacks > 1:
20802085
assert final_unpack is not None
2081-
self.fail("More than one variadic Unpack in a type is not allowed", final_unpack.type)
2086+
self.fail("More than one variadic Unpack in a type is not allowed", final_unpack)
20822087
return new_items
20832088

20842089
def tuple_type(self, items: list[Type], line: int, column: int) -> TupleType:

‎test-data/unit/check-python312.test‎

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2288,11 +2288,11 @@ from typing_extensions import TypeVarTuple, Unpack
22882288

22892289
Us = TypeVarTuple("Us")
22902290

2291-
class C[*Ts, *Us]: # E: Can only use one type var tuple in a class def
2291+
class C[*Ts, *Us]: # E: Can only use one TypeVarTuple in a class definition
22922292
pass
22932293

22942294
class D[*Ts](Generic[Unpack[Us]]): # E: Generic[...] base class is redundant \
2295-
# E: Can only use one type var tuple in a class def
2295+
# E: Can only use one TypeVarTuple in a class definition
22962296
pass
22972297
[builtins fixtures/tuple.pyi]
22982298

@@ -2438,3 +2438,28 @@ def test[*Us](*xs: *Us) -> None:
24382438
# TODO: we should erase last_known_values from Parameters.
24392439
reveal_type(print_args(2, *xs, 4)) # N: Revealed type is "__main__.C[[Literal[2]?, *args: *tuple[*Us, Literal[4]?]], tuple[Literal[2]?, *Us, Literal[4]?]]"
24402440
[builtins fixtures/paramspec.pyi]
2441+
2442+
[case testDoubleTypeVarTupleNoCrashNewSyntax]
2443+
class C[*T, *U](T, U): # E: Can only use one TypeVarTuple in a class definition \
2444+
# E: TypeVarTuple "T" is only valid with an unpack \
2445+
# E: TypeVarTuple "U" is unbound
2446+
x: tuple[*T]
2447+
y: tuple[*T, *U] # E: More than one variadic Unpack in a type is not allowed \
2448+
# E: TypeVarTuple "U" is unbound
2449+
z: tuple[*U] # E: TypeVarTuple "U" is unbound
2450+
2451+
type A[*T, *U] = tuple[tuple[*T], tuple[*T, *U], tuple[*U]] # E: Can only use one TypeVarTuple in a type alias definition \
2452+
# E: More than one variadic Unpack in a type is not allowed \
2453+
# E: TypeVarTuple "U" is unbound
2454+
2455+
c: C[int, str]
2456+
a: A[int, str]
2457+
2458+
reveal_type(c.x) # N: Revealed type is "tuple[builtins.int, builtins.str]"
2459+
reveal_type(c.y) # N: Revealed type is "tuple[builtins.int, builtins.str]"
2460+
reveal_type(c.z) # N: Revealed type is "builtins.tuple[Any, ...]"
2461+
2462+
reveal_type(a[0]) # N: Revealed type is "tuple[builtins.int, builtins.str]"
2463+
reveal_type(a[1]) # N: Revealed type is "tuple[builtins.int, builtins.str]"
2464+
reveal_type(a[2]) # N: Revealed type is "builtins.tuple[Any, ...]"
2465+
[builtins fixtures/tuple.pyi]

‎test-data/unit/check-type-aliases.test‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1186,7 +1186,7 @@ partially_generic1: Ta3[int] = {"a": 1}
11861186
reveal_type(partially_generic1) # N: Revealed type is "builtins.dict[Any, builtins.int]"
11871187
partially_generic2: Ta3[int] = {1: "a"} # E: Dict entry 0 has incompatible type "int": "str"; expected "Any": "int"
11881188

1189-
Ta4 = TypeAliasType("Ta4", Tuple[Unpack[Ts]], type_params=(Ts, Ts1)) # E: Can only use one TypeVarTuple in type_params argument to TypeAliasType
1189+
Ta4 = TypeAliasType("Ta4", Tuple[Unpack[Ts]], type_params=(Ts, Ts1)) # E: Can only use one TypeVarTuple in a type alias definition
11901190

11911191
Ta5 = TypeAliasType("Ta5", Dict) # Unlike old style aliases, this is not generic
11921192
non_generic_dict: Ta5[int, str] # E: Bad number of arguments for type alias, expected 0, given 2

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

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -725,15 +725,21 @@ Ts = TypeVarTuple("Ts")
725725
Us = TypeVarTuple("Us")
726726
class G(Generic[Unpack[Ts]]): ...
727727

728-
A = Tuple[Unpack[Ts], Unpack[Us]] # E: More than one variadic Unpack in a type is not allowed
728+
A = Tuple[Unpack[Ts], Unpack[Us]] # E: Can only use one TypeVarTuple in a type alias definition \
729+
# E: More than one variadic Unpack in a type is not allowed \
730+
# E: TypeVarTuple "Us" is unbound
729731
x: A[int, str]
730732
reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.str]"
731733

732-
B = Callable[[Unpack[Ts], Unpack[Us]], int] # E: More than one variadic Unpack in a type is not allowed
734+
B = Callable[[Unpack[Ts], Unpack[Us]], int] # E: Can only use one TypeVarTuple in a type alias definition \
735+
# E: More than one variadic Unpack in a type is not allowed \
736+
# E: TypeVarTuple "Us" is unbound
733737
y: B[int, str]
734738
reveal_type(y) # N: Revealed type is "def (builtins.int, builtins.str) -> builtins.int"
735739

736-
C = G[Unpack[Ts], Unpack[Us]] # E: More than one variadic Unpack in a type is not allowed
740+
C = G[Unpack[Ts], Unpack[Us]] # E: Can only use one TypeVarTuple in a type alias definition \
741+
# E: More than one variadic Unpack in a type is not allowed \
742+
# E: TypeVarTuple "Us" is unbound
737743
z: C[int, str]
738744
reveal_type(z) # N: Revealed type is "__main__.G[builtins.int, builtins.str]"
739745
[builtins fixtures/tuple.pyi]
@@ -3273,3 +3279,33 @@ reveal_type(x) # N: Revealed type is "__main__.C[builtins.int, builtins.int]"
32733279
y: Union[Any, C[Unpack[tuple[int, ...]]]]
32743280
reveal_type(bar(y)) # N: Revealed type is "builtins.tuple[Any, ...]"
32753281
[builtins fixtures/tuple.pyi]
3282+
3283+
[case testDoubleTypeVarTupleNoCrashOldSyntax]
3284+
from typing import Generic, TypeVarTuple, Unpack
3285+
3286+
T = TypeVarTuple("T")
3287+
U = TypeVarTuple("U")
3288+
3289+
class C(T, U, Generic[Unpack[T], Unpack[U]]): # E: Can only use one TypeVarTuple in a class definition \
3290+
# E: TypeVarTuple "T" is only valid with an unpack \
3291+
# E: TypeVarTuple "U" is unbound
3292+
x: tuple[Unpack[T]]
3293+
y: tuple[Unpack[T], Unpack[U]] # E: More than one variadic Unpack in a type is not allowed \
3294+
# E: TypeVarTuple "U" is unbound
3295+
z: tuple[Unpack[U]] # E: TypeVarTuple "U" is unbound
3296+
3297+
A = tuple[tuple[Unpack[T]], tuple[Unpack[T], Unpack[U]], tuple[Unpack[U]]] # E: Can only use one TypeVarTuple in a type alias definition \
3298+
# E: More than one variadic Unpack in a type is not allowed \
3299+
# E: TypeVarTuple "U" is unbound
3300+
3301+
c: C[int, str]
3302+
a: A[int, str]
3303+
3304+
reveal_type(c.x) # N: Revealed type is "tuple[builtins.int, builtins.str]"
3305+
reveal_type(c.y) # N: Revealed type is "tuple[builtins.int, builtins.str]"
3306+
reveal_type(c.z) # N: Revealed type is "builtins.tuple[Any, ...]"
3307+
3308+
reveal_type(a[0]) # N: Revealed type is "tuple[builtins.int, builtins.str]"
3309+
reveal_type(a[1]) # N: Revealed type is "tuple[builtins.int, builtins.str]"
3310+
reveal_type(a[2]) # N: Revealed type is "builtins.tuple[Any, ...]"
3311+
[builtins fixtures/tuple.pyi]

‎test-data/unit/semanal-errors.test‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1502,7 +1502,7 @@ x: TVariadic # E: TypeVarTuple "TVariadic" is unbound
15021502
y: Unpack[TVariadic] # E: Unpack is only valid in a variadic position
15031503

15041504

1505-
class Variadic(Generic[Unpack[TVariadic], Unpack[TVariadic2]]): # E: Can only use one type var tuple in a class def
1505+
class Variadic(Generic[Unpack[TVariadic], Unpack[TVariadic2]]): # E: Can only use one TypeVarTuple in a class definition
15061506
pass
15071507

15081508
def bad_args(*args: TVariadic): # E: TypeVarTuple "TVariadic" is only valid with an unpack

0 commit comments

Comments
 (0)