Skip to content

Commit 93dac05

Browse files
authored
[PEP 695] Fix a few issues and add tests (#17318)
Fix badly formed types that could be created when using aliases like `type A = list`. Improve some error messages when using PEP 695 syntax. Add a few PEP 695 tests. Work on #15238.
1 parent aa4410f commit 93dac05

File tree

5 files changed

+105
-8
lines changed

5 files changed

+105
-8
lines changed

mypy/message_registry.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
194194
"A function returning TypeVar should receive at least "
195195
"one argument containing the same TypeVar"
196196
)
197+
TYPE_PARAMETERS_SHOULD_BE_DECLARED: Final = (
198+
"All type parameters should be declared ({} not declared)"
199+
)
197200

198201
# Super
199202
TOO_MANY_ARGS_FOR_SUPER: Final = ErrorMessage('Too many arguments for "super"')

mypy/messages.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2423,7 +2423,11 @@ def annotation_in_unchecked_function(self, context: Context) -> None:
24232423

24242424
def type_parameters_should_be_declared(self, undeclared: list[str], context: Context) -> None:
24252425
names = ", ".join('"' + n + '"' for n in undeclared)
2426-
self.fail(f"All type parameters should be declared ({names} not declared)", context)
2426+
self.fail(
2427+
message_registry.TYPE_PARAMETERS_SHOULD_BE_DECLARED.format(names),
2428+
context,
2429+
code=codes.VALID_TYPE,
2430+
)
24272431

24282432

24292433
def quote_type_string(type_string: str) -> str:

mypy/semanal.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3694,6 +3694,7 @@ def analyze_alias(
36943694
allow_placeholder: bool = False,
36953695
declared_type_vars: TypeVarLikeList | None = None,
36963696
all_declared_type_params_names: list[str] | None = None,
3697+
python_3_12_type_alias: bool = False,
36973698
) -> tuple[Type | None, list[TypeVarLikeType], set[str], list[str], bool]:
36983699
"""Check if 'rvalue' is a valid type allowed for aliasing (e.g. not a type variable).
36993700
@@ -3747,6 +3748,7 @@ def analyze_alias(
37473748
global_scope=global_scope,
37483749
allowed_alias_tvars=tvar_defs,
37493750
alias_type_params_names=all_declared_type_params_names,
3751+
python_3_12_type_alias=python_3_12_type_alias,
37503752
)
37513753

37523754
# There can be only one variadic variable at most, the error is reported elsewhere.
@@ -5321,6 +5323,7 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None:
53215323
allow_placeholder=True,
53225324
declared_type_vars=type_params,
53235325
all_declared_type_params_names=all_type_params_names,
5326+
python_3_12_type_alias=True,
53245327
)
53255328
if not res:
53265329
res = AnyType(TypeOfAny.from_error)
@@ -5355,6 +5358,8 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None:
53555358
# so we need to replace it with non-explicit Anys.
53565359
res = make_any_non_explicit(res)
53575360
eager = self.is_func_scope()
5361+
if isinstance(res, ProperType) and isinstance(res, Instance) and not res.args:
5362+
fix_instance(res, self.fail, self.note, disallow_any=False, options=self.options)
53585363
alias_node = TypeAlias(
53595364
res,
53605365
self.qualified_name(s.name.name),

mypy/typeanal.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ def analyze_type_alias(
142142
global_scope: bool = True,
143143
allowed_alias_tvars: list[TypeVarLikeType] | None = None,
144144
alias_type_params_names: list[str] | None = None,
145+
python_3_12_type_alias: bool = False,
145146
) -> tuple[Type, set[str]]:
146147
"""Analyze r.h.s. of a (potential) type alias definition.
147148
@@ -160,6 +161,7 @@ def analyze_type_alias(
160161
prohibit_self_type="type alias target",
161162
allowed_alias_tvars=allowed_alias_tvars,
162163
alias_type_params_names=alias_type_params_names,
164+
python_3_12_type_alias=python_3_12_type_alias,
163165
)
164166
analyzer.in_dynamic_func = in_dynamic_func
165167
analyzer.global_scope = global_scope
@@ -202,6 +204,7 @@ def __init__(
202204
is_typeshed_stub: bool,
203205
*,
204206
defining_alias: bool = False,
207+
python_3_12_type_alias: bool = False,
205208
allow_tuple_literal: bool = False,
206209
allow_unbound_tvars: bool = False,
207210
allow_placeholder: bool = False,
@@ -220,6 +223,7 @@ def __init__(
220223
self.tvar_scope = tvar_scope
221224
# Are we analysing a type alias definition rvalue?
222225
self.defining_alias = defining_alias
226+
self.python_3_12_type_alias = python_3_12_type_alias
223227
self.allow_tuple_literal = allow_tuple_literal
224228
# Positive if we are analyzing arguments of another (outer) type
225229
self.nesting_level = 0
@@ -364,7 +368,12 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
364368
and (tvar_def is None or tvar_def not in self.allowed_alias_tvars)
365369
):
366370
if self.not_declared_in_type_params(t.name):
367-
msg = f'Type variable "{t.name}" is not included in type_params'
371+
if self.python_3_12_type_alias:
372+
msg = message_registry.TYPE_PARAMETERS_SHOULD_BE_DECLARED.format(
373+
f'"{t.name}"'
374+
)
375+
else:
376+
msg = f'Type variable "{t.name}" is not included in type_params'
368377
else:
369378
msg = f'Can\'t use bound type variable "{t.name}" to define generic alias'
370379
self.fail(msg, t, code=codes.VALID_TYPE)
@@ -393,7 +402,12 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
393402
if self.allow_unbound_tvars:
394403
return t
395404
if self.defining_alias and self.not_declared_in_type_params(t.name):
396-
msg = f'TypeVarTuple "{t.name}" is not included in type_params'
405+
if self.python_3_12_type_alias:
406+
msg = message_registry.TYPE_PARAMETERS_SHOULD_BE_DECLARED.format(
407+
f'"{t.name}"'
408+
)
409+
else:
410+
msg = f'TypeVarTuple "{t.name}" is not included in type_params'
397411
else:
398412
msg = f'TypeVarTuple "{t.name}" is unbound'
399413
self.fail(msg, t, code=codes.VALID_TYPE)
@@ -1309,11 +1323,13 @@ def analyze_callable_args_for_paramspec(
13091323
and self.not_declared_in_type_params(tvar_def.name)
13101324
and tvar_def not in self.allowed_alias_tvars
13111325
):
1312-
self.fail(
1313-
f'ParamSpec "{tvar_def.name}" is not included in type_params',
1314-
callable_args,
1315-
code=codes.VALID_TYPE,
1316-
)
1326+
if self.python_3_12_type_alias:
1327+
msg = message_registry.TYPE_PARAMETERS_SHOULD_BE_DECLARED.format(
1328+
f'"{tvar_def.name}"'
1329+
)
1330+
else:
1331+
msg = f'ParamSpec "{tvar_def.name}" is not included in type_params'
1332+
self.fail(msg, callable_args, code=codes.VALID_TYPE)
13171333
return callable_with_ellipsis(
13181334
AnyType(TypeOfAny.special_form), ret_type=ret_type, fallback=fallback
13191335
)

test-data/unit/check-python312.test

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,3 +1244,72 @@ class C[T]:
12441244

12451245
class D[T](C[S]): # E: All type parameters should be declared ("S" not declared)
12461246
pass
1247+
1248+
[case testPEP695MixNewAndOldStyleTypeVarTupleAndParamSpec]
1249+
# mypy: enable-incomplete-feature=NewGenericSyntax
1250+
from typing import TypeVarTuple, ParamSpec, Callable
1251+
Ts = TypeVarTuple("Ts")
1252+
P = ParamSpec("P")
1253+
1254+
def f[T](x: T, f: Callable[P, None] # E: All type parameters should be declared ("P" not declared)
1255+
) -> Callable[P, T]: ...
1256+
def g[T](x: T, f: tuple[*Ts] # E: All type parameters should be declared ("Ts" not declared)
1257+
) -> tuple[T, *Ts]: ...
1258+
[builtins fixtures/tuple.pyi]
1259+
1260+
[case testPEP695MixNewAndOldStyleGenericsInTypeAlias]
1261+
# mypy: enable-incomplete-feature=NewGenericSyntax
1262+
from typing import TypeVar, ParamSpec, TypeVarTuple, Callable
1263+
1264+
T = TypeVar("T")
1265+
Ts = TypeVarTuple("Ts")
1266+
P = ParamSpec("P")
1267+
1268+
type A = list[T] # E: All type parameters should be declared ("T" not declared)
1269+
a: A[int] # E: Bad number of arguments for type alias, expected 0, given 1
1270+
reveal_type(a) # N: Revealed type is "builtins.list[Any]"
1271+
1272+
type B = tuple[*Ts] # E: All type parameters should be declared ("Ts" not declared)
1273+
type C = Callable[P, None] # E: All type parameters should be declared ("P" not declared)
1274+
[builtins fixtures/tuple.pyi]
1275+
1276+
[case testPEP695NonGenericAliasToGenericClass]
1277+
# mypy: enable-incomplete-feature=NewGenericSyntax
1278+
class C[T]: pass
1279+
type A = C
1280+
x: C
1281+
y: A
1282+
reveal_type(x) # N: Revealed type is "__main__.C[Any]"
1283+
reveal_type(y) # N: Revealed type is "__main__.C[Any]"
1284+
z: A[int] # E: Bad number of arguments for type alias, expected 0, given 1
1285+
1286+
[case testPEP695SelfType]
1287+
# mypy: enable-incomplete-feature=NewGenericSyntax
1288+
from typing import Self
1289+
1290+
class C:
1291+
@classmethod
1292+
def m[T](cls, x: T) -> tuple[Self, T]:
1293+
return cls(), x
1294+
1295+
class D(C):
1296+
pass
1297+
1298+
reveal_type(C.m(1)) # N: Revealed type is "Tuple[__main__.C, builtins.int]"
1299+
reveal_type(D.m(1)) # N: Revealed type is "Tuple[__main__.D, builtins.int]"
1300+
1301+
class E[T]:
1302+
def m(self) -> Self:
1303+
return self
1304+
1305+
def mm[S](self, x: S) -> tuple[Self, S]:
1306+
return self, x
1307+
1308+
class F[T](E[T]):
1309+
pass
1310+
1311+
reveal_type(E[int]().m()) # N: Revealed type is "__main__.E[builtins.int]"
1312+
reveal_type(E[int]().mm(b'x')) # N: Revealed type is "Tuple[__main__.E[builtins.int], builtins.bytes]"
1313+
reveal_type(F[str]().m()) # N: Revealed type is "__main__.F[builtins.str]"
1314+
reveal_type(F[str]().mm(b'x')) # N: Revealed type is "Tuple[__main__.F[builtins.str], builtins.bytes]"
1315+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)