8000 PEP 695: Add detection and error reporting for the use of incorrect e… · python/mypy@f2cee90 · GitHub
[go: up one dir, main page]

Skip to content

Commit f2cee90

Browse files
Eclips4pre-commit-ci[bot]sobolevn
authored
PEP 695: Add detection and error reporting for the use of incorrect expressions within the scope of a type parameter and a type alias (#17560)
This fixes part of #15238: > Add detection and error reporting for use of assignment expressions, yield, yield from, and await expressions within the type parameter scope; see [this section of PEP](https://peps.python.org/pep-0695/#type-parameter-scopes) for details --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: sobolevn <mail@sobolevn.me>
1 parent b202f30 commit f2cee90

File tree

3 files changed

+117
-0
lines changed

3 files changed

+117
-0
lines changed

mypy/fastparse.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,12 @@ def ast3_parse(
181181
if sys.version_info >= (3, 12):
182182
ast_TypeAlias = ast3.TypeAlias
183183
ast_ParamSpec = ast3.ParamSpec
184+
ast_TypeVar = ast3.TypeVar
184185
ast_TypeVarTuple = ast3.TypeVarTuple
185186
else:
186187
ast_TypeAlias = Any
187188
ast_ParamSpec = Any
189+
ast_TypeVar = Any
188190
ast_TypeVarTuple = Any
189191

190192
N = TypeVar("N", bound=Node)
@@ -345,6 +347,15 @@ def is_no_type_check_decorator(expr: ast3.expr) -> bool:
345347
return False
346348

347349

350+
def find_disallowed_expression_in_annotation_scope(expr: ast3.expr | None) -> ast3.expr | None:
351+
if expr is None:
352+
return None
353+
for node in ast3.walk(expr):
354+
if isinstance(node, (ast3.Yield, ast3.YieldFrom, ast3.NamedExpr, ast3.Await)):
355+
return node
356+
return None
357+
358+
348359
class ASTConverter:
349360
def __init__(
350361
self,
@@ -1180,6 +1191,29 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef:
11801191
self.class_and_function_stack.pop()
11811192
return cdef
11821193

1194+
def validate_type_param(self, type_param: ast_TypeVar) -> None:
1195+
incorrect_expr = find_disallowed_expression_in_annotation_scope(type_param.bound)
1196+
if incorrect_expr is None:
1197+
return
1198+
if isinstance(incorrect_expr, (ast3.Yield, ast3.YieldFrom)):
1199+
self.fail(
1200+
message_registry.TYPE_VAR_YIELD_EXPRESSION_IN_BOUND,
1201+
type_param.lineno,
1202+
type_param.col_offset,
1203+
)
1204+
if isinstance(incorrect_expr, ast3.NamedExpr):
1205+
self.fail(
1206+
message_registry.TYPE_VAR_NAMED_EXPRESSION_IN_BOUND,
1207+
type_param.lineno,
1208+
type_param.col_offset,
1209+
)
1210+
if isinstance(incorrect_expr, ast3.Await):
1211+
self.fail(
1212+
message_registry.TYPE_VAR_AWAIT_EXPRESSION_IN_BOUND,
1213+
type_param.lineno,
1214+
type_param.col_offset,
1215+
)
1216+
11831217
def translate_type_params(self, type_params: list[Any]) -> list[TypeParam]:
11841218
explicit_type_params = []
11851219
for p in type_params:
@@ -1202,6 +1236,7 @@ def translate_type_params(self, type_params: list[Any]) -> list[TypeParam]:
12021236
conv = TypeConverter(self.errors, line=p.lineno)
12031237
values = [conv.visit(t) for t in p.bound.elts]
12041238
elif p.bound is not None:
1239+
self.validate_type_param(p)
12051240
bound = TypeConverter(self.errors, line=p.lineno).visit(p.bound)
12061241
explicit_type_params.append(TypeParam(p.name, TYPE_VAR_KIND, bound, values))
12071242
return explicit_type_params
@@ -1791,11 +1826,23 @@ def visit_MatchOr(self, n: MatchOr) -> OrPattern:
17911826
node = OrPattern([self.visit(pattern) for pattern in n.patterns])
17921827
return self.set_line(node, n)
17931828

1829+
def validate_type_alias(self, n: ast_TypeAlias) -> None:
1830+
incorrect_expr = find_disallowed_expression_in_annotation_scope(n.value)
1831+
if incorrect_expr is None:
1832+
return
1833+
if isinstance(incorrect_expr, (ast3.Yield, ast3.YieldFrom)):
1834+
self.fail(message_registry.TYPE_ALIAS_WITH_YIELD_EXPRESSION, n.lineno, n.col_offset)
1835+
if isinstance(incorrect_expr, ast3.NamedExpr):
1836+
self.fail(message_registry.TYPE_ALIAS_WITH_NAMED_EXPRESSION, n.lineno, n.col_offset)
1837+
if isinstance(incorrect_expr, ast3.Await):
1838+
self.fail(message_registry.TYPE_ALIAS_WITH_AWAIT_EXPRESSION, n.lineno, n.col_offset)
1839+
17941840
# TypeAlias(identifier name, type_param* type_params, expr value)
17951841
def visit_TypeAlias(self, n: ast_TypeAlias) -> TypeAliasStmt | AssignmentStmt:
17961842
node: TypeAliasStmt | AssignmentStmt
17971843
if NEW_GENERIC_SYNTAX in self.options.enable_incomplete_feature:
17981844
type_params = self.translate_type_params(n.type_params)
1845+
self.validate_type_alias(n)
17991846
value = self.visit(n.value)
18001847
# Since the value is evaluated lazily, wrap the value inside a lambda.
18011848
# This helps mypyc.

mypy/message_registry.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,27 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
338338
TYPE_VAR_TOO_FEW_CONSTRAINED_TYPES: Final = ErrorMessage(
339339
"Type variable must have at least two constrained types", codes.MISC
340340
)
341+
342+
TYPE_VAR_YIELD_EXPRESSION_IN_BOUND: Final = ErrorMessage(
343+
"Yield expression cannot be used as a type variable bound", codes.SYNTAX
344+
)
345+
346+
TYPE_VAR_NAMED_EXPRESSION_IN_BOUND: Final = ErrorMessage(
347+
"Named expression cannot be used as a type variable bound", codes.SYNTAX
348+
)
349+
350+
TYPE_VAR_AWAIT_EXPRESSION_IN_BOUND: Final = ErrorMessage(
351+
"Await expression cannot be used as a type variable bound", codes.SYNTAX
352+
)
353+
354+
TYPE_ALIAS_WITH_YIELD_EXPRESSION: Final = ErrorMessage(
355+
"Yie F438 ld expression cannot be used within a type alias", codes.SYNTAX
356+
)
357+
358+
TYPE_ALIAS_WITH_NAMED_EXPRESSION: Final = ErrorMessage(
359+
"Named expression cannot be used within a type alias", codes.SYNTAX
360+
)
361+
362+
TYPE_ALIAS_WITH_AWAIT_EXPRESSION: Final = ErrorMessage(
363+
"Await expression cannot be used within a type alias", codes.SYNTAX
364+
)

test-data/unit/check-python312.test

Lines changed: 46 additions & 0 deletions
EF00
Original file line numberDiff line numberDiff line change
@@ -1667,3 +1667,49 @@ if x["other"] is not None:
16671667
type Y[T] = {"item": T, **Y[T]} # E: Overwriting TypedDict field "item" while merging
16681668
[builtins fixtures/dict.pyi]
16691669
[typing fixtures/typing-full.pyi]
1670+
1671+
[case testPEP695UsingIncorrectExpressionsInTypeVariableBound]
1672+
# flags: --enable-incomplete-feature=NewGenericSyntax
1673+
1674+
type X[T: (yield 1)] = Any # E: Yield expression cannot be used as a type variable bound
1675+
type Y[T: (yield from [])] = Any # E: Yield expression cannot be used as a type variable bound
1676+
type Z[T: (a := 1)] = Any # E: Named expression cannot be used as a type variable bound
1677+
type K[T: (await 1)] = Any # E: Await expression cannot be used as a type variable bound
1678+
1679+
type XNested[T: (1 + (yield 1))] = Any # E: Yield expression cannot be used as a type variable bound
1680+
type YNested[T: (1 + (yield from []))] = Any # E: Yield expression cannot be used as a type variable bound
1681+
type ZNested[T: (1 + (a := 1))] = Any # E: Named expression cannot be used as a type variable bound
1682+
type KNested[T: (1 + (await 1))] = Any # E: Await expression cannot be used as a type variable bound
1683+
1684+
class FooX[T: (yield 1)]: pass # E: Yield expression cannot be used as a type variable bound
1685+
class FooY[T: (yield from [])]: pass # E: Yield expression cannot be used as a type variable bound
1686+
class FooZ[T: (a := 1)]: pass # E: Named expression cannot be used as a type variable bound
1687+
class FooK[T: (await 1)]: pass # E: Await expression cannot be used as a type variable bound
1688+
1689+
class FooXNested[T: (1 + (yield 1))]: pass # E: Yield expression cannot be used as a type variable bound
1690+
class FooYNested[T: (1 + (yield from []))]: pass # E: Yield expression cannot be used as a type variable bound
1691+
class FooZNested[T: (1 + (a := 1))]: pass # E: Named expression cannot be used as a type variable bound
1692+
class FooKNested[T: (1 + (await 1))]: pass # E: Await expression cannot be used as a type variable bound
1693+
1694+
def foox[T: (yield 1)](): pass # E: Yield expression cannot be used as a type variable bound
1695+
def fooy[T: (yield from [])](): pass # E: Yield expression cannot be used as a type variable bound
1696+
def fooz[T: (a := 1)](): pass # E: Named expression cannot be used as a type variable bound
1697+
def fook[T: (await 1)](): pass # E: Await expression cannot be used as a type variable bound
1698+
1699+
def foox_nested[T: (1 + (yield 1))](): pass # E: Yield expression cannot be used as a type variable bound
1700+
def fooy_nested[T: (1 + (yield from []))](): pass # E: Yield expression cannot be used as a type variable bound
1701+
def fooz_nested[T: (1 + (a := 1))](): pass # E: Named expression cannot be used as a type variable bound
1702+
def fook_nested[T: (1 +(await 1))](): pass # E: Await expression cannot be used as a type variable bound
1703+
1704+
[case testPEP695UsingIncorrectExpressionsInTypeAlias]
1705+
# flags: --enable-incomplete-feature=NewGenericSyntax
1706+
1707+
type X = (yield 1) # E: Yield expression cannot be used within a type alias
1708+
type Y = (yield from []) # E: Yield expression cannot be used within a type alias
1709+
type Z = (a := 1) # E: Named expression cannot be used within a type alias
1710+
type K = (await 1) # E: Await expression cannot be used within a type alias
1711+
1712+
type XNested = (1 + (yield 1)) # E: Yield expression cannot be used within a type alias
1713+
type YNested = (1 + (yield from [])) # E: Yield expression cannot be used within a type alias
1714+
type ZNested = (1 + (a := 1)) # E: Named expression cannot be used within a type alias
1715+
type KNested = (1 + (await 1)) # E: Await expression cannot be used within a type alias

0 commit comments

Comments
 (0)
0