diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F841_1.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F841_1.py index 082646100d..24c051d9bd 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F841_1.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F841_1.py @@ -1,5 +1,5 @@ def f(tup): - x, y = tup # this does NOT trigger F841 + x, y = tup def f(): @@ -7,17 +7,17 @@ def f(): def f(): - (x, y) = coords = 1, 2 # this does NOT trigger F841 + (x, y) = coords = 1, 2 if x > 1: print(coords) def f(): - (x, y) = coords = 1, 2 # this triggers F841 on coords + (x, y) = coords = 1, 2 def f(): - coords = (x, y) = 1, 2 # this triggers F841 on coords + coords = (x, y) = 1, 2 def f(): diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F841_4.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F841_4.py new file mode 100644 index 0000000000..af1e733448 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F841_4.py @@ -0,0 +1,32 @@ +"""Test fix for issue #8441. + +Ref: https://github.com/astral-sh/ruff/issues/8441 +""" + + +def foo(): + ... + + +def bar(): + a = foo() + b, c = foo() + + +def baz(): + d, _e = foo() + print(d) + + +def qux(): + f, _ = foo() + print(f) + + +def quux(): + g, h = foo() + print(g, h) + + +def quuz(): + _i, _j = foo() diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index 4cb3a9f895..cfd3f54749 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -26,6 +26,7 @@ mod tests { use crate::linter::{check_path, LinterResult}; use crate::registry::{AsRule, Linter, Rule}; use crate::rules::pyflakes; + use crate::settings::types::PreviewMode; use crate::settings::{flags, LinterSettings}; use crate::source_kind::SourceKind; use crate::test::{test_path, test_snippet}; @@ -145,6 +146,7 @@ mod tests { #[test_case(Rule::UnusedVariable, Path::new("F841_1.py"))] #[test_case(Rule::UnusedVariable, Path::new("F841_2.py"))] #[test_case(Rule::UnusedVariable, Path::new("F841_3.py"))] + #[test_case(Rule::UnusedVariable, Path::new("F841_4.py"))] #[test_case(Rule::UnusedAnnotation, Path::new("F842.py"))] #[test_case(Rule::RaiseNotImplemented, Path::new("F901.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { @@ -157,6 +159,24 @@ mod tests { Ok(()) } + #[test_case(Rule::UnusedVariable, Path::new("F841_4.py"))] + fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!( + "preview__{}_{}", + rule_code.noqa_code(), + path.to_string_lossy() + ); + let diagnostics = test_path( + Path::new("pyflakes").join(path).as_path(), + &LinterSettings { + preview: PreviewMode::Enabled, + ..LinterSettings::for_rule(rule_code) + }, + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } + #[test] fn f841_dummy_variable_rgx() -> Result<()> { let diagnostics = test_path( @@ -1126,7 +1146,8 @@ mod tests { #[test] fn used_as_star_unpack() { - // Star names in unpack are used if RHS is not a tuple/list literal. + // In stable, starred names in unpack are used if RHS is not a tuple/list literal. + // In preview, these should be marked as unused. flakes( r#" def f(): diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs index b72bb5372f..2bf9ee3721 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs @@ -12,6 +12,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; use crate::fix::edits::delete_stmt; +use crate::settings::types::PreviewMode; /// ## What it does /// Checks for the presence of unused variables in function scopes. @@ -24,6 +25,9 @@ use crate::fix::edits::delete_stmt; /// prefixed with an underscore, or some other value that adheres to the /// [`dummy-variable-rgx`] pattern. /// +/// Under [preview mode](https://docs.astral.sh/ruff/preview), this rule also +/// triggers on unused unpacked assignments (for example, `x, y = foo()`). +/// /// ## Example /// ```python /// def foo(): @@ -318,7 +322,10 @@ pub(crate) fn unused_variable(checker: &Checker, scope: &Scope, diagnostics: &mu .bindings() .map(|(name, binding_id)| (name, checker.semantic().binding(binding_id))) .filter_map(|(name, binding)| { - if (binding.kind.is_assignment() || binding.kind.is_named_expr_assignment()) + if (binding.kind.is_assignment() + || binding.kind.is_named_expr_assignment() + || (matches!(checker.settings.preview, PreviewMode::Enabled) + && binding.kind.is_unpacked_assignment())) && !binding.is_nonlocal() && !binding.is_global() && !binding.is_used() diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F841_F841_1.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F841_F841_1.py.snap index 21aec0a0e5..9ddfa6f5d0 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F841_F841_1.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F841_F841_1.py.snap @@ -20,7 +20,7 @@ F841_1.py:6:8: F841 Local variable `y` is assigned to but never used F841_1.py:16:14: F841 [*] Local variable `coords` is assigned to but never used | 15 | def f(): -16 | (x, y) = coords = 1, 2 # this triggers F841 on coords +16 | (x, y) = coords = 1, 2 | ^^^^^^ F841 | = help: Remove assignment to unused variable `coords` @@ -29,8 +29,8 @@ F841_1.py:16:14: F841 [*] Local variable `coords` is assigned to but never used 13 13 | 14 14 | 15 15 | def f(): -16 |- (x, y) = coords = 1, 2 # this triggers F841 on coords - 16 |+ (x, y) = 1, 2 # this triggers F841 on coords +16 |- (x, y) = coords = 1, 2 + 16 |+ (x, y) = 1, 2 17 17 | 18 18 | 19 19 | def f(): @@ -38,7 +38,7 @@ F841_1.py:16:14: F841 [*] Local variable `coords` is assigned to but never used F841_1.py:20:5: F841 [*] Local variable `coords` is assigned to but never used | 19 | def f(): -20 | coords = (x, y) = 1, 2 # this triggers F841 on coords +20 | coords = (x, y) = 1, 2 | ^^^^^^ F841 | = help: Remove assignment to unused variable `coords` @@ -47,8 +47,8 @@ F841_1.py:20:5: F841 [*] Local variable `coords` is assigned to but never used 17 17 | 18 18 | 19 19 | def f(): -20 |- coords = (x, y) = 1, 2 # this triggers F841 on coords - 20 |+ (x, y) = 1, 2 # this triggers F841 on coords +20 |- coords = (x, y) = 1, 2 + 20 |+ (x, y) = 1, 2 21 21 | 22 22 | 23 23 | def f(): diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F841_F841_4.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F841_F841_4.py.snap new file mode 100644 index 0000000000..f11e0a7a6e --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F841_F841_4.py.snap @@ -0,0 +1,23 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- +F841_4.py:12:5: F841 [*] Local variable `a` is assigned to but never used + | +11 | def bar(): +12 | a = foo() + | ^ F841 +13 | b, c = foo() + | + = help: Remove assignment to unused variable `a` + +ℹ Suggested fix +9 9 | +10 10 | +11 11 | def bar(): +12 |- a = foo() + 12 |+ foo() +13 13 | b, c = foo() +14 14 | +15 15 | + + diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F841_F841_4.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F841_F841_4.py.snap new file mode 100644 index 0000000000..1627e74b9a --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__preview__F841_F841_4.py.snap @@ -0,0 +1,41 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- +F841_4.py:12:5: F841 [*] Local variable `a` is assigned to but never used + | +11 | def bar(): +12 | a = foo() + | ^ F841 +13 | b, c = foo() + | + = help: Remove assignment to unused variable `a` + +ℹ Suggested fix +9 9 | +10 10 | +11 11 | def bar(): +12 |- a = foo() + 12 |+ foo() +13 13 | b, c = foo() +14 14 | +15 15 | + +F841_4.py:13:5: F841 Local variable `b` is assigned to but never used + | +11 | def bar(): +12 | a = foo() +13 | b, c = foo() + | ^ F841 + | + = help: Remove assignment to unused variable `b` + +F841_4.py:13:8: F841 Local variable `c` is assigned to but never used + | +11 | def bar(): +12 | a = foo() +13 | b, c = foo() + | ^ F841 + | + = help: Remove assignment to unused variable `c` + +