Add new rule RUF059: Unused unpacked assignment (#16449)

Split from F841 following discussion in #8884.

Fixes #8884.

<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

Add a new rule for unused assignments in tuples. Remove similar behavior
from F841.

## Test Plan

Adapt F841 tests and move them over to the new rule.

<!-- How was it tested? -->

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
Jelle Zijlstra 2025-03-03 01:51:36 -08:00 committed by GitHub
parent be239b9f25
commit c80678a1c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 893 additions and 132 deletions

View file

@ -0,0 +1,96 @@
try:
1 / 0
except ValueError as e:
pass
try:
1 / 0
except ValueError as e:
print(e)
def f():
x = 1
y = 2
z = x + y
def f():
foo = (1, 2)
(a, b) = (1, 2)
bar = (1, 2)
(c, d) = bar
(x, y) = baz = bar
def f():
locals()
x = 1
def f():
_ = 1
__ = 1
_discarded = 1
a = 1
def f():
global a
# Used in `c` via `nonlocal`.
b = 1
def c():
# F841
b = 1
def d():
nonlocal b
def f():
annotations = []
assert len([annotations for annotations in annotations])
def f():
def connect():
return None, None
with connect() as (connection, cursor):
cursor.execute("SELECT * FROM users")
def f():
def connect():
return None, None
with connect() as (connection, cursor):
cursor.execute("SELECT * FROM users")
def f():
with open("file") as my_file, open("") as ((this, that)):
print("hello")
def f():
with (
open("file") as my_file,
open("") as ((this, that)),
):
print("hello")
def f():
exponential, base_multiplier = 1, 2
hash_map = {
(exponential := (exponential * base_multiplier) % 3): i + 1 for i in range(2)
}
return hash_map

View file

@ -0,0 +1,24 @@
def f(tup):
x, y = tup
def f():
x, y = 1, 2 # this triggers RUF059 as it's just a simple assignment where unpacking isn't needed
def f():
(x, y) = coords = 1, 2
if x > 1:
print(coords)
def f():
(x, y) = coords = 1, 2
def f():
coords = (x, y) = 1, 2
def f():
(a, b) = (x, y) = 1, 2 # this triggers RUF059 on everything

View file

@ -0,0 +1,31 @@
"""Test case for fixing RUF059 violations."""
def f():
with foo() as x1:
pass
with foo() as (x2, y2):
pass
with (foo() as x3, foo() as y3, foo() as z3):
pass
def f():
(x1, y1) = (1, 2)
(x2, y2) = coords2 = (1, 2)
coords3 = (x3, y3) = (1, 2)
def f():
with Nested(m) as (x, y):
pass
def f():
toplevel = (a, b) = lexer.get_token()
def f():
(a, b) = toplevel = lexer.get_token()

View file

@ -48,6 +48,7 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
Rule::UnusedPrivateTypedDict,
Rule::UnusedPrivateTypeVar,
Rule::UnusedStaticMethodArgument,
Rule::UnusedUnpackedVariable,
Rule::UnusedVariable,
]) {
return;
@ -390,8 +391,43 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
}
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Lambda(_)) {
if checker.any_enabled(&[Rule::UnusedVariable, Rule::UnusedUnpackedVariable])
&& !(scope.uses_locals() && scope.kind.is_function())
{
let unused_bindings = scope
.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()
|| binding.kind.is_with_item_var())
&& binding.is_unused()
&& !binding.is_nonlocal()
&& !binding.is_global()
&& !checker.settings.dummy_variable_rgx.is_match(name)
&& !matches!(
name,
"__tracebackhide__"
| "__traceback_info__"
| "__traceback_supplement__"
| "__debuggerskip__"
)
{
return Some((name, binding));
}
None
});
for (unused_name, unused_binding) in unused_bindings {
if checker.enabled(Rule::UnusedVariable) {
pyflakes::rules::unused_variable(checker, scope);
pyflakes::rules::unused_variable(checker, unused_name, unused_binding);
}
if checker.enabled(Rule::UnusedUnpackedVariable) {
ruff::rules::unused_unpacked_variable(checker, unused_name, unused_binding);
}
}
}
if checker.enabled(Rule::UnusedAnnotation) {

View file

@ -1012,6 +1012,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "056") => (RuleGroup::Preview, rules::ruff::rules::FalsyDictGetFallback),
(Ruff, "057") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRound),
(Ruff, "058") => (RuleGroup::Preview, rules::ruff::rules::StarmapZip),
(Ruff, "059") => (RuleGroup::Preview, rules::ruff::rules::UnusedUnpackedVariable),
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),

View file

@ -1,7 +1,6 @@
---
source: crates/ruff_linter/src/message/sarif.rs
expression: value
snapshot_kind: text
---
{
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
@ -120,7 +119,7 @@ snapshot_kind: text
},
{
"fullDescription": {
"text": "## What it does\nChecks for the presence of unused variables in function scopes.\n\n## Why is this bad?\nA variable that is defined but not used is likely a mistake, and should\nbe removed to avoid confusion.\n\nIf a variable is intentionally defined-but-not-used, it should be\nprefixed with an underscore, or some other value that adheres to the\n[`lint.dummy-variable-rgx`] pattern.\n\nUnder [preview mode](https://docs.astral.sh/ruff/preview), this rule also\ntriggers on unused unpacked assignments (for example, `x, y = foo()`).\n\n## Example\n```python\ndef foo():\n x = 1\n y = 2\n return x\n```\n\nUse instead:\n```python\ndef foo():\n x = 1\n return x\n```\n\n## Options\n- `lint.dummy-variable-rgx`\n"
"text": "## What it does\nChecks for the presence of unused variables in function scopes.\n\n## Why is this bad?\nA variable that is defined but not used is likely a mistake, and should\nbe removed to avoid confusion.\n\nIf a variable is intentionally defined-but-not-used, it should be\nprefixed with an underscore, or some other value that adheres to the\n[`lint.dummy-variable-rgx`] pattern.\n\n## Example\n```python\ndef foo():\n x = 1\n y = 2\n return x\n```\n\nUse instead:\n```python\ndef foo():\n x = 1\n return x\n```\n\n## Options\n- `lint.dummy-variable-rgx`\n"
},
"help": {
"text": "Local variable `{name}` is assigned to but never used"

View file

@ -178,7 +178,6 @@ 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<()> {
@ -226,7 +225,6 @@ mod tests {
assert_messages!(diagnostics);
}
#[test_case(Rule::UnusedVariable, Path::new("F841_4.py"))]
#[test_case(Rule::UnusedImport, Path::new("__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_24/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_25__all_nonempty/__init__.py"))]

View file

@ -6,7 +6,7 @@ use ruff_python_ast::helpers::contains_effect;
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::{self as ast, Stmt};
use ruff_python_parser::{TokenKind, Tokens};
use ruff_python_semantic::{Binding, Scope};
use ruff_python_semantic::Binding;
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::checkers::ast::Checker;
@ -23,9 +23,6 @@ use crate::fix::edits::delete_stmt;
/// prefixed with an underscore, or some other value that adheres to the
/// [`lint.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():
@ -249,38 +246,11 @@ fn remove_unused_variable(binding: &Binding, checker: &Checker) -> Option<Fix> {
}
/// F841
pub(crate) fn unused_variable(checker: &Checker, scope: &Scope) {
if scope.uses_locals() && scope.kind.is_function() {
pub(crate) fn unused_variable(checker: &Checker, name: &str, binding: &Binding) {
if binding.is_unpacked_assignment() {
return;
}
for (name, binding) in scope
.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()
|| binding.kind.is_with_item_var())
// Stabilization depends on resolving https://github.com/astral-sh/ruff/issues/8884
&& (!binding.is_unpacked_assignment() || checker.settings.preview.is_enabled())
&& binding.is_unused()
&& !binding.is_nonlocal()
&& !binding.is_global()
&& !checker.settings.dummy_variable_rgx.is_match(name)
&& !matches!(
name,
"__tracebackhide__"
| "__traceback_info__"
| "__traceback_supplement__"
| "__debuggerskip__"
)
{
return Some((name, binding));
}
None
})
{
let mut diagnostic = Diagnostic::new(
UnusedVariable {
name: name.to_string(),
@ -291,5 +261,4 @@ pub(crate) fn unused_variable(checker: &Checker, scope: &Scope) {
diagnostic.set_fix(fix);
}
checker.report_diagnostic(diagnostic);
}
}

View file

@ -1,22 +0,0 @@
---
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
snapshot_kind: text
---
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`
Unsafe fix
9 9 |
10 10 |
11 11 | def bar():
12 |- a = foo()
12 |+ foo()
13 13 | b, c = foo()
14 14 |
15 15 |

View file

@ -1,60 +0,0 @@
---
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
snapshot_kind: text
---
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`
Unsafe 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`
Unsafe fix
10 10 |
11 11 | def bar():
12 12 | a = foo()
13 |- b, c = foo()
13 |+ _b, c = foo()
14 14 |
15 15 |
16 16 | def baz():
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`
Unsafe fix
10 10 |
11 11 | def bar():
12 12 | a = foo()
13 |- b, c = foo()
13 |+ b, _c = foo()
14 14 |
15 15 |
16 16 | def baz():

View file

@ -90,6 +90,10 @@ mod tests {
#[test_case(Rule::IfKeyInDictDel, Path::new("RUF051.py"))]
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"))]
#[test_case(Rule::FalsyDictGetFallback, Path::new("RUF056.py"))]
#[test_case(Rule::UnusedUnpackedVariable, Path::new("RUF059_0.py"))]
#[test_case(Rule::UnusedUnpackedVariable, Path::new("RUF059_1.py"))]
#[test_case(Rule::UnusedUnpackedVariable, Path::new("RUF059_2.py"))]
#[test_case(Rule::UnusedUnpackedVariable, Path::new("RUF059_3.py"))]
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101_0.py"))]
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101_1.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {

View file

@ -50,6 +50,7 @@ pub(crate) use unraw_re_pattern::*;
pub(crate) use unsafe_markup_use::*;
pub(crate) use unused_async::*;
pub(crate) use unused_noqa::*;
pub(crate) use unused_unpacked_variable::*;
pub(crate) use used_dummy_variable::*;
pub(crate) use useless_if_else::*;
pub(crate) use zip_instead_of_pairwise::*;
@ -110,6 +111,7 @@ mod unraw_re_pattern;
mod unsafe_markup_use;
mod unused_async;
mod unused_noqa;
mod unused_unpacked_variable;
mod used_dummy_variable;
mod useless_if_else;
mod zip_instead_of_pairwise;

View file

@ -0,0 +1,91 @@
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_semantic::Binding;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for the presence of unused variables in unpacked assignments.
///
/// ## Why is this bad?
/// A variable that is defined but never used can confuse readers.
///
/// If a variable is intentionally defined-but-not-used, it should be
/// prefixed with an underscore, or some other value that adheres to the
/// [`lint.dummy-variable-rgx`] pattern.
///
/// ## Example
///
/// ```python
/// def get_pair():
/// return 1, 2
///
///
/// def foo():
/// x, y = get_pair()
/// return x
/// ```
///
/// Use instead:
///
/// ```python
/// def foo():
/// x, _ = get_pair()
/// return x
/// ```
///
/// ## Options
/// - `lint.dummy-variable-rgx`
#[derive(ViolationMetadata)]
pub(crate) struct UnusedUnpackedVariable {
pub name: String,
}
impl Violation for UnusedUnpackedVariable {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
let UnusedUnpackedVariable { name } = self;
format!("Unpacked variable `{name}` is never used")
}
fn fix_title(&self) -> Option<String> {
Some("Prefix it with an underscore or any other dummy variable pattern".to_string())
}
}
/// Generate a [`Edit`] to remove an unused variable assignment to a [`Binding`].
fn remove_unused_variable(binding: &Binding, checker: &Checker) -> Option<Fix> {
let node_id = binding.source?;
let isolation = Checker::isolation(checker.semantic().parent_statement_id(node_id));
let name = binding.name(checker.source());
let renamed = format!("_{name}");
if checker.settings.dummy_variable_rgx.is_match(&renamed) {
let edit = Edit::range_replacement(renamed, binding.range());
return Some(Fix::unsafe_edit(edit).isolate(isolation));
}
None
}
/// RUF059
pub(crate) fn unused_unpacked_variable(checker: &Checker, name: &str, binding: &Binding) {
if !binding.is_unpacked_assignment() {
return;
}
let mut diagnostic = Diagnostic::new(
UnusedUnpackedVariable {
name: name.to_string(),
},
binding.range(),
);
if let Some(fix) = remove_unused_variable(binding, checker) {
diagnostic.set_fix(fix);
}
checker.report_diagnostic(diagnostic);
}

View file

@ -0,0 +1,200 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---
RUF059_0.py:24:6: RUF059 [*] Unpacked variable `c` is never used
|
23 | bar = (1, 2)
24 | (c, d) = bar
| ^ RUF059
25 |
26 | (x, y) = baz = bar
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
21 21 | (a, b) = (1, 2)
22 22 |
23 23 | bar = (1, 2)
24 |- (c, d) = bar
24 |+ (_c, d) = bar
25 25 |
26 26 | (x, y) = baz = bar
27 27 |
RUF059_0.py:24:9: RUF059 [*] Unpacked variable `d` is never used
|
23 | bar = (1, 2)
24 | (c, d) = bar
| ^ RUF059
25 |
26 | (x, y) = baz = bar
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
21 21 | (a, b) = (1, 2)
22 22 |
23 23 | bar = (1, 2)
24 |- (c, d) = bar
24 |+ (c, _d) = bar
25 25 |
26 26 | (x, y) = baz = bar
27 27 |
RUF059_0.py:26:6: RUF059 [*] Unpacked variable `x` is never used
|
24 | (c, d) = bar
25 |
26 | (x, y) = baz = bar
| ^ RUF059
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
23 23 | bar = (1, 2)
24 24 | (c, d) = bar
25 25 |
26 |- (x, y) = baz = bar
26 |+ (_x, y) = baz = bar
27 27 |
28 28 |
29 29 | def f():
RUF059_0.py:26:9: RUF059 [*] Unpacked variable `y` is never used
|
24 | (c, d) = bar
25 |
26 | (x, y) = baz = bar
| ^ RUF059
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
23 23 | bar = (1, 2)
24 24 | (c, d) = bar
25 25 |
26 |- (x, y) = baz = bar
26 |+ (x, _y) = baz = bar
27 27 |
28 28 |
29 29 | def f():
RUF059_0.py:66:24: RUF059 [*] Unpacked variable `connection` is never used
|
64 | return None, None
65 |
66 | with connect() as (connection, cursor):
| ^^^^^^^^^^ RUF059
67 | cursor.execute("SELECT * FROM users")
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
63 63 | def connect():
64 64 | return None, None
65 65 |
66 |- with connect() as (connection, cursor):
66 |+ with connect() as (_connection, cursor):
67 67 | cursor.execute("SELECT * FROM users")
68 68 |
69 69 |
RUF059_0.py:74:24: RUF059 [*] Unpacked variable `connection` is never used
|
72 | return None, None
73 |
74 | with connect() as (connection, cursor):
| ^^^^^^^^^^ RUF059
75 | cursor.execute("SELECT * FROM users")
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
71 71 | def connect():
72 72 | return None, None
73 73 |
74 |- with connect() as (connection, cursor):
74 |+ with connect() as (_connection, cursor):
75 75 | cursor.execute("SELECT * FROM users")
76 76 |
77 77 |
RUF059_0.py:79:49: RUF059 [*] Unpacked variable `this` is never used
|
78 | def f():
79 | with open("file") as my_file, open("") as ((this, that)):
| ^^^^ RUF059
80 | print("hello")
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
76 76 |
77 77 |
78 78 | def f():
79 |- with open("file") as my_file, open("") as ((this, that)):
79 |+ with open("file") as my_file, open("") as ((_this, that)):
80 80 | print("hello")
81 81 |
82 82 |
RUF059_0.py:79:55: RUF059 [*] Unpacked variable `that` is never used
|
78 | def f():
79 | with open("file") as my_file, open("") as ((this, that)):
| ^^^^ RUF059
80 | print("hello")
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
76 76 |
77 77 |
78 78 | def f():
79 |- with open("file") as my_file, open("") as ((this, that)):
79 |+ with open("file") as my_file, open("") as ((this, _that)):
80 80 | print("hello")
81 81 |
82 82 |
RUF059_0.py:86:23: RUF059 [*] Unpacked variable `this` is never used
|
84 | with (
85 | open("file") as my_file,
86 | open("") as ((this, that)),
| ^^^^ RUF059
87 | ):
88 | print("hello")
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
83 83 | def f():
84 84 | with (
85 85 | open("file") as my_file,
86 |- open("") as ((this, that)),
86 |+ open("") as ((_this, that)),
87 87 | ):
88 88 | print("hello")
89 89 |
RUF059_0.py:86:29: RUF059 [*] Unpacked variable `that` is never used
|
84 | with (
85 | open("file") as my_file,
86 | open("") as ((this, that)),
| ^^^^ RUF059
87 | ):
88 | print("hello")
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
83 83 | def f():
84 84 | with (
85 85 | open("file") as my_file,
86 |- open("") as ((this, that)),
86 |+ open("") as ((this, _that)),
87 87 | ):
88 88 | print("hello")
89 89 |

View file

@ -0,0 +1,126 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---
RUF059_1.py:2:5: RUF059 [*] Unpacked variable `x` is never used
|
1 | def f(tup):
2 | x, y = tup
| ^ RUF059
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
1 1 | def f(tup):
2 |- x, y = tup
2 |+ _x, y = tup
3 3 |
4 4 |
5 5 | def f():
RUF059_1.py:2:8: RUF059 [*] Unpacked variable `y` is never used
|
1 | def f(tup):
2 | x, y = tup
| ^ RUF059
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
1 1 | def f(tup):
2 |- x, y = tup
2 |+ x, _y = tup
3 3 |
4 4 |
5 5 | def f():
RUF059_1.py:10:9: RUF059 [*] Unpacked variable `y` is never used
|
9 | def f():
10 | (x, y) = coords = 1, 2
| ^ RUF059
11 | if x > 1:
12 | print(coords)
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
7 7 |
8 8 |
9 9 | def f():
10 |- (x, y) = coords = 1, 2
10 |+ (x, _y) = coords = 1, 2
11 11 | if x > 1:
12 12 | print(coords)
13 13 |
RUF059_1.py:16:6: RUF059 [*] Unpacked variable `x` is never used
|
15 | def f():
16 | (x, y) = coords = 1, 2
| ^ RUF059
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
13 13 |
14 14 |
15 15 | def f():
16 |- (x, y) = coords = 1, 2
16 |+ (_x, y) = coords = 1, 2
17 17 |
18 18 |
19 19 | def f():
RUF059_1.py:16:9: RUF059 [*] Unpacked variable `y` is never used
|
15 | def f():
16 | (x, y) = coords = 1, 2
| ^ RUF059
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
13 13 |
14 14 |
15 15 | def f():
16 |- (x, y) = coords = 1, 2
16 |+ (x, _y) = coords = 1, 2
17 17 |
18 18 |
19 19 | def f():
RUF059_1.py:20:15: RUF059 [*] Unpacked variable `x` is never used
|
19 | def f():
20 | coords = (x, y) = 1, 2
| ^ RUF059
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
17 17 |
18 18 |
19 19 | def f():
20 |- coords = (x, y) = 1, 2
20 |+ coords = (_x, y) = 1, 2
21 21 |
22 22 |
23 23 | def f():
RUF059_1.py:20:18: RUF059 [*] Unpacked variable `y` is never used
|
19 | def f():
20 | coords = (x, y) = 1, 2
| ^ RUF059
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
17 17 |
18 18 |
19 19 | def f():
20 |- coords = (x, y) = 1, 2
20 |+ coords = (x, _y) = 1, 2
21 21 |
22 22 |
23 23 | def f():

View file

@ -0,0 +1,224 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---
RUF059_2.py:8:20: RUF059 [*] Unpacked variable `x2` is never used
|
6 | pass
7 |
8 | with foo() as (x2, y2):
| ^^ RUF059
9 | pass
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
5 5 | with foo() as x1:
6 6 | pass
7 7 |
8 |- with foo() as (x2, y2):
8 |+ with foo() as (_x2, y2):
9 9 | pass
10 10 |
11 11 | with (foo() as x3, foo() as y3, foo() as z3):
RUF059_2.py:8:24: RUF059 [*] Unpacked variable `y2` is never used
|
6 | pass
7 |
8 | with foo() as (x2, y2):
| ^^ RUF059
9 | pass
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
5 5 | with foo() as x1:
6 6 | pass
7 7 |
8 |- with foo() as (x2, y2):
8 |+ with foo() as (x2, _y2):
9 9 | pass
10 10 |
11 11 | with (foo() as x3, foo() as y3, foo() as z3):
RUF059_2.py:17:6: RUF059 [*] Unpacked variable `x2` is never used
|
15 | def f():
16 | (x1, y1) = (1, 2)
17 | (x2, y2) = coords2 = (1, 2)
| ^^ RUF059
18 | coords3 = (x3, y3) = (1, 2)
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
14 14 |
15 15 | def f():
16 16 | (x1, y1) = (1, 2)
17 |- (x2, y2) = coords2 = (1, 2)
17 |+ (_x2, y2) = coords2 = (1, 2)
18 18 | coords3 = (x3, y3) = (1, 2)
19 19 |
20 20 |
RUF059_2.py:17:10: RUF059 [*] Unpacked variable `y2` is never used
|
15 | def f():
16 | (x1, y1) = (1, 2)
17 | (x2, y2) = coords2 = (1, 2)
| ^^ RUF059
18 | coords3 = (x3, y3) = (1, 2)
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
14 14 |
15 15 | def f():
16 16 | (x1, y1) = (1, 2)
17 |- (x2, y2) = coords2 = (1, 2)
17 |+ (x2, _y2) = coords2 = (1, 2)
18 18 | coords3 = (x3, y3) = (1, 2)
19 19 |
20 20 |
RUF059_2.py:18:16: RUF059 [*] Unpacked variable `x3` is never used
|
16 | (x1, y1) = (1, 2)
17 | (x2, y2) = coords2 = (1, 2)
18 | coords3 = (x3, y3) = (1, 2)
| ^^ RUF059
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
15 15 | def f():
16 16 | (x1, y1) = (1, 2)
17 17 | (x2, y2) = coords2 = (1, 2)
18 |- coords3 = (x3, y3) = (1, 2)
18 |+ coords3 = (_x3, y3) = (1, 2)
19 19 |
20 20 |
21 21 | def f():
RUF059_2.py:18:20: RUF059 [*] Unpacked variable `y3` is never used
|
16 | (x1, y1) = (1, 2)
17 | (x2, y2) = coords2 = (1, 2)
18 | coords3 = (x3, y3) = (1, 2)
| ^^ RUF059
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
15 15 | def f():
16 16 | (x1, y1) = (1, 2)
17 17 | (x2, y2) = coords2 = (1, 2)
18 |- coords3 = (x3, y3) = (1, 2)
18 |+ coords3 = (x3, _y3) = (1, 2)
19 19 |
20 20 |
21 21 | def f():
RUF059_2.py:22:24: RUF059 [*] Unpacked variable `x` is never used
|
21 | def f():
22 | with Nested(m) as (x, y):
| ^ RUF059
23 | pass
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
19 19 |
20 20 |
21 21 | def f():
22 |- with Nested(m) as (x, y):
22 |+ with Nested(m) as (_x, y):
23 23 | pass
24 24 |
25 25 |
RUF059_2.py:22:27: RUF059 [*] Unpacked variable `y` is never used
|
21 | def f():
22 | with Nested(m) as (x, y):
| ^ RUF059
23 | pass
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
19 19 |
20 20 |
21 21 | def f():
22 |- with Nested(m) as (x, y):
22 |+ with Nested(m) as (x, _y):
23 23 | pass
24 24 |
25 25 |
RUF059_2.py:27:17: RUF059 [*] Unpacked variable `a` is never used
|
26 | def f():
27 | toplevel = (a, b) = lexer.get_token()
| ^ RUF059
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
24 24 |
25 25 |
26 26 | def f():
27 |- toplevel = (a, b) = lexer.get_token()
27 |+ toplevel = (_a, b) = lexer.get_token()
28 28 |
29 29 |
30 30 | def f():
RUF059_2.py:27:20: RUF059 [*] Unpacked variable `b` is never used
|
26 | def f():
27 | toplevel = (a, b) = lexer.get_token()
| ^ RUF059
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
24 24 |
25 25 |
26 26 | def f():
27 |- toplevel = (a, b) = lexer.get_token()
27 |+ toplevel = (a, _b) = lexer.get_token()
28 28 |
29 29 |
30 30 | def f():
RUF059_2.py:31:6: RUF059 [*] Unpacked variable `a` is never used
|
30 | def f():
31 | (a, b) = toplevel = lexer.get_token()
| ^ RUF059
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
28 28 |
29 29 |
30 30 | def f():
31 |- (a, b) = toplevel = lexer.get_token()
31 |+ (_a, b) = toplevel = lexer.get_token()
RUF059_2.py:31:9: RUF059 [*] Unpacked variable `b` is never used
|
30 | def f():
31 | (a, b) = toplevel = lexer.get_token()
| ^ RUF059
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
28 28 |
29 29 |
30 30 | def f():
31 |- (a, b) = toplevel = lexer.get_token()
31 |+ (a, _b) = toplevel = lexer.get_token()

View file

@ -0,0 +1,40 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---
RUF059_3.py:13:5: RUF059 [*] Unpacked variable `b` is never used
|
11 | def bar():
12 | a = foo()
13 | b, c = foo()
| ^ RUF059
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
10 10 |
11 11 | def bar():
12 12 | a = foo()
13 |- b, c = foo()
13 |+ _b, c = foo()
14 14 |
15 15 |
16 16 | def baz():
RUF059_3.py:13:8: RUF059 [*] Unpacked variable `c` is never used
|
11 | def bar():
12 | a = foo()
13 | b, c = foo()
| ^ RUF059
|
= help: Prefix it with an underscore or any other dummy variable pattern
Unsafe fix
10 10 |
11 11 | def bar():
12 12 | a = foo()
13 |- b, c = foo()
13 |+ b, _c = foo()
14 14 |
15 15 |
16 16 | def baz():

View file

@ -445,7 +445,8 @@ fn tags(code: &str) -> Option<Vec<lsp_types::DiagnosticTag>> {
match code {
// F401: <module> imported but unused
// F841: local variable <name> is assigned to but never used
"F401" | "F841" => Some(vec![lsp_types::DiagnosticTag::UNNECESSARY]),
// RUF059: Unused unpacked variable
"F401" | "F841" | "RUF059" => Some(vec![lsp_types::DiagnosticTag::UNNECESSARY]),
_ => None,
}
}

1
ruff.schema.json generated
View file

@ -3972,6 +3972,7 @@
"RUF056",
"RUF057",
"RUF058",
"RUF059",
"RUF1",
"RUF10",
"RUF100",