Skip to content

Commit cf59cee

Browse files
[syntax-errors] nonlocal declaration at module level (#17559)
## Summary Part of #17412 Add a new compile-time syntax error for detecting `nonlocal` declarations at a module level. ## Test Plan - Added new inline tests for the syntax error - Updated existing tests for `nonlocal` statement parsing to be inside a function scope Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
1 parent 538393d commit cf59cee

16 files changed

+363
-131
lines changed

crates/ruff_linter/src/checkers/ast/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,8 @@ impl SemanticSyntaxContext for Checker<'_> {
616616
| SemanticSyntaxErrorKind::DuplicateMatchClassAttribute(_)
617617
| SemanticSyntaxErrorKind::InvalidStarExpression
618618
| SemanticSyntaxErrorKind::AsyncComprehensionInSyncComprehension(_)
619-
| SemanticSyntaxErrorKind::DuplicateParameter(_) => {
619+
| SemanticSyntaxErrorKind::DuplicateParameter(_)
620+
| SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel => {
620621
if self.settings.preview.is_enabled() {
621622
self.semantic_errors.borrow_mut().push(error);
622623
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
nonlocal x
2+
nonlocal x, y
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
nonlocal
1+
def _():
2+
nonlocal
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
nonlocal x + 1
1+
def _():
2+
nonlocal x + 1
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
nonlocal ,
2-
nonlocal x,
3-
nonlocal x, y,
1+
def _():
2+
nonlocal ,
3+
nonlocal x,
4+
nonlocal x, y,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def _():
2+
nonlocal x
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
nonlocal x
2-
nonlocal x, y, z
1+
def _():
2+
nonlocal x
3+
nonlocal x, y, z

crates/ruff_python_parser/src/parser/statement.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -897,29 +897,33 @@ impl<'src> Parser<'src> {
897897
self.bump(TokenKind::Nonlocal);
898898

899899
// test_err nonlocal_stmt_trailing_comma
900-
// nonlocal ,
901-
// nonlocal x,
902-
// nonlocal x, y,
900+
// def _():
901+
// nonlocal ,
902+
// nonlocal x,
903+
// nonlocal x, y,
903904

904905
// test_err nonlocal_stmt_expression
905-
// nonlocal x + 1
906+
// def _():
907+
// nonlocal x + 1
906908
let names = self.parse_comma_separated_list_into_vec(
907909
RecoveryContextKind::Identifiers,
908910
Parser::parse_identifier,
909911
);
910912

911913
if names.is_empty() {
912914
// test_err nonlocal_stmt_empty
913-
// nonlocal
915+
// def _():
916+
// nonlocal
914917
self.add_error(
915918
ParseErrorType::EmptyNonlocalNames,
916919
self.current_token_range(),
917920
);
918921
}
919922

920923
// test_ok nonlocal_stmt
921-
// nonlocal x
922-
// nonlocal x, y, z
924+
// def _():
925+
// nonlocal x
926+
// nonlocal x, y, z
923927
ast::StmtNonlocal {
924928
range: self.node_range(start),
925929
names,

crates/ruff_python_parser/src/semantic_errors.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,22 @@ impl SemanticSyntaxChecker {
142142
AwaitOutsideAsyncFunctionKind::AsyncWith,
143143
);
144144
}
145+
Stmt::Nonlocal(ast::StmtNonlocal { range, .. }) => {
146+
// test_ok nonlocal_declaration_at_module_level
147+
// def _():
148+
// nonlocal x
149+
150+
// test_err nonlocal_declaration_at_module_level
151+
// nonlocal x
152+
// nonlocal x, y
153+
if ctx.in_module_scope() {
154+
Self::add_error(
155+
ctx,
156+
SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel,
157+
*range,
158+
);
159+
}
160+
}
145161
_ => {}
146162
}
147163

@@ -933,6 +949,9 @@ impl Display for SemanticSyntaxError {
933949
SemanticSyntaxErrorKind::DuplicateParameter(name) => {
934950
write!(f, r#"Duplicate parameter "{name}""#)
935951
}
952+
SemanticSyntaxErrorKind::NonlocalDeclarationAtModuleLevel => {
953+
write!(f, "nonlocal declaration not allowed at module level")
954+
}
936955
}
937956
}
938957
}
@@ -1254,6 +1273,9 @@ pub enum SemanticSyntaxErrorKind {
12541273
/// lambda x, x: ...
12551274
/// ```
12561275
DuplicateParameter(String),
1276+
1277+
/// Represents a nonlocal declaration at module level
1278+
NonlocalDeclarationAtModuleLevel,
12571279
}
12581280

12591281
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]

crates/ruff_python_parser/tests/fixtures.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ impl SemanticSyntaxContext for SemanticSyntaxCheckerVisitor<'_> {
538538
}
539539

540540
fn in_module_scope(&self) -> bool {
541-
true
541+
self.scopes.len() == 1
542542
}
543543

544544
fn in_function_scope(&self) -> bool {

0 commit comments

Comments
 (0)