Avoid renaming unused loop variables with deferred usages (#2509)

This commit is contained in:
Charlie Marsh 2023-02-02 20:59:47 -05:00 committed by GitHub
parent 9c55ab35df
commit a074625121
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 161 additions and 59 deletions

View File

@ -44,4 +44,29 @@ for foo, bar in [(1, 2)]:
print(FMT.format(**vars())) print(FMT.format(**vars()))
for foo, bar in [(1, 2)]: for foo, bar in [(1, 2)]:
print(FMT.format(foo=foo, bar=eval('bar'))) print(FMT.format(foo=foo, bar=eval("bar")))
def f():
# Fixable.
for foo, bar, baz in (["1", "2", "3"],):
if foo or baz:
break
def f():
# Unfixable due to usage of `bar` outside of loop.
for foo, bar, baz in (["1", "2", "3"],):
if foo or baz:
break
print(bar)
def f():
# Fixable, but not fixed in practice, since we can't tell if `bar` is used.
for foo, bar, baz in (["1", "2", "3"],):
if foo or baz:
break
bar = 1

View File

@ -84,6 +84,7 @@ pub struct Checker<'a> {
deferred_type_definitions: Vec<(&'a Expr, bool, DeferralContext<'a>)>, deferred_type_definitions: Vec<(&'a Expr, bool, DeferralContext<'a>)>,
deferred_functions: Vec<(&'a Stmt, DeferralContext<'a>, VisibleScope)>, deferred_functions: Vec<(&'a Stmt, DeferralContext<'a>, VisibleScope)>,
deferred_lambdas: Vec<(&'a Expr, DeferralContext<'a>)>, deferred_lambdas: Vec<(&'a Expr, DeferralContext<'a>)>,
deferred_for_loops: Vec<(&'a Stmt, DeferralContext<'a>)>,
deferred_assignments: Vec<DeferralContext<'a>>, deferred_assignments: Vec<DeferralContext<'a>>,
// Body iteration; used to peek at siblings. // Body iteration; used to peek at siblings.
body: &'a [Stmt], body: &'a [Stmt],
@ -145,6 +146,7 @@ impl<'a> Checker<'a> {
deferred_type_definitions: vec![], deferred_type_definitions: vec![],
deferred_functions: vec![], deferred_functions: vec![],
deferred_lambdas: vec![], deferred_lambdas: vec![],
deferred_for_loops: vec![],
deferred_assignments: vec![], deferred_assignments: vec![],
// Body iteration. // Body iteration.
body: &[], body: &[],
@ -1557,7 +1559,8 @@ where
.rules .rules
.enabled(&Rule::UnusedLoopControlVariable) .enabled(&Rule::UnusedLoopControlVariable)
{ {
flake8_bugbear::rules::unused_loop_control_variable(self, target, body); self.deferred_for_loops
.push((stmt, (self.scope_stack.clone(), self.parents.clone())));
} }
if self if self
.settings .settings
@ -4421,6 +4424,28 @@ impl<'a> Checker<'a> {
} }
} }
fn check_deferred_for_loops(&mut self) {
self.deferred_for_loops.reverse();
while let Some((stmt, (scopes, parents))) = self.deferred_for_loops.pop() {
self.scope_stack = scopes.clone();
self.parents = parents.clone();
if let StmtKind::For { target, body, .. } | StmtKind::AsyncFor { target, body, .. } =
&stmt.node
{
if self
.settings
.rules
.enabled(&Rule::UnusedLoopControlVariable)
{
flake8_bugbear::rules::unused_loop_control_variable(self, stmt, target, body);
}
} else {
unreachable!("Expected ExprKind::Lambda");
}
}
}
fn check_dead_scopes(&mut self) { fn check_dead_scopes(&mut self) {
if !(self.settings.rules.enabled(&Rule::UnusedImport) if !(self.settings.rules.enabled(&Rule::UnusedImport)
|| self.settings.rules.enabled(&Rule::ImportStarUsage) || self.settings.rules.enabled(&Rule::ImportStarUsage)
@ -5201,6 +5226,7 @@ pub fn check_ast(
checker.check_deferred_type_definitions(); checker.check_deferred_type_definitions();
let mut allocator = vec![]; let mut allocator = vec![];
checker.check_deferred_string_type_definitions(&mut allocator); checker.check_deferred_string_type_definitions(&mut allocator);
checker.check_deferred_for_loops();
// Check docstrings. // Check docstrings.
checker.check_definitions(); checker.check_definitions();

View File

@ -18,7 +18,13 @@
//! method() //! method()
//! ``` //! ```
use crate::ast::types::Range; use rustc_hash::FxHashMap;
use rustpython_ast::{Expr, ExprKind, Stmt};
use serde::{Deserialize, Serialize};
use ruff_macros::derive_message_formats;
use crate::ast::types::{BindingKind, Range, RefEquality};
use crate::ast::visitor::Visitor; use crate::ast::visitor::Visitor;
use crate::ast::{helpers, visitor}; use crate::ast::{helpers, visitor};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
@ -26,18 +32,21 @@ use crate::define_violation;
use crate::fix::Fix; use crate::fix::Fix;
use crate::registry::Diagnostic; use crate::registry::Diagnostic;
use crate::violation::{AutofixKind, Availability, Violation}; use crate::violation::{AutofixKind, Availability, Violation};
use ruff_macros::derive_message_formats;
use rustc_hash::FxHashMap; #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
use rustpython_ast::{Expr, ExprKind, Stmt}; pub enum Certainty {
Certain,
Uncertain,
}
define_violation!( define_violation!(
pub struct UnusedLoopControlVariable { pub struct UnusedLoopControlVariable {
/// The name of the loop control variable. /// The name of the loop control variable.
pub name: String, pub name: String,
/// Whether the variable is safe to rename. A variable is unsafe to /// Whether the variable is certain to be unused, or merely suspect.
/// rename if it _might_ be used by magic control flow (e.g., /// A variable _may_ be used, but undetectably so, if the loop incorporates
/// `locals()`). /// by magic control flow (e.g., `locals()`).
pub safe: bool, pub certainty: Certainty,
} }
); );
impl Violation for UnusedLoopControlVariable { impl Violation for UnusedLoopControlVariable {
@ -45,8 +54,8 @@ impl Violation for UnusedLoopControlVariable {
#[derive_message_formats] #[derive_message_formats]
fn message(&self) -> String { fn message(&self) -> String {
let UnusedLoopControlVariable { name, safe } = self; let UnusedLoopControlVariable { name, certainty } = self;
if *safe { if matches!(certainty, Certainty::Certain) {
format!("Loop control variable `{name}` not used within loop body") format!("Loop control variable `{name}` not used within loop body")
} else { } else {
format!("Loop control variable `{name}` may not be used within loop body") format!("Loop control variable `{name}` may not be used within loop body")
@ -54,8 +63,8 @@ impl Violation for UnusedLoopControlVariable {
} }
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> { fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
let UnusedLoopControlVariable { safe, .. } = self; let UnusedLoopControlVariable { certainty, .. } = self;
if *safe { if matches!(certainty, Certainty::Certain) {
Some(|UnusedLoopControlVariable { name, .. }| { Some(|UnusedLoopControlVariable { name, .. }| {
format!("Rename unused `{name}` to `_{name}`") format!("Rename unused `{name}` to `_{name}`")
}) })
@ -92,7 +101,12 @@ where
} }
/// B007 /// B007
pub fn unused_loop_control_variable(checker: &mut Checker, target: &Expr, body: &[Stmt]) { pub fn unused_loop_control_variable(
checker: &mut Checker,
stmt: &Stmt,
target: &Expr,
body: &[Stmt],
) {
let control_names = { let control_names = {
let mut finder = NameFinder::new(); let mut finder = NameFinder::new();
finder.visit_expr(target); finder.visit_expr(target);
@ -118,21 +132,38 @@ pub fn unused_loop_control_variable(checker: &mut Checker, target: &Expr, body:
continue; continue;
} }
let safe = !helpers::uses_magic_variable_access(checker, body); let certainty = if helpers::uses_magic_variable_access(checker, body) {
Certainty::Uncertain
} else {
Certainty::Certain
};
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
UnusedLoopControlVariable { UnusedLoopControlVariable {
name: name.to_string(), name: name.to_string(),
safe, certainty,
}, },
Range::from_located(expr), Range::from_located(expr),
); );
if safe && checker.patch(diagnostic.kind.rule()) { if matches!(certainty, Certainty::Certain) && checker.patch(diagnostic.kind.rule()) {
// Prefix the variable name with an underscore. // Guard against cases in which the loop variable is used later on in the scope.
diagnostic.amend(Fix::replacement( // TODO(charlie): This avoids fixing cases in which the loop variable is overridden (but
format!("_{name}"), // unused) later on in the scope.
expr.location, if let Some(binding) = checker.find_binding(name) {
expr.end_location.unwrap(), if matches!(binding.kind, BindingKind::LoopVar) {
)); if !binding.used() {
if let Some(source) = &binding.source {
if source == &RefEquality(stmt) {
// Prefix the variable name with an underscore.
diagnostic.amend(Fix::replacement(
format!("_{name}"),
expr.location,
expr.end_location.unwrap(),
));
}
}
}
}
}
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View File

@ -5,67 +5,43 @@ expression: diagnostics
- kind: - kind:
UnusedLoopControlVariable: UnusedLoopControlVariable:
name: i name: i
safe: true certainty: Certain
location: location:
row: 6 row: 6
column: 4 column: 4
end_location: end_location:
row: 6 row: 6
column: 5 column: 5
fix: fix: ~
content:
- _i
location:
row: 6
column: 4
end_location:
row: 6
column: 5
parent: ~ parent: ~
- kind: - kind:
UnusedLoopControlVariable: UnusedLoopControlVariable:
name: k name: k
safe: true certainty: Certain
location: location:
row: 18 row: 18
column: 12 column: 12
end_location: end_location:
row: 18 row: 18
column: 13 column: 13
fix: fix: ~
content:
- _k
location:
row: 18
column: 12
end_location:
row: 18
column: 13
parent: ~ parent: ~
- kind: - kind:
UnusedLoopControlVariable: UnusedLoopControlVariable:
name: i name: i
safe: true certainty: Certain
location: location:
row: 30 row: 30
column: 4 column: 4
end_location: end_location:
row: 30 row: 30
column: 5 column: 5
fix: fix: ~
content:
- _i
location:
row: 30
column: 4
end_location:
row: 30
column: 5
parent: ~ parent: ~
- kind: - kind:
UnusedLoopControlVariable: UnusedLoopControlVariable:
name: k name: k
safe: true certainty: Certain
location: location:
row: 30 row: 30
column: 12 column: 12
@ -85,7 +61,7 @@ expression: diagnostics
- kind: - kind:
UnusedLoopControlVariable: UnusedLoopControlVariable:
name: bar name: bar
safe: false certainty: Uncertain
location: location:
row: 34 row: 34
column: 9 column: 9
@ -97,7 +73,7 @@ expression: diagnostics
- kind: - kind:
UnusedLoopControlVariable: UnusedLoopControlVariable:
name: bar name: bar
safe: false certainty: Uncertain
location: location:
row: 38 row: 38
column: 9 column: 9
@ -109,7 +85,7 @@ expression: diagnostics
- kind: - kind:
UnusedLoopControlVariable: UnusedLoopControlVariable:
name: bar name: bar
safe: false certainty: Uncertain
location: location:
row: 42 row: 42
column: 9 column: 9
@ -121,7 +97,7 @@ expression: diagnostics
- kind: - kind:
UnusedLoopControlVariable: UnusedLoopControlVariable:
name: bar name: bar
safe: false certainty: Uncertain
location: location:
row: 46 row: 46
column: 9 column: 9
@ -130,4 +106,48 @@ expression: diagnostics
column: 12 column: 12
fix: ~ fix: ~
parent: ~ parent: ~
- kind:
UnusedLoopControlVariable:
name: bar
certainty: Certain
location:
row: 52
column: 13
end_location:
row: 52
column: 16
fix:
content:
- _bar
location:
row: 52
column: 13
end_location:
row: 52
column: 16
parent: ~
- kind:
UnusedLoopControlVariable:
name: bar
certainty: Certain
location:
row: 59
column: 13
end_location:
row: 59
column: 16
fix: ~
parent: ~
- kind:
UnusedLoopControlVariable:
name: bar
certainty: Certain
location:
row: 68
column: 13
end_location:
row: 68
column: 16
fix: ~
parent: ~