Skip to content

Commit

Permalink
PEP 695: Add detection and error reporting for the use of incorrect e…
Browse files Browse the repository at this point in the history
…xpressions 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 <[email protected]>
  • Loading branch information
3 people committed Jul 23, 2024
1 parent b202f30 commit f2cee90
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 0 deletions.
47 changes: 47 additions & 0 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,12 @@ def ast3_parse(
if sys.version_info >= (3, 12):
ast_TypeAlias = ast3.TypeAlias
ast_ParamSpec = ast3.ParamSpec
ast_TypeVar = ast3.TypeVar
ast_TypeVarTuple = ast3.TypeVarTuple
else:
ast_TypeAlias = Any
ast_ParamSpec = Any
ast_TypeVar = Any
ast_TypeVarTuple = Any

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


def find_disallowed_expression_in_annotation_scope(expr: ast3.expr | None) -> ast3.expr | None:
if expr is None:
return None
for node in ast3.walk(expr):
if isinstance(node, (ast3.Yield, ast3.YieldFrom, ast3.NamedExpr, ast3.Await)):
return node
return None


class ASTConverter:
def __init__(
self,
Expand Down Expand Up @@ -1180,6 +1191,29 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef:
self.class_and_function_stack.pop()
return cdef

def validate_type_param(self, type_param: ast_TypeVar) -> None:
incorrect_expr = find_disallowed_expression_in_annotation_scope(type_param.bound)
if incorrect_expr is None:
return
if isinstance(incorrect_expr, (ast3.Yield, ast3.YieldFrom)):
self.fail(
message_registry.TYPE_VAR_YIELD_EXPRESSION_IN_BOUND,
type_param.lineno,
type_param.col_offset,
)
if isinstance(incorrect_expr, ast3.NamedExpr):
self.fail(
message_registry.TYPE_VAR_NAMED_EXPRESSION_IN_BOUND,
type_param.lineno,
type_param.col_offset,
)
if isinstance(incorrect_expr, ast3.Await):
self.fail(
message_registry.TYPE_VAR_AWAIT_EXPRESSION_IN_BOUND,
type_param.lineno,
type_param.col_offset,
)

def translate_type_params(self, type_params: list[Any]) -> list[TypeParam]:
explicit_type_params = []
for p in type_params:
Expand All @@ -1202,6 +1236,7 @@ def translate_type_params(self, type_params: list[Any]) -> list[TypeParam]:
conv = TypeConverter(self.errors, line=p.lineno)
values = [conv.visit(t) for t in p.bound.elts]
elif p.bound is not None:
self.validate_type_param(p)
bound = TypeConverter(self.errors, line=p.lineno).visit(p.bound)
explicit_type_params.append(TypeParam(p.name, TYPE_VAR_KIND, bound, values))
return explicit_type_params
Expand Down Expand Up @@ -1791,11 +1826,23 @@ def visit_MatchOr(self, n: MatchOr) -> OrPattern:
node = OrPattern([self.visit(pattern) for pattern in n.patterns])
return self.set_line(node, n)

def validate_type_alias(self, n: ast_TypeAlias) -> None:
incorrect_expr = find_disallowed_expression_in_annotation_scope(n.value)
if incorrect_expr is None:
return
if isinstance(incorrect_expr, (ast3.Yield, ast3.YieldFrom)):
self.fail(message_registry.TYPE_ALIAS_WITH_YIELD_EXPRESSION, n.lineno, n.col_offset)
if isinstance(incorrect_expr, ast3.NamedExpr):
self.fail(message_registry.TYPE_ALIAS_WITH_NAMED_EXPRESSION, n.lineno, n.col_offset)
if isinstance(incorrect_expr, ast3.Await):
self.fail(message_registry.TYPE_ALIAS_WITH_AWAIT_EXPRESSION, n.lineno, n.col_offset)

# TypeAlias(identifier name, type_param* type_params, expr value)
def visit_TypeAlias(self, n: ast_TypeAlias) -> TypeAliasStmt | AssignmentStmt:
node: TypeAliasStmt | AssignmentStmt
if NEW_GENERIC_SYNTAX in self.options.enable_incomplete_feature:
type_params = self.translate_type_params(n.type_params)
self.validate_type_alias(n)
value = self.visit(n.value)
# Since the value is evaluated lazily, wrap the value inside a lambda.
# This helps mypyc.
Expand Down
24 changes: 24 additions & 0 deletions mypy/message_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,3 +338,27 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
TYPE_VAR_TOO_FEW_CONSTRAINED_TYPES: Final = ErrorMessage(
"Type variable must have at least two constrained types", codes.MISC
)

TYPE_VAR_YIELD_EXPRESSION_IN_BOUND: Final = ErrorMessage(
"Yield expression cannot be used as a type variable bound", codes.SYNTAX
)

TYPE_VAR_NAMED_EXPRESSION_IN_BOUND: Final = ErrorMessage(
"Named expression cannot be used as a type variable bound", codes.SYNTAX
)

TYPE_VAR_AWAIT_EXPRESSION_IN_BOUND: Final = ErrorMessage(
"Await expression cannot be used as a type variable bound", codes.SYNTAX
)

TYPE_ALIAS_WITH_YIELD_EXPRESSION: Final = ErrorMessage(
"Yield expression cannot be used within a type alias", codes.SYNTAX
)

TYPE_ALIAS_WITH_NAMED_EXPRESSION: Final = ErrorMessage(
"Named expression cannot be used within a type alias", codes.SYNTAX
)

TYPE_ALIAS_WITH_AWAIT_EXPRESSION: Final = ErrorMessage(
"Await expression cannot be used within a type alias", codes.SYNTAX
)
46 changes: 46 additions & 0 deletions test-data/unit/check-python312.test
Original file line number Diff line number Diff line change
Expand Up @@ -1667,3 +1667,49 @@ if x["other"] is not None:
type Y[T] = {"item": T, **Y[T]} # E: Overwriting TypedDict field "item" while merging
[builtins fixtures/dict.pyi]
[typing fixtures/typing-full.pyi]

[case testPEP695UsingIncorrectExpressionsInTypeVariableBound]
# flags: --enable-incomplete-feature=NewGenericSyntax

type X[T: (yield 1)] = Any # E: Yield expression cannot be used as a type variable bound
type Y[T: (yield from [])] = Any # E: Yield expression cannot be used as a type variable bound
type Z[T: (a := 1)] = Any # E: Named expression cannot be used as a type variable bound
type K[T: (await 1)] = Any # E: Await expression cannot be used as a type variable bound

type XNested[T: (1 + (yield 1))] = Any # E: Yield expression cannot be used as a type variable bound
type YNested[T: (1 + (yield from []))] = Any # E: Yield expression cannot be used as a type variable bound
type ZNested[T: (1 + (a := 1))] = Any # E: Named expression cannot be used as a type variable bound
type KNested[T: (1 + (await 1))] = Any # E: Await expression cannot be used as a type variable bound

class FooX[T: (yield 1)]: pass # E: Yield expression cannot be used as a type variable bound
class FooY[T: (yield from [])]: pass # E: Yield expression cannot be used as a type variable bound
class FooZ[T: (a := 1)]: pass # E: Named expression cannot be used as a type variable bound
class FooK[T: (await 1)]: pass # E: Await expression cannot be used as a type variable bound

class FooXNested[T: (1 + (yield 1))]: pass # E: Yield expression cannot be used as a type variable bound
class FooYNested[T: (1 + (yield from []))]: pass # E: Yield expression cannot be used as a type variable bound
class FooZNested[T: (1 + (a := 1))]: pass # E: Named expression cannot be used as a type variable bound
class FooKNested[T: (1 + (await 1))]: pass # E: Await expression cannot be used as a type variable bound

def foox[T: (yield 1)](): pass # E: Yield expression cannot be used as a type variable bound
def fooy[T: (yield from [])](): pass # E: Yield expression cannot be used as a type variable bound
def fooz[T: (a := 1)](): pass # E: Named expression cannot be used as a type variable bound
def fook[T: (await 1)](): pass # E: Await expression cannot be used as a type variable bound

def foox_nested[T: (1 + (yield 1))](): pass # E: Yield expression cannot be used as a type variable bound
def fooy_nested[T: (1 + (yield from []))](): pass # E: Yield expression cannot be used as a type variable bound
def fooz_nested[T: (1 + (a := 1))](): pass # E: Named expression cannot be used as a type variable bound
def fook_nested[T: (1 +(await 1))](): pass # E: Await expression cannot be used as a type variable bound

[case testPEP695UsingIncorrectExpressionsInTypeAlias]
# flags: --enable-incomplete-feature=NewGenericSyntax

type X = (yield 1) # E: Yield expression cannot be used within a type alias
type Y = (yield from []) # E: Yield expression cannot be used within a type alias
type Z = (a := 1) # E: Named expression cannot be used within a type alias
type K = (await 1) # E: Await expression cannot be used within a type alias

type XNested = (1 + (yield 1)) # E: Yield expression cannot be used within a type alias
type YNested = (1 + (yield from [])) # E: Yield expression cannot be used within a type alias
type ZNested = (1 + (a := 1)) # E: Named expression cannot be used within a type alias
type KNested = (1 + (await 1)) # E: Await expression cannot be used within a type alias

0 comments on commit f2cee90

Please sign in to comment.