Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading