Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/ruff/RUF070.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
###
# Errors
###

def foo():
x = 1
yield x # RUF070

def foo():
x = [1, 2, 3]
yield from x # RUF070

def foo():
for i in range(10):
x = i * 2
yield x # RUF070

def foo():
if True:
x = 1
yield x # RUF070

def foo():
with open("foo.txt") as f:
x = f.read()
yield x # RUF070

def foo():
try:
x = something()
yield x # RUF070
except Exception:
pass

def foo():
x = some_function()
yield x # RUF070

def foo():
x = some_generator()
yield from x # RUF070

def foo():
x = lambda: 1
yield x # RUF070

def foo():
x = (y := 1)
yield x # RUF070

def foo():
x =1
yield x # RUF070 (no space after `=`)

def foo():
x = yield 1
yield x # RUF070 (yield as assigned value, fix adds parentheses)

# Assignment inside `with`, yield outside
def foo():
with open("foo.txt") as f:
x = f.read()
yield x # RUF070


###
# Non-errors
###

# Variable used after yield
def foo():
x = 1
yield x
print(x)

# Variable used before yield (two references)
def foo():
x = 1
print(x)
yield x

# Multiple yields of same var
def foo():
x = 1
yield x
yield x

# Annotated variable
def foo():
x: int
x = 1
yield x

# Nonlocal variable
def foo():
x = 0
def bar():
nonlocal x
x = 1
yield x

# Global variable
def foo():
global x
x = 1
yield x

# Intervening statement between assign and yield
def foo():
x = 1
print("hello")
yield x

# Augmented assignment
def foo():
x = 1
x += 1
yield x

# Unpacking assignment
def foo():
x, y = 1, 2
yield x

# Non-name target (attribute)
def foo():
self.x = 1
yield self.x

# Yield with no value
def foo():
x = 1
yield

# Multiple assignment targets (e.g. `x = y = 1`)
def foo():
x = y = 1
yield x

# Different variable names
def foo():
x = 1
yield y

# Cross-scope reference (closure)
def foo():
x = 1
def inner():
print(x)
yield x

# Cross-scope reference (comprehension)
def foo():
x = 1
_ = [i for i in x]
yield x

# Yield from with non-name value
def foo():
yield from [1, 2, 3]

# Yield non-name value
def foo():
yield 1

# Variable used in nested function after yield
def foo():
x = compute()
yield x
def bar():
return x

# Yield non-name value preceded by assignment
def foo():
x = 1
yield x + 1

# Yield from non-name value preceded by assignment
def foo():
x = [1, 2, 3]
yield from [1, 2, 3]

# Annotated assignment with value (not a plain assignment)
def foo():
x: int = 1
yield x

# Assignment inside `with` using `contextlib.suppress` (body may not execute)
import contextlib

def foo():
x = default()
with contextlib.suppress(Exception):
x = something()
yield x
9 changes: 9 additions & 0 deletions crates/ruff_linter/src/checkers/ast/analyze/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub(crate) fn bindings(checker: &Checker) {
Rule::CustomTypeVarForSelf,
Rule::PrivateTypeParameter,
Rule::UnnecessaryAssign,
Rule::UnnecessaryAssignBeforeYield,
]) {
return;
}
Expand All @@ -39,6 +40,14 @@ pub(crate) fn bindings(checker: &Checker) {
);
}
}
if checker.is_rule_enabled(Rule::UnnecessaryAssignBeforeYield) {
if binding.kind.is_function_definition() {
ruff::rules::unnecessary_assign_before_yield(
checker,
binding.statement(checker.semantic()).unwrap(),
);
}
}
if checker.is_rule_enabled(Rule::UnusedVariable) {
if binding.kind.is_bound_exception()
&& binding.is_unused()
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "067") => rules::ruff::rules::NonEmptyInitModule,
(Ruff, "068") => rules::ruff::rules::DuplicateEntryInDunderAll,
(Ruff, "069") => rules::ruff::rules::FloatEqualityComparison,
(Ruff, "070") => rules::ruff::rules::UnnecessaryAssignBeforeYield,

(Ruff, "100") => rules::ruff::rules::UnusedNOQA,
(Ruff, "101") => rules::ruff::rules::RedirectedNOQA,
Expand Down
2 changes: 2 additions & 0 deletions crates/ruff_linter/src/rules/flake8_return/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ mod helpers;
pub(crate) mod rules;
mod visitor;

pub(crate) use visitor::has_conditional_body;

#[cfg(test)]
mod tests {
use std::path::Path;
Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_linter/src/rules/flake8_return/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ impl<'a> Visitor<'a> for ReturnVisitor<'_, 'a> {
/// data = data.decode()
/// return data
/// ```
fn has_conditional_body(with: &ast::StmtWith, semantic: &SemanticModel) -> bool {
pub(crate) fn has_conditional_body(with: &ast::StmtWith, semantic: &SemanticModel) -> bool {
with.items.iter().any(|item| {
let ast::WithItem {
context_expr: Expr::Call(ast::ExprCall { func, .. }),
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_linter/src/rules/ruff/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ mod tests {
#[test_case(Rule::IndentedFormFeed, Path::new("RUF054.py"))]
#[test_case(Rule::ImplicitClassVarInDataclass, Path::new("RUF045.py"))]
#[test_case(Rule::FloatEqualityComparison, Path::new("RUF069.py"))]
#[test_case(Rule::UnnecessaryAssignBeforeYield, Path::new("RUF070.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",
Expand Down
2 changes: 2 additions & 0 deletions crates/ruff_linter/src/rules/ruff/rules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub(crate) use static_key_dict_comprehension::*;
#[cfg(any(feature = "test-rules", test))]
pub(crate) use test_rules::*;
pub(crate) use unmatched_suppression_comment::*;
pub(crate) use unnecessary_assign_before_yield::*;
pub(crate) use unnecessary_cast_to_int::*;
pub(crate) use unnecessary_iterable_allocation_for_first_element::*;
pub(crate) use unnecessary_key_check::*;
Expand Down Expand Up @@ -123,6 +124,7 @@ mod suppression_comment_visitor;
#[cfg(any(feature = "test-rules", test))]
pub(crate) mod test_rules;
mod unmatched_suppression_comment;
mod unnecessary_assign_before_yield;
mod unnecessary_cast_to_int;
mod unnecessary_iterable_allocation_for_first_element;
mod unnecessary_key_check;
Expand Down
Loading