Add box to stmt

This commit is contained in:
Charlie Marsh
2025-12-11 11:24:31 -05:00
parent c9155d5e72
commit cc850ec348
123 changed files with 2523 additions and 2786 deletions

View File

@@ -114,13 +114,13 @@ impl<'src> Parser<'src> {
let start = self.node_start();
match self.current_token_kind() {
TokenKind::If => Stmt::If(self.parse_if_statement()),
TokenKind::For => Stmt::For(self.parse_for_statement(start)),
TokenKind::While => Stmt::While(self.parse_while_statement()),
TokenKind::Def => Stmt::FunctionDef(self.parse_function_definition(vec![], start)),
TokenKind::Class => Stmt::ClassDef(self.parse_class_definition(vec![], start)),
TokenKind::Try => Stmt::Try(self.parse_try_statement()),
TokenKind::With => Stmt::With(self.parse_with_statement(start)),
TokenKind::If => self.parse_if_statement().into(),
TokenKind::For => self.parse_for_statement(start).into(),
TokenKind::While => self.parse_while_statement().into(),
TokenKind::Def => self.parse_function_definition(vec![], start).into(),
TokenKind::Class => self.parse_class_definition(vec![], start).into(),
TokenKind::Try => self.parse_try_statement().into(),
TokenKind::With => self.parse_with_statement(start).into(),
TokenKind::At => self.parse_decorators(),
TokenKind::Async => self.parse_async_statement(),
token => {
@@ -130,11 +130,11 @@ impl<'src> Parser<'src> {
match self.classify_match_token() {
MatchTokenKind::Keyword => {
return Stmt::Match(self.parse_match_statement());
return self.parse_match_statement().into();
}
MatchTokenKind::KeywordOrIdentifier => {
if let Some(match_stmt) = self.try_parse_match_statement() {
return Stmt::Match(match_stmt);
return match_stmt.into();
}
}
MatchTokenKind::Identifier => {}
@@ -262,19 +262,19 @@ impl<'src> Parser<'src> {
/// See: <https://docs.python.org/3/reference/simple_stmts.html>
fn parse_simple_statement(&mut self) -> Stmt {
match self.current_token_kind() {
TokenKind::Return => Stmt::Return(self.parse_return_statement()),
TokenKind::Import => Stmt::Import(self.parse_import_statement()),
TokenKind::From => Stmt::ImportFrom(self.parse_from_import_statement()),
TokenKind::Pass => Stmt::Pass(self.parse_pass_statement()),
TokenKind::Continue => Stmt::Continue(self.parse_continue_statement()),
TokenKind::Break => Stmt::Break(self.parse_break_statement()),
TokenKind::Raise => Stmt::Raise(self.parse_raise_statement()),
TokenKind::Del => Stmt::Delete(self.parse_delete_statement()),
TokenKind::Assert => Stmt::Assert(self.parse_assert_statement()),
TokenKind::Global => Stmt::Global(self.parse_global_statement()),
TokenKind::Nonlocal => Stmt::Nonlocal(self.parse_nonlocal_statement()),
TokenKind::Return => self.parse_return_statement().into(),
TokenKind::Import => self.parse_import_statement().into(),
TokenKind::From => self.parse_from_import_statement().into(),
TokenKind::Pass => self.parse_pass_statement().into(),
TokenKind::Continue => self.parse_continue_statement().into(),
TokenKind::Break => self.parse_break_statement().into(),
TokenKind::Raise => self.parse_raise_statement().into(),
TokenKind::Del => self.parse_delete_statement().into(),
TokenKind::Assert => self.parse_assert_statement().into(),
TokenKind::Global => self.parse_global_statement().into(),
TokenKind::Nonlocal => self.parse_nonlocal_statement().into(),
TokenKind::IpyEscapeCommand => {
Stmt::IpyEscapeCommand(self.parse_ipython_escape_command_statement())
self.parse_ipython_escape_command_statement().into()
}
token => {
if token == TokenKind::Type {
@@ -285,7 +285,7 @@ impl<'src> Parser<'src> {
if (first == TokenKind::Name || first.is_soft_keyword())
&& matches!(second, TokenKind::Lsqb | TokenKind::Equal)
{
return Stmt::TypeAlias(self.parse_type_alias_statement());
return self.parse_type_alias_statement().into();
}
}
@@ -296,19 +296,17 @@ impl<'src> Parser<'src> {
self.parse_expression_list(ExpressionContext::yield_or_starred_bitwise_or());
if self.at(TokenKind::Equal) {
Stmt::Assign(self.parse_assign_statement(parsed_expr, start))
self.parse_assign_statement(parsed_expr, start).into()
} else if self.at(TokenKind::Colon) {
Stmt::AnnAssign(self.parse_annotated_assignment_statement(parsed_expr, start))
self.parse_annotated_assignment_statement(parsed_expr, start).into()
} else if let Some(op) = self.current_token_kind().as_augmented_assign_operator() {
Stmt::AugAssign(self.parse_augmented_assignment_statement(
self.parse_augmented_assignment_statement(
parsed_expr,
op,
start,
))
).into()
} else if self.options.mode == Mode::Ipython && self.at(TokenKind::Question) {
Stmt::IpyEscapeCommand(
self.parse_ipython_help_end_escape_command_statement(&parsed_expr),
)
self.parse_ipython_help_end_escape_command_statement(&parsed_expr).into()
} else {
Stmt::Expr(ast::StmtExpr {
range: self.node_range(start),
@@ -2728,24 +2726,24 @@ impl<'src> Parser<'src> {
match self.current_token_kind() {
// test_ok async_function_definition
// async def foo(): ...
TokenKind::Def => Stmt::FunctionDef(ast::StmtFunctionDef {
TokenKind::Def => ast::StmtFunctionDef {
is_async: true,
..self.parse_function_definition(vec![], async_start)
}),
}.into(),
// test_ok async_with_statement
// async with item: ...
TokenKind::With => Stmt::With(ast::StmtWith {
TokenKind::With => ast::StmtWith {
is_async: true,
..self.parse_with_statement(async_start)
}),
}.into(),
// test_ok async_for_statement
// async for target in iter: ...
TokenKind::For => Stmt::For(ast::StmtFor {
TokenKind::For => ast::StmtFor {
is_async: true,
..self.parse_for_statement(async_start)
}),
}.into(),
kind => {
// test_err async_unexpected_token
@@ -2888,18 +2886,18 @@ impl<'src> Parser<'src> {
}
match self.current_token_kind() {
TokenKind::Def => Stmt::FunctionDef(self.parse_function_definition(decorators, start)),
TokenKind::Class => Stmt::ClassDef(self.parse_class_definition(decorators, start)),
TokenKind::Def => self.parse_function_definition(decorators, start).into(),
TokenKind::Class => self.parse_class_definition(decorators, start).into(),
TokenKind::Async if self.peek() == TokenKind::Def => {
self.bump(TokenKind::Async);
// test_ok decorator_async_function
// @decorator
// async def foo(): ...
Stmt::FunctionDef(ast::StmtFunctionDef {
ast::StmtFunctionDef {
is_async: true,
..self.parse_function_definition(decorators, start)
})
}.into()
}
_ => {
// test_err decorator_unexpected_token

View File

@@ -5,8 +5,7 @@
//! be called in a parent `Visitor`'s `visit_stmt` and `visit_expr` methods, respectively.
use ruff_python_ast::{
self as ast, Expr, ExprContext, IrrefutablePatternKind, Pattern, PythonVersion, Stmt, StmtExpr,
StmtFunctionDef, StmtImportFrom,
self as ast, Expr, ExprContext, IrrefutablePatternKind, Pattern, PythonVersion, Stmt,
comparable::ComparableExpr,
helpers,
visitor::{Visitor, walk_expr, walk_stmt},
@@ -59,13 +58,14 @@ impl SemanticSyntaxChecker {
fn check_stmt<Ctx: SemanticSyntaxContext>(&mut self, stmt: &ast::Stmt, ctx: &Ctx) {
match stmt {
Stmt::ImportFrom(StmtImportFrom {
range,
module,
level,
names,
..
}) => {
Stmt::ImportFrom(node) => {
let ast::StmtImportFrom {
range,
module,
level,
names,
node_index: _,
} = &**node;
if matches!(module.as_deref(), Some("__future__")) {
for name in names {
if !is_known_future_feature(&name.name) {
@@ -124,17 +124,19 @@ impl SemanticSyntaxChecker {
visitor.visit_pattern(&case.pattern);
}
}
Stmt::FunctionDef(ast::StmtFunctionDef {
type_params,
parameters,
..
}) => {
Stmt::FunctionDef(node) => {
let ast::StmtFunctionDef {
type_params,
parameters,
..
} = &**node;
if let Some(type_params) = type_params {
Self::duplicate_type_parameter_name(type_params, ctx);
}
Self::duplicate_parameter_name(parameters, ctx);
}
Stmt::Global(ast::StmtGlobal { names, .. }) => {
Stmt::Global(node) => {
let ast::StmtGlobal { names, .. } = &**node;
for name in names {
if ctx.is_bound_parameter(name) {
Self::add_error(
@@ -145,19 +147,18 @@ impl SemanticSyntaxChecker {
}
}
}
Stmt::ClassDef(ast::StmtClassDef {
type_params: Some(type_params),
..
})
| Stmt::TypeAlias(ast::StmtTypeAlias {
type_params: Some(type_params),
..
}) => {
Stmt::ClassDef(node) if node.type_params.is_some() => {
let type_params = node.type_params.as_ref().unwrap();
Self::duplicate_type_parameter_name(type_params, ctx);
Self::type_parameter_default_order(type_params, ctx);
}
Stmt::Assign(ast::StmtAssign { targets, value, .. }) => {
if let [Expr::Starred(ast::ExprStarred { range, .. })] = targets.as_slice() {
Stmt::TypeAlias(node) if node.type_params.is_some() => {
let type_params = node.type_params.as_ref().unwrap();
Self::duplicate_type_parameter_name(type_params, ctx);
Self::type_parameter_default_order(type_params, ctx);
}
Stmt::Assign(node) => {
if let [Expr::Starred(ast::ExprStarred { range, .. })] = node.targets.as_slice() {
// test_ok single_starred_assignment_target
// (*a,) = (1,)
// *a, = (1,)
@@ -183,7 +184,7 @@ impl SemanticSyntaxChecker {
// _ = *{42}
// _ = *list()
// _ = *(p + q)
Self::invalid_star_expression(value, ctx);
Self::invalid_star_expression(&node.value, ctx);
}
Stmt::Return(ast::StmtReturn {
value,
@@ -199,12 +200,13 @@ impl SemanticSyntaxChecker {
Self::add_error(ctx, SemanticSyntaxErrorKind::ReturnOutsideFunction, *range);
}
}
Stmt::For(ast::StmtFor {
target,
iter,
is_async,
..
}) => {
Stmt::For(node) => {
let ast::StmtFor {
target,
iter,
is_async,
..
} = &**node;
// test_err single_star_for
// for _ in *x: ...
// for *x in xs: ...
@@ -218,14 +220,15 @@ impl SemanticSyntaxChecker {
);
}
}
Stmt::With(ast::StmtWith { is_async: true, .. }) => {
Stmt::With(node) if node.is_async => {
Self::await_outside_async_function(
ctx,
stmt,
AwaitOutsideAsyncFunctionKind::AsyncWith,
);
}
Stmt::Nonlocal(ast::StmtNonlocal { names, range, .. }) => {
Stmt::Nonlocal(node) => {
let ast::StmtNonlocal { names, range, .. } = &**node;
// test_ok nonlocal_declaration_at_module_level
// def _():
// nonlocal x
@@ -272,7 +275,7 @@ impl SemanticSyntaxChecker {
fn check_annotation<Ctx: SemanticSyntaxContext>(stmt: &ast::Stmt, ctx: &Ctx) {
match stmt {
Stmt::AnnAssign(ast::StmtAnnAssign { annotation, .. }) => {
Stmt::AnnAssign(node) => {
if ctx.python_version() > PythonVersion::PY313 {
// test_ok valid_annotation_py313
// # parse_options: {"target-version": "3.13"}
@@ -295,15 +298,10 @@ impl SemanticSyntaxChecker {
position: InvalidExpressionPosition::TypeAnnotation,
ctx,
};
visitor.visit_expr(annotation);
visitor.visit_expr(&node.annotation);
}
}
Stmt::FunctionDef(ast::StmtFunctionDef {
type_params,
parameters,
returns,
..
}) => {
Stmt::FunctionDef(node) => {
// test_ok valid_annotation_function_py313
// # parse_options: {"target-version": "3.13"}
// def f() -> (y := 3): ...
@@ -355,32 +353,30 @@ impl SemanticSyntaxChecker {
position: InvalidExpressionPosition::TypeAnnotation,
ctx,
};
if let Some(type_params) = type_params {
if let Some(type_params) = &node.type_params {
visitor.visit_type_params(type_params);
}
// the __future__ annotation error takes precedence over the generic error
if ctx.future_annotations_or_stub() || ctx.python_version() > PythonVersion::PY313 {
visitor.position = InvalidExpressionPosition::TypeAnnotation;
} else if type_params.is_some() {
} else if node.type_params.is_some() {
visitor.position = InvalidExpressionPosition::GenericDefinition;
} else {
return;
}
for param in parameters
for param in node
.parameters
.iter()
.filter_map(ast::AnyParameterRef::annotation)
{
visitor.visit_expr(param);
}
if let Some(returns) = returns {
if let Some(returns) = &node.returns {
visitor.visit_expr(returns);
}
}
Stmt::ClassDef(ast::StmtClassDef {
type_params: Some(type_params),
arguments,
..
}) => {
Stmt::ClassDef(node) if node.type_params.is_some() => {
let type_params = node.type_params.as_ref().unwrap();
// test_ok valid_annotation_class
// class F(y := list): ...
// def f():
@@ -402,14 +398,12 @@ impl SemanticSyntaxChecker {
ctx,
};
visitor.visit_type_params(type_params);
if let Some(arguments) = arguments {
if let Some(arguments) = &node.arguments {
visitor.position = InvalidExpressionPosition::GenericDefinition;
visitor.visit_arguments(arguments);
}
}
Stmt::TypeAlias(ast::StmtTypeAlias {
type_params, value, ..
}) => {
Stmt::TypeAlias(node) => {
// test_err invalid_annotation_type_alias
// type X[T: (yield 1)] = int # TypeVar bound
// type X[T = (yield 1)] = int # TypeVar default
@@ -423,8 +417,8 @@ impl SemanticSyntaxChecker {
position: InvalidExpressionPosition::TypeAlias,
ctx,
};
visitor.visit_expr(value);
if let Some(type_params) = type_params {
visitor.visit_expr(&node.value);
if let Some(type_params) = &node.type_params {
visitor.visit_type_params(type_params);
}
}
@@ -485,43 +479,34 @@ impl SemanticSyntaxChecker {
/// Check for [`SemanticSyntaxErrorKind::WriteToDebug`] in `stmt`.
fn debug_shadowing<Ctx: SemanticSyntaxContext>(stmt: &ast::Stmt, ctx: &Ctx) {
match stmt {
Stmt::FunctionDef(ast::StmtFunctionDef {
name,
type_params,
parameters,
..
}) => {
Stmt::FunctionDef(node) => {
// test_err debug_shadow_function
// def __debug__(): ... # function name
// def f[__debug__](): ... # type parameter name
// def f(__debug__): ... # parameter name
Self::check_identifier(name, ctx);
if let Some(type_params) = type_params {
Self::check_identifier(&node.name, ctx);
if let Some(type_params) = &node.type_params {
for type_param in type_params.iter() {
Self::check_identifier(type_param.name(), ctx);
}
}
for parameter in parameters {
for parameter in &node.parameters {
Self::check_identifier(parameter.name(), ctx);
}
}
Stmt::ClassDef(ast::StmtClassDef {
name, type_params, ..
}) => {
Stmt::ClassDef(node) => {
// test_err debug_shadow_class
// class __debug__: ... # class name
// class C[__debug__]: ... # type parameter name
Self::check_identifier(name, ctx);
if let Some(type_params) = type_params {
Self::check_identifier(&node.name, ctx);
if let Some(type_params) = &node.type_params {
for type_param in type_params.iter() {
Self::check_identifier(type_param.name(), ctx);
}
}
}
Stmt::TypeAlias(ast::StmtTypeAlias {
type_params: Some(type_params),
..
}) => {
Stmt::TypeAlias(node) if node.type_params.is_some() => {
let type_params = node.type_params.as_ref().unwrap();
// test_err debug_shadow_type_alias
// type __debug__ = list[int] # visited as an Expr but still flagged
// type Debug[__debug__] = str
@@ -529,8 +514,7 @@ impl SemanticSyntaxChecker {
Self::check_identifier(type_param.name(), ctx);
}
}
Stmt::Import(ast::StmtImport { names, .. })
| Stmt::ImportFrom(ast::StmtImportFrom { names, .. }) => {
Stmt::Import(node) => {
// test_err debug_shadow_import
// import __debug__
// import debug as __debug__
@@ -541,18 +525,27 @@ impl SemanticSyntaxChecker {
// import __debug__ as debug
// from __debug__ import Some
// from x import __debug__ as debug
for name in names {
for name in &node.names {
match &name.asname {
Some(asname) => Self::check_identifier(asname, ctx),
None => Self::check_identifier(&name.name, ctx),
}
}
}
Stmt::Try(ast::StmtTry { handlers, .. }) => {
Stmt::ImportFrom(node) => {
for name in &node.names {
match &name.asname {
Some(asname) => Self::check_identifier(asname, ctx),
None => Self::check_identifier(&name.name, ctx),
}
}
}
Stmt::Try(node) => {
// test_err debug_shadow_try
// try: ...
// except Exception as __debug__: ...
for handler in handlers
for handler in node
.handlers
.iter()
.filter_map(ast::ExceptHandler::as_except_handler)
{
@@ -732,18 +725,18 @@ impl SemanticSyntaxChecker {
// update internal state
match stmt {
Stmt::Expr(StmtExpr { value, .. })
if !self.seen_module_docstring_boundary && value.is_string_literal_expr() => {}
Stmt::ImportFrom(StmtImportFrom { module, .. }) => {
Stmt::Expr(node)
if !self.seen_module_docstring_boundary && node.value.is_string_literal_expr() => {}
Stmt::ImportFrom(node) => {
// Allow __future__ imports until we see a non-__future__ import.
if !matches!(module.as_deref(), Some("__future__")) {
if !matches!(node.module.as_deref(), Some("__future__")) {
self.seen_futures_boundary = true;
}
}
Stmt::FunctionDef(StmtFunctionDef { is_async, body, .. }) => {
if *is_async {
Stmt::FunctionDef(node) => {
if node.is_async {
let mut visitor = ReturnVisitor::default();
visitor.visit_body(body);
visitor.visit_body(&node.body);
if visitor.has_yield {
if let Some(return_range) = visitor.return_range {