mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 22:01:18 +00:00
[pylint] redefined-loop-name (W2901) (#3022)
Slightly broadens W2901 to cover `with` statements too. Closes #2972.
This commit is contained in:
parent
9645790a8b
commit
97338e4cd6
11 changed files with 809 additions and 5 deletions
155
crates/ruff/resources/test/fixtures/pylint/redefined_loop_name.py
vendored
Normal file
155
crates/ruff/resources/test/fixtures/pylint/redefined_loop_name.py
vendored
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
# For -> for, variable reused
|
||||||
|
for i in []:
|
||||||
|
for i in []: # error
|
||||||
|
pass
|
||||||
|
|
||||||
|
# With -> for, variable reused
|
||||||
|
with None as i:
|
||||||
|
for i in []: # error
|
||||||
|
pass
|
||||||
|
|
||||||
|
# For -> with, variable reused
|
||||||
|
for i in []:
|
||||||
|
with None as i: # error
|
||||||
|
pass
|
||||||
|
|
||||||
|
# With -> with, variable reused
|
||||||
|
with None as i:
|
||||||
|
with None as i: # error
|
||||||
|
pass
|
||||||
|
|
||||||
|
# For -> for, different variable
|
||||||
|
for i in []:
|
||||||
|
for j in []: # ok
|
||||||
|
pass
|
||||||
|
|
||||||
|
# With -> with, different variable
|
||||||
|
with None as i:
|
||||||
|
with None as j: # ok
|
||||||
|
pass
|
||||||
|
|
||||||
|
# For -> for -> for, doubly nested variable reuse
|
||||||
|
for i in []:
|
||||||
|
for j in []:
|
||||||
|
for i in []: # error
|
||||||
|
pass
|
||||||
|
|
||||||
|
# For -> for -> for -> for, doubly nested variable reuse x2
|
||||||
|
for i in []:
|
||||||
|
for j in []:
|
||||||
|
for i in []: # error
|
||||||
|
for j in []: # error
|
||||||
|
pass
|
||||||
|
|
||||||
|
# For -> assignment
|
||||||
|
for i in []:
|
||||||
|
i = 5 # error
|
||||||
|
|
||||||
|
# For -> augmented assignment
|
||||||
|
for i in []:
|
||||||
|
i += 5 # error
|
||||||
|
|
||||||
|
# For -> annotated assignment
|
||||||
|
for i in []:
|
||||||
|
i: int = 5 # error
|
||||||
|
|
||||||
|
# Async for -> for, variable reused
|
||||||
|
async for i in []:
|
||||||
|
for i in []: # error
|
||||||
|
pass
|
||||||
|
|
||||||
|
# For -> async for, variable reused
|
||||||
|
for i in []:
|
||||||
|
async for i in []: # error
|
||||||
|
pass
|
||||||
|
|
||||||
|
# For -> for, outer loop unpacks tuple
|
||||||
|
for i, j in enumerate([]):
|
||||||
|
for i in []: # error
|
||||||
|
pass
|
||||||
|
|
||||||
|
# For -> for, inner loop unpacks tuple
|
||||||
|
for i in []:
|
||||||
|
for i, j in enumerate([]): # error
|
||||||
|
pass
|
||||||
|
|
||||||
|
# For -> for, both loops unpack tuple
|
||||||
|
for (i, (j, k)) in []:
|
||||||
|
for i, j in enumerate([]): # two errors
|
||||||
|
pass
|
||||||
|
|
||||||
|
# For else -> for, variable reused in else
|
||||||
|
for i in []:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
for i in []: # no error
|
||||||
|
pass
|
||||||
|
|
||||||
|
# For -> for, ignore dummy variables
|
||||||
|
for _ in []:
|
||||||
|
for _ in []: # no error
|
||||||
|
pass
|
||||||
|
|
||||||
|
# For -> for, outer loop unpacks with asterisk
|
||||||
|
for i, *j in []:
|
||||||
|
for j in []: # error
|
||||||
|
pass
|
||||||
|
|
||||||
|
# For -> function definition
|
||||||
|
for i in []:
|
||||||
|
def f():
|
||||||
|
i = 2 # no error
|
||||||
|
|
||||||
|
# For -> class definition
|
||||||
|
for i in []:
|
||||||
|
class A:
|
||||||
|
i = 2 # no error
|
||||||
|
|
||||||
|
# For -> function definition -> for -> assignment
|
||||||
|
for i in []:
|
||||||
|
def f():
|
||||||
|
for i in []: # no error
|
||||||
|
i = 2 # error
|
||||||
|
|
||||||
|
# For -> class definition -> for -> for
|
||||||
|
for i in []:
|
||||||
|
class A:
|
||||||
|
for i in []: # no error
|
||||||
|
for i in []: # error
|
||||||
|
pass
|
||||||
|
|
||||||
|
# For -> use in assignment target without actual assignment; subscript
|
||||||
|
for i in []:
|
||||||
|
a[i] = 2 # no error
|
||||||
|
i[a] = 2 # no error
|
||||||
|
|
||||||
|
# For -> use in assignment target without actual assignment; attribute
|
||||||
|
for i in []:
|
||||||
|
a.i = 2 # no error
|
||||||
|
i.a = 2 # no error
|
||||||
|
|
||||||
|
# For target with subscript -> assignment
|
||||||
|
for a[0] in []:
|
||||||
|
a[0] = 2 # error
|
||||||
|
a[1] = 2 # no error
|
||||||
|
|
||||||
|
# For target with subscript -> assignment
|
||||||
|
for a['i'] in []:
|
||||||
|
a['i'] = 2 # error
|
||||||
|
a['j'] = 2 # no error
|
||||||
|
|
||||||
|
# For target with attribute -> assignment
|
||||||
|
for a.i in []:
|
||||||
|
a.i = 2 # error
|
||||||
|
a.j = 2 # no error
|
||||||
|
|
||||||
|
# For target with double nested attribute -> assignment
|
||||||
|
for a.i.j in []:
|
||||||
|
a.i.j = 2 # error
|
||||||
|
a.j.i = 2 # no error
|
||||||
|
|
||||||
|
# For target with attribute -> assignment with different spacing
|
||||||
|
for a.i in []:
|
||||||
|
a. i = 2 # error
|
||||||
|
for a. i in []:
|
||||||
|
a.i = 2 # error
|
|
@ -928,7 +928,10 @@ pub fn binding_range(binding: &Binding, locator: &Locator) -> Range {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the ranges of `Name` tokens within a specified node.
|
// Return the ranges of `Name` tokens within a specified node.
|
||||||
pub fn find_names<T>(located: &Located<T>, locator: &Locator) -> Vec<Range> {
|
pub fn find_names<'a, T, U>(
|
||||||
|
located: &'a Located<T, U>,
|
||||||
|
locator: &'a Locator,
|
||||||
|
) -> impl Iterator<Item = Range> + 'a {
|
||||||
let contents = locator.slice(&Range::from_located(located));
|
let contents = locator.slice(&Range::from_located(located));
|
||||||
lexer::make_tokenizer_located(contents, located.location)
|
lexer::make_tokenizer_located(contents, located.location)
|
||||||
.flatten()
|
.flatten()
|
||||||
|
@ -937,7 +940,6 @@ pub fn find_names<T>(located: &Located<T>, locator: &Locator) -> Vec<Range> {
|
||||||
location: start,
|
location: start,
|
||||||
end_location: end,
|
end_location: end,
|
||||||
})
|
})
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the `Range` of `name` in `Excepthandler`.
|
/// Return the `Range` of `name` in `Excepthandler`.
|
||||||
|
|
|
@ -29,7 +29,7 @@ impl Range {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_located<T>(located: &Located<T>) -> Self {
|
pub fn from_located<T, U>(located: &Located<T, U>) -> Self {
|
||||||
Range::new(located.location, located.end_location.unwrap())
|
Range::new(located.location, located.end_location.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -342,7 +342,7 @@ where
|
||||||
match &stmt.node {
|
match &stmt.node {
|
||||||
StmtKind::Global { names } => {
|
StmtKind::Global { names } => {
|
||||||
let scope_index = *self.scope_stack.last().expect("No current scope found");
|
let scope_index = *self.scope_stack.last().expect("No current scope found");
|
||||||
let ranges = helpers::find_names(stmt, self.locator);
|
let ranges: Vec<Range> = helpers::find_names(stmt, self.locator).collect();
|
||||||
if scope_index != GLOBAL_SCOPE_INDEX {
|
if scope_index != GLOBAL_SCOPE_INDEX {
|
||||||
// Add the binding to the current scope.
|
// Add the binding to the current scope.
|
||||||
let context = self.execution_context();
|
let context = self.execution_context();
|
||||||
|
@ -372,7 +372,7 @@ where
|
||||||
}
|
}
|
||||||
StmtKind::Nonlocal { names } => {
|
StmtKind::Nonlocal { names } => {
|
||||||
let scope_index = *self.scope_stack.last().expect("No current scope found");
|
let scope_index = *self.scope_stack.last().expect("No current scope found");
|
||||||
let ranges = helpers::find_names(stmt, self.locator);
|
let ranges: Vec<Range> = helpers::find_names(stmt, self.locator).collect();
|
||||||
if scope_index != GLOBAL_SCOPE_INDEX {
|
if scope_index != GLOBAL_SCOPE_INDEX {
|
||||||
let context = self.execution_context();
|
let context = self.execution_context();
|
||||||
let scope = &mut self.scopes[scope_index];
|
let scope = &mut self.scopes[scope_index];
|
||||||
|
@ -1651,6 +1651,9 @@ where
|
||||||
self.current_stmt_parent().map(Into::into),
|
self.current_stmt_parent().map(Into::into),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if self.settings.rules.enabled(&Rule::RedefinedLoopName) {
|
||||||
|
pylint::rules::redefined_loop_name(self, &Node::Stmt(stmt));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
StmtKind::While { body, orelse, .. } => {
|
StmtKind::While { body, orelse, .. } => {
|
||||||
if self.settings.rules.enabled(&Rule::FunctionUsesLoopVariable) {
|
if self.settings.rules.enabled(&Rule::FunctionUsesLoopVariable) {
|
||||||
|
@ -1695,6 +1698,9 @@ where
|
||||||
if self.settings.rules.enabled(&Rule::UselessElseOnLoop) {
|
if self.settings.rules.enabled(&Rule::UselessElseOnLoop) {
|
||||||
pylint::rules::useless_else_on_loop(self, stmt, body, orelse);
|
pylint::rules::useless_else_on_loop(self, stmt, body, orelse);
|
||||||
}
|
}
|
||||||
|
if self.settings.rules.enabled(&Rule::RedefinedLoopName) {
|
||||||
|
pylint::rules::redefined_loop_name(self, &Node::Stmt(stmt));
|
||||||
|
}
|
||||||
if matches!(stmt.node, StmtKind::For { .. }) {
|
if matches!(stmt.node, StmtKind::For { .. }) {
|
||||||
if self.settings.rules.enabled(&Rule::ReimplementedBuiltin) {
|
if self.settings.rules.enabled(&Rule::ReimplementedBuiltin) {
|
||||||
flake8_simplify::rules::convert_for_loop_to_any_all(
|
flake8_simplify::rules::convert_for_loop_to_any_all(
|
||||||
|
|
|
@ -146,6 +146,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||||
(Pylint, "R0913") => Rule::TooManyArguments,
|
(Pylint, "R0913") => Rule::TooManyArguments,
|
||||||
(Pylint, "R0912") => Rule::TooManyBranches,
|
(Pylint, "R0912") => Rule::TooManyBranches,
|
||||||
(Pylint, "R0915") => Rule::TooManyStatements,
|
(Pylint, "R0915") => Rule::TooManyStatements,
|
||||||
|
(Pylint, "W2901") => Rule::RedefinedLoopName,
|
||||||
|
|
||||||
// flake8-builtins
|
// flake8-builtins
|
||||||
(Flake8Builtins, "001") => Rule::BuiltinVariableShadowing,
|
(Flake8Builtins, "001") => Rule::BuiltinVariableShadowing,
|
||||||
|
|
|
@ -149,6 +149,7 @@ ruff_macros::register_rules!(
|
||||||
rules::pylint::rules::TooManyArguments,
|
rules::pylint::rules::TooManyArguments,
|
||||||
rules::pylint::rules::TooManyBranches,
|
rules::pylint::rules::TooManyBranches,
|
||||||
rules::pylint::rules::TooManyStatements,
|
rules::pylint::rules::TooManyStatements,
|
||||||
|
rules::pylint::rules::RedefinedLoopName,
|
||||||
// flake8-builtins
|
// flake8-builtins
|
||||||
rules::flake8_builtins::rules::BuiltinVariableShadowing,
|
rules::flake8_builtins::rules::BuiltinVariableShadowing,
|
||||||
rules::flake8_builtins::rules::BuiltinArgumentShadowing,
|
rules::flake8_builtins::rules::BuiltinArgumentShadowing,
|
||||||
|
|
|
@ -47,6 +47,7 @@ mod tests {
|
||||||
#[test_case(Rule::BidirectionalUnicode, Path::new("bidirectional_unicode.py"); "PLE2502")]
|
#[test_case(Rule::BidirectionalUnicode, Path::new("bidirectional_unicode.py"); "PLE2502")]
|
||||||
#[test_case(Rule::BadStrStripCall, Path::new("bad_str_strip_call.py"); "PLE01310")]
|
#[test_case(Rule::BadStrStripCall, Path::new("bad_str_strip_call.py"); "PLE01310")]
|
||||||
#[test_case(Rule::YieldInInit, Path::new("yield_in_init.py"); "PLE0100")]
|
#[test_case(Rule::YieldInInit, Path::new("yield_in_init.py"); "PLE0100")]
|
||||||
|
#[test_case(Rule::RedefinedLoopName, Path::new("redefined_loop_name.py"); "PLW2901")]
|
||||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||||
let diagnostics = test_path(
|
let diagnostics = test_path(
|
||||||
|
|
|
@ -11,6 +11,7 @@ pub use magic_value_comparison::{magic_value_comparison, MagicValueComparison};
|
||||||
pub use merge_isinstance::{merge_isinstance, ConsiderMergingIsinstance};
|
pub use merge_isinstance::{merge_isinstance, ConsiderMergingIsinstance};
|
||||||
pub use nonlocal_without_binding::NonlocalWithoutBinding;
|
pub use nonlocal_without_binding::NonlocalWithoutBinding;
|
||||||
pub use property_with_parameters::{property_with_parameters, PropertyWithParameters};
|
pub use property_with_parameters::{property_with_parameters, PropertyWithParameters};
|
||||||
|
pub use redefined_loop_name::{redefined_loop_name, RedefinedLoopName};
|
||||||
pub use return_in_init::{return_in_init, ReturnInInit};
|
pub use return_in_init::{return_in_init, ReturnInInit};
|
||||||
pub use too_many_arguments::{too_many_arguments, TooManyArguments};
|
pub use too_many_arguments::{too_many_arguments, TooManyArguments};
|
||||||
pub use too_many_branches::{too_many_branches, TooManyBranches};
|
pub use too_many_branches::{too_many_branches, TooManyBranches};
|
||||||
|
@ -40,6 +41,7 @@ mod magic_value_comparison;
|
||||||
mod merge_isinstance;
|
mod merge_isinstance;
|
||||||
mod nonlocal_without_binding;
|
mod nonlocal_without_binding;
|
||||||
mod property_with_parameters;
|
mod property_with_parameters;
|
||||||
|
mod redefined_loop_name;
|
||||||
mod return_in_init;
|
mod return_in_init;
|
||||||
mod too_many_arguments;
|
mod too_many_arguments;
|
||||||
mod too_many_branches;
|
mod too_many_branches;
|
||||||
|
|
302
crates/ruff/src/rules/pylint/rules/redefined_loop_name.rs
Normal file
302
crates/ruff/src/rules/pylint/rules/redefined_loop_name.rs
Normal file
|
@ -0,0 +1,302 @@
|
||||||
|
use crate::ast::comparable::ComparableExpr;
|
||||||
|
use crate::ast::helpers::unparse_expr;
|
||||||
|
use crate::ast::types::{Node, Range};
|
||||||
|
use crate::ast::visitor;
|
||||||
|
use crate::ast::visitor::Visitor;
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::registry::Diagnostic;
|
||||||
|
use crate::settings::hashable::HashableRegex;
|
||||||
|
use crate::violation::Violation;
|
||||||
|
use ruff_macros::{define_violation, derive_message_formats};
|
||||||
|
use rustpython_parser::ast::{Expr, ExprContext, ExprKind, Stmt, StmtKind, Withitem};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{fmt, iter};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
|
||||||
|
pub enum BindingKind {
|
||||||
|
For,
|
||||||
|
With,
|
||||||
|
Assignment,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for BindingKind {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
BindingKind::For => fmt.write_str("for loop"),
|
||||||
|
BindingKind::With => fmt.write_str("with statement"),
|
||||||
|
BindingKind::Assignment => fmt.write_str("assignment"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
define_violation!(
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for variables defined in `for` loops and `with` statements that
|
||||||
|
/// get overwritten within the body, for example by another `for` loop or
|
||||||
|
/// `with` statement or by direct assignment.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Redefinition of a loop variable inside the loop's body causes its value
|
||||||
|
/// to differ from the original loop iteration for the remainder of the
|
||||||
|
/// block, in a way that will likely cause bugs.
|
||||||
|
///
|
||||||
|
/// In Python, unlike many other languages, `for` loops and `with`
|
||||||
|
/// statements don't define their own scopes. Therefore, a nested loop that
|
||||||
|
/// uses the same target variable name as an outer loop will reuse the same
|
||||||
|
/// actual variable, and the value from the last iteration will "leak out"
|
||||||
|
/// into the remainder of the enclosing loop.
|
||||||
|
///
|
||||||
|
/// While this mistake is easy to spot in small examples, it can be hidden
|
||||||
|
/// in larger blocks of code where the definition and redefinition of the
|
||||||
|
/// variable may not be visible at the same time.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// for i in range(10):
|
||||||
|
/// i = 9
|
||||||
|
/// print(i) # prints 9 every iteration
|
||||||
|
///
|
||||||
|
/// for i in range(10):
|
||||||
|
/// for i in range(10): # original value overwritten
|
||||||
|
/// pass
|
||||||
|
/// print(i) # also prints 9 every iteration
|
||||||
|
///
|
||||||
|
/// with path1.open() as f:
|
||||||
|
/// with path2.open() as f:
|
||||||
|
/// f = path2.open()
|
||||||
|
/// print(f.readline()) # prints a line from path2
|
||||||
|
/// ```
|
||||||
|
pub struct RedefinedLoopName {
|
||||||
|
pub name: String,
|
||||||
|
pub outer_kind: BindingKind,
|
||||||
|
pub inner_kind: BindingKind,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
impl Violation for RedefinedLoopName {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
let RedefinedLoopName {
|
||||||
|
name,
|
||||||
|
outer_kind,
|
||||||
|
inner_kind,
|
||||||
|
} = self;
|
||||||
|
format!("Outer {outer_kind} variable `{name}` overwritten by inner {inner_kind} target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ExprWithBindingKind<'a> {
|
||||||
|
expr: &'a Expr,
|
||||||
|
binding_kind: BindingKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InnerForWithAssignTargetsVisitor<'a> {
|
||||||
|
dummy_variable_rgx: &'a HashableRegex,
|
||||||
|
assignment_targets: Vec<ExprWithBindingKind<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> Visitor<'b> for InnerForWithAssignTargetsVisitor<'a>
|
||||||
|
where
|
||||||
|
'b: 'a,
|
||||||
|
{
|
||||||
|
fn visit_stmt(&mut self, stmt: &'b Stmt) {
|
||||||
|
// Collect target expressions.
|
||||||
|
match &stmt.node {
|
||||||
|
// For and async for.
|
||||||
|
StmtKind::For { target, .. } | StmtKind::AsyncFor { target, .. } => {
|
||||||
|
self.assignment_targets.extend(
|
||||||
|
assignment_targets_from_expr(target, self.dummy_variable_rgx).map(|expr| {
|
||||||
|
ExprWithBindingKind {
|
||||||
|
expr,
|
||||||
|
binding_kind: BindingKind::For,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// With.
|
||||||
|
StmtKind::With { items, .. } => {
|
||||||
|
self.assignment_targets.extend(
|
||||||
|
assignment_targets_from_with_items(items, self.dummy_variable_rgx).map(
|
||||||
|
|expr| ExprWithBindingKind {
|
||||||
|
expr,
|
||||||
|
binding_kind: BindingKind::With,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Assignment, augmented assignment, and annotated assignment.
|
||||||
|
StmtKind::Assign { targets, .. } => {
|
||||||
|
self.assignment_targets.extend(
|
||||||
|
assignment_targets_from_assign_targets(targets, self.dummy_variable_rgx).map(
|
||||||
|
|expr| ExprWithBindingKind {
|
||||||
|
expr,
|
||||||
|
binding_kind: BindingKind::Assignment,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
StmtKind::AugAssign { target, .. } | StmtKind::AnnAssign { target, .. } => {
|
||||||
|
self.assignment_targets.extend(
|
||||||
|
assignment_targets_from_expr(target, self.dummy_variable_rgx).map(|expr| {
|
||||||
|
ExprWithBindingKind {
|
||||||
|
expr,
|
||||||
|
binding_kind: BindingKind::Assignment,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
// Decide whether to recurse.
|
||||||
|
match &stmt.node {
|
||||||
|
// Don't recurse into blocks that create a new scope.
|
||||||
|
StmtKind::ClassDef { .. } => {}
|
||||||
|
StmtKind::FunctionDef { .. } => {}
|
||||||
|
// Otherwise, do recurse.
|
||||||
|
_ => {
|
||||||
|
visitor::walk_stmt(self, stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assignment_targets_from_expr<'a, U>(
|
||||||
|
expr: &'a Expr<U>,
|
||||||
|
dummy_variable_rgx: &'a HashableRegex,
|
||||||
|
) -> Box<dyn Iterator<Item = &'a Expr<U>> + 'a> {
|
||||||
|
// The Box is necessary to ensure the match arms have the same return type - we can't use
|
||||||
|
// a cast to "impl Iterator", since at the time of writing that is only allowed for
|
||||||
|
// return types and argument types.
|
||||||
|
match &expr.node {
|
||||||
|
ExprKind::Attribute {
|
||||||
|
ctx: ExprContext::Store,
|
||||||
|
..
|
||||||
|
} => Box::new(iter::once(expr)),
|
||||||
|
ExprKind::Subscript {
|
||||||
|
ctx: ExprContext::Store,
|
||||||
|
..
|
||||||
|
} => Box::new(iter::once(expr)),
|
||||||
|
ExprKind::Starred {
|
||||||
|
ctx: ExprContext::Store,
|
||||||
|
value,
|
||||||
|
..
|
||||||
|
} => Box::new(iter::once(&**value)),
|
||||||
|
ExprKind::Name {
|
||||||
|
ctx: ExprContext::Store,
|
||||||
|
id,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// Ignore dummy variables.
|
||||||
|
if dummy_variable_rgx.is_match(id) {
|
||||||
|
Box::new(iter::empty())
|
||||||
|
} else {
|
||||||
|
Box::new(iter::once(expr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ExprKind::List {
|
||||||
|
ctx: ExprContext::Store,
|
||||||
|
elts,
|
||||||
|
..
|
||||||
|
} => Box::new(
|
||||||
|
elts.iter()
|
||||||
|
.flat_map(|elt| assignment_targets_from_expr(elt, dummy_variable_rgx)),
|
||||||
|
),
|
||||||
|
ExprKind::Tuple {
|
||||||
|
ctx: ExprContext::Store,
|
||||||
|
elts,
|
||||||
|
..
|
||||||
|
} => Box::new(
|
||||||
|
elts.iter()
|
||||||
|
.flat_map(|elt| assignment_targets_from_expr(elt, dummy_variable_rgx)),
|
||||||
|
),
|
||||||
|
_ => Box::new(iter::empty()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assignment_targets_from_with_items<'a, U>(
|
||||||
|
items: &'a [Withitem<U>],
|
||||||
|
dummy_variable_rgx: &'a HashableRegex,
|
||||||
|
) -> impl Iterator<Item = &'a Expr<U>> + 'a {
|
||||||
|
items
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| {
|
||||||
|
item.optional_vars
|
||||||
|
.as_ref()
|
||||||
|
.map(|expr| assignment_targets_from_expr(&**expr, dummy_variable_rgx))
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assignment_targets_from_assign_targets<'a, U>(
|
||||||
|
targets: &'a [Expr<U>],
|
||||||
|
dummy_variable_rgx: &'a HashableRegex,
|
||||||
|
) -> impl Iterator<Item = &'a Expr<U>> + 'a {
|
||||||
|
targets
|
||||||
|
.iter()
|
||||||
|
.flat_map(|target| assignment_targets_from_expr(target, dummy_variable_rgx))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PLW2901
|
||||||
|
pub fn redefined_loop_name<'a, 'b>(checker: &'a mut Checker<'b>, node: &Node<'b>) {
|
||||||
|
let (outer_assignment_targets, inner_assignment_targets) = match node {
|
||||||
|
Node::Stmt(stmt) => match &stmt.node {
|
||||||
|
// With.
|
||||||
|
StmtKind::With { items, body, .. } => {
|
||||||
|
let outer_assignment_targets: Vec<ExprWithBindingKind<'a>> =
|
||||||
|
assignment_targets_from_with_items(items, &checker.settings.dummy_variable_rgx)
|
||||||
|
.map(|expr| ExprWithBindingKind {
|
||||||
|
expr,
|
||||||
|
binding_kind: BindingKind::With,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let mut visitor = InnerForWithAssignTargetsVisitor {
|
||||||
|
dummy_variable_rgx: &checker.settings.dummy_variable_rgx,
|
||||||
|
assignment_targets: vec![],
|
||||||
|
};
|
||||||
|
for stmt in body {
|
||||||
|
visitor.visit_stmt(stmt);
|
||||||
|
}
|
||||||
|
(outer_assignment_targets, visitor.assignment_targets)
|
||||||
|
}
|
||||||
|
// For and async for.
|
||||||
|
StmtKind::For { target, body, .. } | StmtKind::AsyncFor { target, body, .. } => {
|
||||||
|
let outer_assignment_targets: Vec<ExprWithBindingKind<'a>> =
|
||||||
|
assignment_targets_from_expr(target, &checker.settings.dummy_variable_rgx)
|
||||||
|
.map(|expr| ExprWithBindingKind {
|
||||||
|
expr,
|
||||||
|
binding_kind: BindingKind::For,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let mut visitor = InnerForWithAssignTargetsVisitor {
|
||||||
|
dummy_variable_rgx: &checker.settings.dummy_variable_rgx,
|
||||||
|
assignment_targets: vec![],
|
||||||
|
};
|
||||||
|
for stmt in body {
|
||||||
|
visitor.visit_stmt(stmt);
|
||||||
|
}
|
||||||
|
(outer_assignment_targets, visitor.assignment_targets)
|
||||||
|
}
|
||||||
|
_ => panic!(
|
||||||
|
"redefined_loop_name called on Statement that is not a With, For, or AsyncFor"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Node::Expr(_) => panic!("redefined_loop_name called on Node that is not a Statement"),
|
||||||
|
};
|
||||||
|
|
||||||
|
for outer_assignment_target in &outer_assignment_targets {
|
||||||
|
for inner_assignment_target in &inner_assignment_targets {
|
||||||
|
// Compare the targets structurally.
|
||||||
|
if ComparableExpr::from(outer_assignment_target.expr)
|
||||||
|
.eq(&(ComparableExpr::from(inner_assignment_target.expr)))
|
||||||
|
{
|
||||||
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
|
RedefinedLoopName {
|
||||||
|
name: unparse_expr(outer_assignment_target.expr, checker.stylist),
|
||||||
|
outer_kind: outer_assignment_target.binding_kind,
|
||||||
|
inner_kind: inner_assignment_target.binding_kind,
|
||||||
|
},
|
||||||
|
Range::from_located(inner_assignment_target.expr),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,330 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/pylint/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: i
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: For
|
||||||
|
location:
|
||||||
|
row: 3
|
||||||
|
column: 8
|
||||||
|
end_location:
|
||||||
|
row: 3
|
||||||
|
column: 9
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: i
|
||||||
|
outer_kind: With
|
||||||
|
inner_kind: For
|
||||||
|
location:
|
||||||
|
row: 8
|
||||||
|
column: 8
|
||||||
|
end_location:
|
||||||
|
row: 8
|
||||||
|
column: 9
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: i
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: With
|
||||||
|
location:
|
||||||
|
row: 13
|
||||||
|
column: 17
|
||||||
|
end_location:
|
||||||
|
row: 13
|
||||||
|
column: 18
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: i
|
||||||
|
outer_kind: With
|
||||||
|
inner_kind: With
|
||||||
|
location:
|
||||||
|
row: 18
|
||||||
|
column: 17
|
||||||
|
end_location:
|
||||||
|
row: 18
|
||||||
|
column: 18
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: i
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: For
|
||||||
|
location:
|
||||||
|
row: 34
|
||||||
|
column: 12
|
||||||
|
end_location:
|
||||||
|
row: 34
|
||||||
|
column: 13
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: i
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: For
|
||||||
|
location:
|
||||||
|
row: 40
|
||||||
|
column: 12
|
||||||
|
end_location:
|
||||||
|
row: 40
|
||||||
|
column: 13
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: j
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: For
|
||||||
|
location:
|
||||||
|
row: 41
|
||||||
|
column: 16
|
||||||
|
end_location:
|
||||||
|
row: 41
|
||||||
|
column: 17
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: i
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: Assignment
|
||||||
|
location:
|
||||||
|
row: 46
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 46
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: i
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: Assignment
|
||||||
|
location:
|
||||||
|
row: 50
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 50
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: i
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: Assignment
|
||||||
|
location:
|
||||||
|
row: 54
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 54
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: i
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: For
|
||||||
|
location:
|
||||||
|
row: 58
|
||||||
|
column: 8
|
||||||
|
end_location:
|
||||||
|
row: 58
|
||||||
|
column: 9
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: i
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: For
|
||||||
|
location:
|
||||||
|
row: 63
|
||||||
|
column: 14
|
||||||
|
end_location:
|
||||||
|
row: 63
|
||||||
|
column: 15
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: i
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: For
|
||||||
|
location:
|
||||||
|
row: 68
|
||||||
|
column: 8
|
||||||
|
end_location:
|
||||||
|
row: 68
|
||||||
|
column: 9
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: i
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: For
|
||||||
|
location:
|
||||||
|
row: 73
|
||||||
|
column: 8
|
||||||
|
end_location:
|
||||||
|
row: 73
|
||||||
|
column: 9
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: i
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: For
|
||||||
|
location:
|
||||||
|
row: 78
|
||||||
|
column: 8
|
||||||
|
end_location:
|
||||||
|
row: 78
|
||||||
|
column: 9
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: j
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: For
|
||||||
|
location:
|
||||||
|
row: 78
|
||||||
|
column: 11
|
||||||
|
end_location:
|
||||||
|
row: 78
|
||||||
|
column: 12
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: j
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: For
|
||||||
|
location:
|
||||||
|
row: 95
|
||||||
|
column: 8
|
||||||
|
end_location:
|
||||||
|
row: 95
|
||||||
|
column: 9
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: i
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: Assignment
|
||||||
|
location:
|
||||||
|
row: 112
|
||||||
|
column: 12
|
||||||
|
end_location:
|
||||||
|
row: 112
|
||||||
|
column: 13
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: i
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: For
|
||||||
|
location:
|
||||||
|
row: 118
|
||||||
|
column: 16
|
||||||
|
end_location:
|
||||||
|
row: 118
|
||||||
|
column: 17
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: "a[0]"
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: Assignment
|
||||||
|
location:
|
||||||
|
row: 133
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 133
|
||||||
|
column: 8
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: "a['i']"
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: Assignment
|
||||||
|
location:
|
||||||
|
row: 138
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 138
|
||||||
|
column: 10
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: a.i
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: Assignment
|
||||||
|
location:
|
||||||
|
row: 143
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 143
|
||||||
|
column: 7
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: a.i.j
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: Assignment
|
||||||
|
location:
|
||||||
|
row: 148
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 148
|
||||||
|
column: 9
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: a.i
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: Assignment
|
||||||
|
location:
|
||||||
|
row: 153
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 153
|
||||||
|
column: 8
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
RedefinedLoopName:
|
||||||
|
name: a.i
|
||||||
|
outer_kind: For
|
||||||
|
inner_kind: Assignment
|
||||||
|
location:
|
||||||
|
row: 155
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 155
|
||||||
|
column: 7
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
|
4
ruff.schema.json
generated
4
ruff.schema.json
generated
|
@ -1837,6 +1837,10 @@
|
||||||
"PLW06",
|
"PLW06",
|
||||||
"PLW060",
|
"PLW060",
|
||||||
"PLW0602",
|
"PLW0602",
|
||||||
|
"PLW2",
|
||||||
|
"PLW29",
|
||||||
|
"PLW290",
|
||||||
|
"PLW2901",
|
||||||
"PT",
|
"PT",
|
||||||
"PT0",
|
"PT0",
|
||||||
"PT00",
|
"PT00",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue