Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pylint] redefined-loop-name (W2901) #3022

Merged
merged 12 commits into from
Feb 22, 2023
119 changes: 119 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,119 @@
# For -> for, variable reused
matthewlloyd marked this conversation as resolved.
Show resolved Hide resolved
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
6 changes: 4 additions & 2 deletions crates/ruff/src/ast/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -869,7 +869,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 @@ -878,7 +881,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 @@ -341,7 +341,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 @@ -371,7 +371,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 @@ -1639,6 +1639,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 @@ -1683,6 +1686,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