diff --git a/crates/ruff/resources/test/fixtures/flake8_bugbear/B007.py b/crates/ruff/resources/test/fixtures/flake8_bugbear/B007.py index 6df22e0027c0d..6c855a7989c5c 100644 --- a/crates/ruff/resources/test/fixtures/flake8_bugbear/B007.py +++ b/crates/ruff/resources/test/fixtures/flake8_bugbear/B007.py @@ -73,7 +73,18 @@ def f(): def f(): - # Fixable. + # Unfixable. + for foo, bar, baz in (["1", "2", "3"],): + if foo or baz: + break + else: + bar = 1 + + print(bar) + + +def f(): + # Unfixable (false negative) due to usage of `bar` outside of loop. for foo, bar, baz in (["1", "2", "3"],): if foo or baz: break @@ -85,4 +96,4 @@ def f(): # Unfixable due to trailing underscore (`_line_` wouldn't be considered an ignorable # variable name). for line_ in range(self.header_lines): - fp.readline() + fp.readline() diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs b/crates/ruff/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs index 58df55f2f757d..d365b8cf5796c 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs @@ -152,24 +152,19 @@ pub(crate) fn unused_loop_control_variable(checker: &mut Checker, target: &Expr, ); if let Some(rename) = rename { if certainty.into() && checker.patch(diagnostic.kind.rule()) { - // Find the `BindingKind::LoopVar` corresponding to the name. + // Avoid fixing if the variable, or any future bindings to the variable, are + // used _after_ the loop. let scope = checker.semantic_model().scope(); - let binding = scope.bindings_for_name(name).find_map(|binding_id| { - let binding = &checker.semantic_model().bindings[*binding_id]; - binding.source.and_then(|source| { - (Some(source) == checker.semantic_model().stmt_id).then_some(binding) - }) - }); - if let Some(binding) = binding { - if binding.kind.is_loop_var() { - if !binding.used() { - #[allow(deprecated)] - diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( - rename, - expr.range(), - ))); - } - } + if scope + .bindings_for_name(name) + .map(|binding_id| &checker.semantic_model().bindings[*binding_id]) + .all(|binding| !binding.used()) + { + #[allow(deprecated)] + diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( + rename, + expr.range(), + ))); } } } diff --git a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B007_B007.py.snap b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B007_B007.py.snap index bb9c28d9e3c9c..f263105c6083e 100644 --- a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B007_B007.py.snap +++ b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B007_B007.py.snap @@ -152,10 +152,10 @@ B007.py:68:14: B007 [*] Loop control variable `bar` not used within loop body 70 70 | break 71 71 | -B007.py:77:14: B007 [*] Loop control variable `bar` not used within loop body +B007.py:77:14: B007 Loop control variable `bar` not used within loop body | 77 | def f(): -78 | # Fixable. +78 | # Unfixable. 79 | for foo, bar, baz in (["1", "2", "3"],): | ^^^ B007 80 | if foo or baz: @@ -163,23 +163,24 @@ B007.py:77:14: B007 [*] Loop control variable `bar` not used within loop body | = help: Rename unused `bar` to `_bar` -ℹ Suggested fix -74 74 | -75 75 | def f(): -76 76 | # Fixable. -77 |- for foo, bar, baz in (["1", "2", "3"],): - 77 |+ for foo, _bar, baz in (["1", "2", "3"],): -78 78 | if foo or baz: -79 79 | break -80 80 | - -B007.py:87:5: B007 Loop control variable `line_` not used within loop body - | -87 | # Unfixable due to trailing underscore (`_line_` wouldn't be considered an ignorable -88 | # variable name). -89 | for line_ in range(self.header_lines): - | ^^^^^ B007 -90 | fp.readline() +B007.py:88:14: B007 Loop control variable `bar` not used within loop body + | +88 | def f(): +89 | # Unfixable (false negative) due to usage of `bar` outside of loop. +90 | for foo, bar, baz in (["1", "2", "3"],): + | ^^^ B007 +91 | if foo or baz: +92 | break | + = help: Rename unused `bar` to `_bar` + +B007.py:98:5: B007 Loop control variable `line_` not used within loop body + | + 98 | # Unfixable due to trailing underscore (`_line_` wouldn't be considered an ignorable + 99 | # variable name). +100 | for line_ in range(self.header_lines): + | ^^^^^ B007 +101 | fp.readline() + |