Skip to content

Commit da32a83

Browse files
authored
[syntax-errors] return outside function (#17300)
Summary -- This PR reimplements [return-outside-function (F706)](https://docs.astral.sh/ruff/rules/return-outside-function/) as a semantic syntax error. These changes are very similar to those in #17298. Test Plan -- New linter tests, plus existing F706 tests.
1 parent 4bfdf54 commit da32a83

File tree

7 files changed

+82
-29
lines changed

7 files changed

+82
-29
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
def f():
2+
return 1 # okay
3+
4+
5+
def f():
6+
return # okay
7+
8+
9+
async def f():
10+
return # okay
11+
12+
13+
return 1 # error
14+
return # error
15+
16+
17+
class C:
18+
return 1 # error
19+
20+
21+
def f():
22+
class C:
23+
return 1 # error

crates/ruff_linter/src/checkers/ast/analyze/statement.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,9 +380,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
380380
}
381381
}
382382
Stmt::Return(_) => {
383-
if checker.enabled(Rule::ReturnOutsideFunction) {
384-
pyflakes::rules::return_outside_function(checker, stmt);
385-
}
386383
if checker.enabled(Rule::ReturnInInit) {
387384
pylint::rules::return_in_init(checker, stmt);
388385
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ use crate::importer::{ImportRequest, Importer, ResolutionError};
6666
use crate::noqa::NoqaMapping;
6767
use crate::package::PackageRoot;
6868
use crate::registry::Rule;
69-
use crate::rules::pyflakes::rules::{LateFutureImport, YieldOutsideFunction};
69+
use crate::rules::pyflakes::rules::{
70+
LateFutureImport, ReturnOutsideFunction, YieldOutsideFunction,
71+
};
7072
use crate::rules::pylint::rules::LoadBeforeGlobalDeclaration;
7173
use crate::rules::{flake8_pyi, flake8_type_checking, pyflakes, pyupgrade};
7274
use crate::settings::{flags, LinterSettings};
@@ -597,6 +599,11 @@ impl SemanticSyntaxContext for Checker<'_> {
597599
));
598600
}
599601
}
602+
SemanticSyntaxErrorKind::ReturnOutsideFunction => {
603+
if self.settings.rules.enabled(Rule::ReturnOutsideFunction) {
604+
self.report_diagnostic(Diagnostic::new(ReturnOutsideFunction, error.range));
605+
}
606+
}
600607
SemanticSyntaxErrorKind::ReboundComprehensionVariable
601608
| SemanticSyntaxErrorKind::DuplicateTypeParameter
602609
| SemanticSyntaxErrorKind::MultipleCaseAssignment(_)

crates/ruff_linter/src/linter.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,14 +1061,15 @@ mod tests {
10611061
Ok(())
10621062
}
10631063

1064-
#[test_case(Path::new("yield_scope.py"); "yield_scope")]
1065-
fn test_yield_scope(path: &Path) -> Result<()> {
1064+
#[test_case(Rule::YieldOutsideFunction, Path::new("yield_scope.py"))]
1065+
#[test_case(Rule::ReturnOutsideFunction, Path::new("return_outside_function.py"))]
1066+
fn test_syntax_errors(rule: Rule, path: &Path) -> Result<()> {
10661067
let snapshot = path.to_string_lossy().to_string();
10671068
let path = Path::new("resources/test/fixtures/syntax_errors").join(path);
10681069
let messages = test_contents_syntax_errors(
10691070
&SourceKind::Python(std::fs::read_to_string(&path)?),
10701071
&path,
1071-
&settings::LinterSettings::for_rule(Rule::YieldOutsideFunction),
1072+
&settings::LinterSettings::for_rule(rule),
10721073
);
10731074
insta::with_settings!({filters => vec![(r"\\", "/")]}, {
10741075
assert_messages!(snapshot, messages);

crates/ruff_linter/src/rules/pyflakes/rules/return_outside_function.rs

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
1-
use ruff_python_ast::Stmt;
2-
3-
use ruff_diagnostics::{Diagnostic, Violation};
1+
use ruff_diagnostics::Violation;
42
use ruff_macros::{derive_message_formats, ViolationMetadata};
5-
use ruff_python_semantic::ScopeKind;
6-
use ruff_text_size::Ranged;
7-
8-
use crate::checkers::ast::Checker;
93

104
/// ## What it does
115
/// Checks for `return` statements outside of functions.
@@ -31,12 +25,3 @@ impl Violation for ReturnOutsideFunction {
3125
"`return` statement outside of a function/method".to_string()
3226
}
3327
}
34-
35-
pub(crate) fn return_outside_function(checker: &Checker, stmt: &Stmt) {
36-
if matches!(
37-
checker.semantic().current_scope().kind,
38-
ScopeKind::Class(_) | ScopeKind::Module
39-
) {
40-
checker.report_diagnostic(Diagnostic::new(ReturnOutsideFunction, stmt.range()));
41-
}
42-
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
source: crates/ruff_linter/src/linter.rs
3+
---
4+
resources/test/fixtures/syntax_errors/return_outside_function.py:13:1: F706 `return` statement outside of a function/method
5+
|
6+
13 | return 1 # error
7+
| ^^^^^^^^ F706
8+
14 | return # error
9+
|
10+
11+
resources/test/fixtures/syntax_errors/return_outside_function.py:14:1: F706 `return` statement outside of a function/method
12+
|
13+
13 | return 1 # error
14+
14 | return # error
15+
| ^^^^^^ F706
16+
|
17+
18+
resources/test/fixtures/syntax_errors/return_outside_function.py:18:5: F706 `return` statement outside of a function/method
19+
|
20+
17 | class C:
21+
18 | return 1 # error
22+
| ^^^^^^^^ F706
23+
|
24+
25+
resources/test/fixtures/syntax_errors/return_outside_function.py:23:9: F706 `return` statement outside of a function/method
26+
|
27+
21 | def f():
28+
22 | class C:
29+
23 | return 1 # error
30+
| ^^^^^^^^ F706
31+
|

crates/ruff_python_parser/src/semantic_errors.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,15 @@ impl SemanticSyntaxChecker {
9393
);
9494
}
9595
}
96-
Stmt::Return(ast::StmtReturn {
97-
value: Some(value), ..
98-
}) => {
99-
// test_err single_star_return
100-
// def f(): return *x
101-
Self::invalid_star_expression(value, ctx);
96+
Stmt::Return(ast::StmtReturn { value, range }) => {
97+
if let Some(value) = value {
98+
// test_err single_star_return
99+
// def f(): return *x
100+
Self::invalid_star_expression(value, ctx);
101+
}
102+
if !ctx.in_function_scope() {
103+
Self::add_error(ctx, SemanticSyntaxErrorKind::ReturnOutsideFunction, *range);
104+
}
102105
}
103106
Stmt::For(ast::StmtFor { target, iter, .. }) => {
104107
// test_err single_star_for
@@ -797,6 +800,9 @@ impl Display for SemanticSyntaxError {
797800
SemanticSyntaxErrorKind::YieldOutsideFunction(kind) => {
798801
write!(f, "`{kind}` statement outside of a function")
799802
}
803+
SemanticSyntaxErrorKind::ReturnOutsideFunction => {
804+
f.write_str("`return` statement outside of a function")
805+
}
800806
}
801807
}
802808
}
@@ -1092,6 +1098,9 @@ pub enum SemanticSyntaxErrorKind {
10921098
/// [PEP 492]: https://peps.python.org/pep-0492/
10931099
/// [PEP 530]: https://peps.python.org/pep-0530/
10941100
YieldOutsideFunction(YieldOutsideFunctionKind),
1101+
1102+
/// Represents the use of `return` outside of a function scope.
1103+
ReturnOutsideFunction,
10951104
}
10961105

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

0 commit comments

Comments
 (0)