diff --git a/crates/ruff_linter/resources/mdtest/pylint/redefined-loop-name.md b/crates/ruff_linter/resources/mdtest/pylint/redefined-loop-name.md new file mode 100644 index 0000000000000..6b360389cee55 --- /dev/null +++ b/crates/ruff_linter/resources/mdtest/pylint/redefined-loop-name.md @@ -0,0 +1,86 @@ +# `redefined-loop-name` (`PLW2901`) + +```toml +[lint] +select = ["PLW2901"] +``` + +## Augmented assignment + +Ignore in-place update of a mutable type. + +```py +for i in []: + i += [1] + +for i in []: + i = [1] # snapshot: redefined-loop-name + +for i in []: + i |= {"a": 1} + +for i in []: + i = {"b": 2} # snapshot: redefined-loop-name + +for i in []: + i |= {1} + +for i in []: + i &= {1} + +for i in []: + i ^= {1} + +for i in []: + i -= {1} + +for i in []: + i = {1} # snapshot: redefined-loop-name + +for i in []: + i += (1,) # snapshot: redefined-loop-name + +for i in []: + i += "a" # snapshot: redefined-loop-name +``` + +```snapshot +error[PLW2901]: `for` loop variable `i` overwritten by assignment target + --> src/mdtest_snippet.py:5:5 + | +5 | i = [1] # snapshot: redefined-loop-name + | ^ + | + + +error[PLW2901]: `for` loop variable `i` overwritten by assignment target + --> src/mdtest_snippet.py:11:5 + | +11 | i = {"b": 2} # snapshot: redefined-loop-name + | ^ + | + + +error[PLW2901]: `for` loop variable `i` overwritten by assignment target + --> src/mdtest_snippet.py:26:5 + | +26 | i = {1} # snapshot: redefined-loop-name + | ^ + | + + +error[PLW2901]: `for` loop variable `i` overwritten by assignment target + --> src/mdtest_snippet.py:29:5 + | +29 | i += (1,) # snapshot: redefined-loop-name + | ^ + | + + +error[PLW2901]: `for` loop variable `i` overwritten by assignment target + --> src/mdtest_snippet.py:32:5 + | +32 | i += "a" # snapshot: redefined-loop-name + | ^ + | +``` diff --git a/crates/ruff_linter/src/rules/pylint/rules/redefined_loop_name.rs b/crates/ruff_linter/src/rules/pylint/rules/redefined_loop_name.rs index b77f07f2a22c2..316412b733804 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/redefined_loop_name.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/redefined_loop_name.rs @@ -7,6 +7,7 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt}; use ruff_python_semantic::SemanticModel; +use ruff_python_semantic::analyze::typing::is_mutable_expr; use ruff_text_size::Ranged; use crate::Violation; @@ -193,7 +194,23 @@ impl<'b> StatementVisitor<'b> for InnerForWithAssignTargetsVisitor<'_, 'b> { ), ); } - Stmt::AugAssign(ast::StmtAugAssign { target, .. }) => { + Stmt::AugAssign(ast::StmtAugAssign { + target, value, op, .. + }) => { + // Check for in-place update of mutable type + if is_mutable_expr(value, self.context) + && matches!( + op, + ast::Operator::Add + | ast::Operator::Sub + | ast::Operator::BitOr + | ast::Operator::BitAnd + | ast::Operator::BitXor + ) + { + return; + } + self.assignment_targets.extend( assignment_targets_from_expr(target, self.dummy_variable_rgx).map(|expr| { ExprWithInnerBindingKind {