diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pie/PIE794.py b/crates/ruff_linter/resources/test/fixtures/flake8_pie/PIE794.py index 12912ce9a7ec3..51981397899de 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pie/PIE794.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pie/PIE794.py @@ -38,3 +38,15 @@ class User: foo: bool = BooleanField() # ... bar = StringField() # PIE794 + + +class Person: + name = "Foo" + name = name + " Bar" + name = "Bar" # PIE794 + + +class Person: + name: str = "Foo" + name: str = name + " Bar" + name: str = "Bar" # PIE794 diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/duplicate_class_field_definition.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/duplicate_class_field_definition.rs index 4fa34d1450eb3..e50af0ec2fc8c 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/duplicate_class_field_definition.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/duplicate_class_field_definition.rs @@ -3,6 +3,7 @@ use rustc_hash::FxHashSet; use ruff_diagnostics::Diagnostic; use ruff_diagnostics::{AlwaysFixableViolation, Fix}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::helpers::any_over_expr; use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_text_size::Ranged; @@ -55,14 +56,14 @@ pub(crate) fn duplicate_class_field_definition(checker: &mut Checker, body: &[St // Extract the property name from the assignment statement. let target = match stmt { Stmt::Assign(ast::StmtAssign { targets, .. }) => { - if let [Expr::Name(ast::ExprName { id, .. })] = targets.as_slice() { + if let [Expr::Name(id)] = targets.as_slice() { id } else { continue; } } Stmt::AnnAssign(ast::StmtAnnAssign { target, .. }) => { - if let Expr::Name(ast::ExprName { id, .. }) = target.as_ref() { + if let Expr::Name(id) = target.as_ref() { id } else { continue; @@ -71,10 +72,31 @@ pub(crate) fn duplicate_class_field_definition(checker: &mut Checker, body: &[St _ => continue, }; - if !seen_targets.insert(target) { + // If this is an unrolled augmented assignment (e.g., `x = x + 1`), skip it. + match stmt { + Stmt::Assign(ast::StmtAssign { value, .. }) => { + if any_over_expr(value.as_ref(), &|expr| { + expr.as_name_expr().is_some_and(|name| name.id == target.id) + }) { + continue; + } + } + Stmt::AnnAssign(ast::StmtAnnAssign { + value: Some(value), .. + }) => { + if any_over_expr(value.as_ref(), &|expr| { + expr.as_name_expr().is_some_and(|name| name.id == target.id) + }) { + continue; + } + } + _ => continue, + } + + if !seen_targets.insert(target.id.as_str()) { let mut diagnostic = Diagnostic::new( DuplicateClassFieldDefinition { - name: target.to_string(), + name: target.id.to_string(), }, stmt.range(), ); diff --git a/crates/ruff_linter/src/rules/flake8_pie/snapshots/ruff_linter__rules__flake8_pie__tests__PIE794_PIE794.py.snap b/crates/ruff_linter/src/rules/flake8_pie/snapshots/ruff_linter__rules__flake8_pie__tests__PIE794_PIE794.py.snap index 3fa8ac491a88d..f5945563ffb5f 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/snapshots/ruff_linter__rules__flake8_pie__tests__PIE794_PIE794.py.snap +++ b/crates/ruff_linter/src/rules/flake8_pie/snapshots/ruff_linter__rules__flake8_pie__tests__PIE794_PIE794.py.snap @@ -73,5 +73,41 @@ PIE794.py:40:5: PIE794 [*] Class field `bar` is defined multiple times 38 38 | foo: bool = BooleanField() 39 39 | # ... 40 |- bar = StringField() # PIE794 +41 40 | +42 41 | +43 42 | class Person: + +PIE794.py:46:5: PIE794 [*] Class field `name` is defined multiple times + | +44 | name = "Foo" +45 | name = name + " Bar" +46 | name = "Bar" # PIE794 + | ^^^^^^^^^^^^ PIE794 + | + = help: Remove duplicate field definition for `name` + +ℹ Unsafe fix +43 43 | class Person: +44 44 | name = "Foo" +45 45 | name = name + " Bar" +46 |- name = "Bar" # PIE794 +47 46 | +48 47 | +49 48 | class Person: + +PIE794.py:52:5: PIE794 [*] Class field `name` is defined multiple times + | +50 | name: str = "Foo" +51 | name: str = name + " Bar" +52 | name: str = "Bar" # PIE794 + | ^^^^^^^^^^^^^^^^^ PIE794 + | + = help: Remove duplicate field definition for `name` + +ℹ Unsafe fix +49 49 | class Person: +50 50 | name: str = "Foo" +51 51 | name: str = name + " Bar" +52 |- name: str = "Bar" # PIE794