mirror of https://github.com/astral-sh/ruff
Fix `F841` false negative on assignment to multiple variables (#8489)
## Summary Closes #8441 behind preview feature flag. ## Test Plan `cargo test`
This commit is contained in:
parent
b3c2935fa5
commit
8c0d65c98e
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
||||
|
||||
|
|
@ -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`
|
||||
|
||||
|
||||
Loading…
Reference in New Issue