diff --git a/crates/ruff_linter/resources/test/fixtures/syntax_errors/return_outside_function.py b/crates/ruff_linter/resources/test/fixtures/syntax_errors/return_outside_function.py new file mode 100644 index 0000000000..5fd759479c --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/syntax_errors/return_outside_function.py @@ -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 diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 744bb7c11d..17f8609a16 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -380,9 +380,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { } } Stmt::Return(_) => { - if checker.enabled(Rule::ReturnOutsideFunction) { - pyflakes::rules::return_outside_function(checker, stmt); - } if checker.enabled(Rule::ReturnInInit) { pylint::rules::return_in_init(checker, stmt); } diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 31ecc71262..50c2da238b 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -66,7 +66,9 @@ use crate::importer::{ImportRequest, Importer, ResolutionError}; use crate::noqa::NoqaMapping; use crate::package::PackageRoot; 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::{flake8_pyi, flake8_type_checking, pyflakes, pyupgrade}; 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::DuplicateTypeParameter | SemanticSyntaxErrorKind::MultipleCaseAssignment(_) diff --git a/crates/ruff_linter/src/linter.rs b/crates/ruff_linter/src/linter.rs index 96f8f114ea..b266a82e54 100644 --- a/crates/ruff_linter/src/linter.rs +++ b/crates/ruff_linter/src/linter.rs @@ -1061,14 +1061,15 @@ mod tests { Ok(()) } - #[test_case(Path::new("yield_scope.py"); "yield_scope")] - fn test_yield_scope(path: &Path) -> Result<()> { + #[test_case(Rule::YieldOutsideFunction, Path::new("yield_scope.py"))] + #[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 path = Path::new("resources/test/fixtures/syntax_errors").join(path); let messages = test_contents_syntax_errors( &SourceKind::Python(std::fs::read_to_string(&path)?), &path, - &settings::LinterSettings::for_rule(Rule::YieldOutsideFunction), + &settings::LinterSettings::for_rule(rule), ); insta::with_settings!({filters => vec![(r"\\", "/")]}, { assert_messages!(snapshot, messages); diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/return_outside_function.rs b/crates/ruff_linter/src/rules/pyflakes/rules/return_outside_function.rs index 548452b3cf..ce7affefdd 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/return_outside_function.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/return_outside_function.rs @@ -1,11 +1,5 @@ -use ruff_python_ast::Stmt; - -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::Violation; 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 /// Checks for `return` statements outside of functions. @@ -31,12 +25,3 @@ impl Violation for ReturnOutsideFunction { "`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())); - } -} diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__return_outside_function.py.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__return_outside_function.py.snap new file mode 100644 index 0000000000..db348572fe --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__return_outside_function.py.snap @@ -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 + | diff --git a/crates/ruff_python_parser/src/semantic_errors.rs b/crates/ruff_python_parser/src/semantic_errors.rs index b6fbb13676..c41f4af6f3 100644 --- a/crates/ruff_python_parser/src/semantic_errors.rs +++ b/crates/ruff_python_parser/src/semantic_errors.rs @@ -93,12 +93,15 @@ impl SemanticSyntaxChecker { ); } } - Stmt::Return(ast::StmtReturn { - value: Some(value), .. - }) => { - // test_err single_star_return - // def f(): return *x - Self::invalid_star_expression(value, ctx); + Stmt::Return(ast::StmtReturn { value, range }) => { + if let Some(value) = value { + // test_err single_star_return + // def f(): return *x + Self::invalid_star_expression(value, ctx); + } + if !ctx.in_function_scope() { + Self::add_error(ctx, SemanticSyntaxErrorKind::ReturnOutsideFunction, *range); + } } Stmt::For(ast::StmtFor { target, iter, .. }) => { // test_err single_star_for @@ -797,6 +800,9 @@ impl Display for SemanticSyntaxError { SemanticSyntaxErrorKind::YieldOutsideFunction(kind) => { 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 530]: https://peps.python.org/pep-0530/ YieldOutsideFunction(YieldOutsideFunctionKind), + + /// Represents the use of `return` outside of a function scope. + ReturnOutsideFunction, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]