diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_dunder_call.py b/crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_dunder_call.py index dcac4a8e50..ef313aa609 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_dunder_call.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_dunder_call.py @@ -24,6 +24,12 @@ class Thing: super().__init__() # OK super().__class__(stuff=(1, 2, 3)) # OK + def __getattribute__(self, item): + return object.__getattribute__(self, item) # OK + + def do_thing(self, item): + return object.__getattribute__(self, item) # PLC2801 + blah = lambda: {"a": 1}.__delitem__("a") # OK diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs index 2d8ebd9516..f3174b170d 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs @@ -1,6 +1,6 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::{self as ast, Expr}; +use ruff_python_ast::{self as ast, Expr, Stmt}; use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; @@ -80,11 +80,17 @@ pub(crate) fn unnecessary_dunder_call(checker: &mut Checker, call: &ast::ExprCal return; } - // Ignore certain dunder methods used in lambda expressions. + // Ignore certain dunder method calls in lambda expressions. These methods would require + // rewriting as a statement, which is not possible in a lambda expression. if allow_nested_expression(attr, checker.semantic()) { return; } + // Ignore dunder method calls within dunder methods definitions. + if in_dunder_method_definition(checker.semantic()) { + return; + } + // Ignore dunder methods used on `super`. if let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() { if checker.semantic().is_builtin("super") { @@ -344,3 +350,13 @@ fn allow_nested_expression(dunder_name: &str, semantic: &SemanticModel) -> bool | "__ior__" ) } + +/// Returns `true` if the [`SemanticModel`] is currently in a dunder method definition. +fn in_dunder_method_definition(semantic: &SemanticModel) -> bool { + semantic.current_statements().any(|statement| { + let Stmt::FunctionDef(func_def) = statement else { + return false; + }; + func_def.name.starts_with("__") && func_def.name.ends_with("__") + }) +} diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC2801_unnecessary_dunder_call.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC2801_unnecessary_dunder_call.py.snap index c24069340a..33d2999618 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC2801_unnecessary_dunder_call.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC2801_unnecessary_dunder_call.py.snap @@ -321,4 +321,12 @@ unnecessary_dunder_call.py:19:7: PLC2801 Unnecessary dunder call to `__neg__`. M | = help: Multiply by -1 instead +unnecessary_dunder_call.py:31:16: PLC2801 Unnecessary dunder call to `__getattribute__`. Access attribute directly or use getattr built-in function. + | +30 | def do_thing(self, item): +31 | return object.__getattribute__(self, item) # PLC2801 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC2801 + | + = help: Access attribute directly or use getattr built-in function +