Skip to content

Commit

Permalink
[pylint] redefined-loop-name (W2901) (#3022)
Browse files Browse the repository at this point in the history
Slightly broadens W2901 to cover `with` statements too.

Closes #2972.
  • Loading branch information
matthewlloyd committed Feb 22, 2023
1 parent 9645790 commit 97338e4
Show file tree
Hide file tree
Showing 11 changed files with 809 additions and 5 deletions.
155 changes: 155 additions & 0 deletions crates/ruff/resources/test/fixtures/pylint/redefined_loop_name.py
Original file line number Diff line number Diff line change
@@ -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
6 changes: 4 additions & 2 deletions crates/ruff/src/ast/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -928,7 +928,10 @@ pub fn binding_range(binding: &Binding, locator: &Locator) -> Range {
}

// 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));
lexer::make_tokenizer_located(contents, located.location)
.flatten()
Expand All @@ -937,7 +940,6 @@ pub fn find_names<T>(located: &Located<T>, locator: &Locator) -> Vec<Range> {
location: start,
end_location: end,
})
.collect()
}

/// Return the `Range` of `name` in `Excepthandler`.
Expand Down
2 changes: 1 addition & 1 deletion crates/ruff/src/ast/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}

Expand Down
10 changes: 8 additions & 2 deletions crates/ruff/src/checkers/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ where
match &stmt.node {
StmtKind::Global { names } => {
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 {
// Add the binding to the current scope.
let context = self.execution_context();
Expand Down Expand Up @@ -372,7 +372,7 @@ where
}
StmtKind::Nonlocal { names } => {
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 {
let context = self.execution_context();
let scope = &mut self.scopes[scope_index];
Expand Down Expand Up @@ -1651,6 +1651,9 @@ where
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, .. } => {
if self.settings.rules.enabled(&Rule::FunctionUsesLoopVariable) {
Expand Down Expand Up @@ -1695,6 +1698,9 @@ where
if self.settings.rules.enabled(&Rule::UselessElseOnLoop) {
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 self.settings.rules.enabled(&Rule::ReimplementedBuiltin) {
flake8_simplify::rules::convert_for_loop_to_any_all(
Expand Down
1 change: 1 addition & 0 deletions crates/ruff/src/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pylint, "R0913") => Rule::TooManyArguments,
(Pylint, "R0912") => Rule::TooManyBranches,
(Pylint, "R0915") => Rule::TooManyStatements,
(Pylint, "W2901") => Rule::RedefinedLoopName,

// flake8-builtins
(Flake8Builtins, "001") => Rule::BuiltinVariableShadowing,
Expand Down
1 change: 1 addition & 0 deletions crates/ruff/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ ruff_macros::register_rules!(
rules::pylint::rules::TooManyArguments,
rules::pylint::rules::TooManyBranches,
rules::pylint::rules::TooManyStatements,
rules::pylint::rules::RedefinedLoopName,
// flake8-builtins
rules::flake8_builtins::rules::BuiltinVariableShadowing,
rules::flake8_builtins::rules::BuiltinArgumentShadowing,
Expand Down
1 change: 1 addition & 0 deletions crates/ruff/src/rules/pylint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ mod tests {
#[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::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<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Expand Down
2 changes: 2 additions & 0 deletions crates/ruff/src/rules/pylint/rules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub use magic_value_comparison::{magic_value_comparison, MagicValueComparison};
pub use merge_isinstance::{merge_isinstance, ConsiderMergingIsinstance};
pub use nonlocal_without_binding::NonlocalWithoutBinding;
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 too_many_arguments::{too_many_arguments, TooManyArguments};
pub use too_many_branches::{too_many_branches, TooManyBranches};
Expand Down Expand Up @@ -40,6 +41,7 @@ mod magic_value_comparison;
mod merge_isinstance;
mod nonlocal_without_binding;
mod property_with_parameters;
mod redefined_loop_name;
mod return_in_init;
mod too_many_arguments;
mod too_many_branches;
Expand Down

0 comments on commit 97338e4

Please sign in to comment.