[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
https://github.com/astral-sh/ruff/pull/17298.

Test Plan
--

New linter tests, plus existing F706 tests.
This commit is contained in:
Brent Westbrook 2025-04-11 13:05:54 -04:00 committed by GitHub
parent 4bfdf54d1a
commit da32a83c9f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 82 additions and 29 deletions

View file

@ -0,0 +1,23 @@
def f():
return 1 # okay
def f():
return # okay
async def f():
return # okay
return 1 # error
return # error
class C:
return 1 # error
def f():
class C:
return 1 # error

View file

@ -380,9 +380,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
} }
} }
Stmt::Return(_) => { Stmt::Return(_) => {
if checker.enabled(Rule::ReturnOutsideFunction) {
pyflakes::rules::return_outside_function(checker, stmt);
}
if checker.enabled(Rule::ReturnInInit) { if checker.enabled(Rule::ReturnInInit) {
pylint::rules::return_in_init(checker, stmt); pylint::rules::return_in_init(checker, stmt);
} }

View file

@ -66,7 +66,9 @@ use crate::importer::{ImportRequest, Importer, ResolutionError};
use crate::noqa::NoqaMapping; use crate::noqa::NoqaMapping;
use crate::package::PackageRoot; use crate::package::PackageRoot;
use crate::registry::Rule; use crate::registry::Rule;
use crate::rules::pyflakes::rules::{LateFutureImport, YieldOutsideFunction}; use crate::rules::pyflakes::rules::{
LateFutureImport, ReturnOutsideFunction, YieldOutsideFunction,
};
use crate::rules::pylint::rules::LoadBeforeGlobalDeclaration; use crate::rules::pylint::rules::LoadBeforeGlobalDeclaration;
use crate::rules::{flake8_pyi, flake8_type_checking, pyflakes, pyupgrade}; use crate::rules::{flake8_pyi, flake8_type_checking, pyflakes, pyupgrade};
use crate::settings::{flags, LinterSettings}; use crate::settings::{flags, LinterSettings};
@ -597,6 +599,11 @@ impl SemanticSyntaxContext for Checker<'_> {
)); ));
} }
} }
SemanticSyntaxErrorKind::ReturnOutsideFunction => {
if self.settings.rules.enabled(Rule::ReturnOutsideFunction) {
self.report_diagnostic(Diagnostic::new(ReturnOutsideFunction, error.range));
}
}
SemanticSyntaxErrorKind::ReboundComprehensionVariable SemanticSyntaxErrorKind::ReboundComprehensionVariable
| SemanticSyntaxErrorKind::DuplicateTypeParameter | SemanticSyntaxErrorKind::DuplicateTypeParameter
| SemanticSyntaxErrorKind::MultipleCaseAssignment(_) | SemanticSyntaxErrorKind::MultipleCaseAssignment(_)

View file

@ -1061,14 +1061,15 @@ mod tests {
Ok(()) Ok(())
} }
#[test_case(Path::new("yield_scope.py"); "yield_scope")] #[test_case(Rule::YieldOutsideFunction, Path::new("yield_scope.py"))]
fn test_yield_scope(path: &Path) -> Result<()> { #[test_case(Rule::ReturnOutsideFunction, Path::new("return_outside_function.py"))]
fn test_syntax_errors(rule: Rule, path: &Path) -> Result<()> {
let snapshot = path.to_string_lossy().to_string(); let snapshot = path.to_string_lossy().to_string();
let path = Path::new("resources/test/fixtures/syntax_errors").join(path); let path = Path::new("resources/test/fixtures/syntax_errors").join(path);
let messages = test_contents_syntax_errors( let messages = test_contents_syntax_errors(
&SourceKind::Python(std::fs::read_to_string(&path)?), &SourceKind::Python(std::fs::read_to_string(&path)?),
&path, &path,
&settings::LinterSettings::for_rule(Rule::YieldOutsideFunction), &settings::LinterSettings::for_rule(rule),
); );
insta::with_settings!({filters => vec![(r"\\", "/")]}, { insta::with_settings!({filters => vec![(r"\\", "/")]}, {
assert_messages!(snapshot, messages); assert_messages!(snapshot, messages);

View file

@ -1,11 +1,5 @@
use ruff_python_ast::Stmt; use ruff_diagnostics::Violation;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_semantic::ScopeKind;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does /// ## What it does
/// Checks for `return` statements outside of functions. /// Checks for `return` statements outside of functions.
@ -31,12 +25,3 @@ impl Violation for ReturnOutsideFunction {
"`return` statement outside of a function/method".to_string() "`return` statement outside of a function/method".to_string()
} }
} }
pub(crate) fn return_outside_function(checker: &Checker, stmt: &Stmt) {
if matches!(
checker.semantic().current_scope().kind,
ScopeKind::Class(_) | ScopeKind::Module
) {
checker.report_diagnostic(Diagnostic::new(ReturnOutsideFunction, stmt.range()));
}
}

View file

@ -0,0 +1,31 @@
---
source: crates/ruff_linter/src/linter.rs
---
resources/test/fixtures/syntax_errors/return_outside_function.py:13:1: F706 `return` statement outside of a function/method
|
13 | return 1 # error
| ^^^^^^^^ F706
14 | return # error
|
resources/test/fixtures/syntax_errors/return_outside_function.py:14:1: F706 `return` statement outside of a function/method
|
13 | return 1 # error
14 | return # error
| ^^^^^^ F706
|
resources/test/fixtures/syntax_errors/return_outside_function.py:18:5: F706 `return` statement outside of a function/method
|
17 | class C:
18 | return 1 # error
| ^^^^^^^^ F706
|
resources/test/fixtures/syntax_errors/return_outside_function.py:23:9: F706 `return` statement outside of a function/method
|
21 | def f():
22 | class C:
23 | return 1 # error
| ^^^^^^^^ F706
|

View file

@ -93,13 +93,16 @@ impl SemanticSyntaxChecker {
); );
} }
} }
Stmt::Return(ast::StmtReturn { Stmt::Return(ast::StmtReturn { value, range }) => {
value: Some(value), .. if let Some(value) = value {
}) => {
// test_err single_star_return // test_err single_star_return
// def f(): return *x // def f(): return *x
Self::invalid_star_expression(value, ctx); Self::invalid_star_expression(value, ctx);
} }
if !ctx.in_function_scope() {
Self::add_error(ctx, SemanticSyntaxErrorKind::ReturnOutsideFunction, *range);
}
}
Stmt::For(ast::StmtFor { target, iter, .. }) => { Stmt::For(ast::StmtFor { target, iter, .. }) => {
// test_err single_star_for // test_err single_star_for
// for _ in *x: ... // for _ in *x: ...
@ -797,6 +800,9 @@ impl Display for SemanticSyntaxError {
SemanticSyntaxErrorKind::YieldOutsideFunction(kind) => { SemanticSyntaxErrorKind::YieldOutsideFunction(kind) => {
write!(f, "`{kind}` statement outside of a function") write!(f, "`{kind}` statement outside of a function")
} }
SemanticSyntaxErrorKind::ReturnOutsideFunction => {
f.write_str("`return` statement outside of a function")
}
} }
} }
} }
@ -1092,6 +1098,9 @@ pub enum SemanticSyntaxErrorKind {
/// [PEP 492]: https://peps.python.org/pep-0492/ /// [PEP 492]: https://peps.python.org/pep-0492/
/// [PEP 530]: https://peps.python.org/pep-0530/ /// [PEP 530]: https://peps.python.org/pep-0530/
YieldOutsideFunction(YieldOutsideFunctionKind), YieldOutsideFunction(YieldOutsideFunctionKind),
/// Represents the use of `return` outside of a function scope.
ReturnOutsideFunction,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]