diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py index 877e952cd2fa1..50c4e45f57b5f 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py @@ -347,7 +347,6 @@ def __init__(self, foo): class CommonNesting: class C(Base): def __init__(self, foo): - # TODO(charlie): false positive until nested class matching is fixed. super(C, self).__init__(foo) # Should NOT trigger UP008 @@ -355,7 +354,6 @@ class HigherLevelsOfNesting: class Inner: class C(Base): def __init__(self, foo): - # TODO(charlie): false positive until nested class matching is fixed. super(Inner.C, self).__init__(foo) # Should NOT trigger UP008 diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs index 54315ea2cdebf..813276376d6c2 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs @@ -2,7 +2,7 @@ use ruff_diagnostics::Applicability; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::visitor::{Visitor, walk_expr, walk_stmt}; use ruff_python_ast::{self as ast, Expr, Stmt}; -use ruff_python_semantic::SemanticModel; +use ruff_python_semantic::{ScopeKind, SemanticModel}; use ruff_text_size::{Ranged, TextSize}; use crate::checkers::ast::Checker; @@ -112,12 +112,19 @@ pub(crate) fn super_call_with_parameters(checker: &Checker, call: &ast::ExprCall return; }; + let mut enclosing_classes = checker.semantic().current_scopes().filter_map(|scope| { + let ScopeKind::Class(class_def) = &scope.kind else { + return None; + }; + Some(*class_def) + }); + // Find the enclosing class definition (if any). - let Some(Stmt::ClassDef(ast::StmtClassDef { + let Some(ast::StmtClassDef { name: parent_name, decorator_list, .. - })) = parents.find(|stmt| stmt.is_class_def_stmt()) + }) = enclosing_classes.next() else { return; }; @@ -138,9 +145,15 @@ pub(crate) fn super_call_with_parameters(checker: &Checker, call: &ast::ExprCall // For `super(Outer.Inner, self)`, verify each segment matches the enclosing class nesting. match first_arg { Expr::Name(ast::ExprName { id, .. }) => { - if !((id == "__class__" || id == parent_name.as_str()) - && !checker.semantic().current_scope().has(id)) - { + if checker.semantic().current_scope().has(id) { + return; + } + + if id != "__class__" && id == parent_name.as_str() { + if enclosing_classes.next().is_some() { + return; + } + } else if id != "__class__" { return; } } @@ -152,10 +165,10 @@ pub(crate) fn super_call_with_parameters(checker: &Checker, call: &ast::ExprCall } // Each preceding name must match the next enclosing class. for name in chain.iter().rev().skip(1) { - let Some(Stmt::ClassDef(ast::StmtClassDef { + let Some(ast::StmtClassDef { name: enclosing_name, .. - })) = parents.find(|stmt| stmt.is_class_def_stmt()) + }) = enclosing_classes.next() else { return; }; @@ -163,6 +176,9 @@ pub(crate) fn super_call_with_parameters(checker: &Checker, call: &ast::ExprCall return; } } + if enclosing_classes.next().is_some() { + return; + } } _ => return, } diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py.snap index 850fb7fb7b511..942695a391367 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP008.py.snap @@ -496,144 +496,108 @@ help: Remove `super()` parameters 345 | # See: https://github.com/astral-sh/ruff/issues/24001 UP008 [*] Use `super()` instead of `super(__class__, self)` - --> UP008.py:351:18 + --> UP008.py:377:22 | -349 | def __init__(self, foo): -350 | # TODO(charlie): false positive until nested class matching is fixed. -351 | super(C, self).__init__(foo) # Should NOT trigger UP008 - | ^^^^^^^^^ - | -help: Remove `super()` parameters -348 | class C(Base): -349 | def __init__(self, foo): -350 | # TODO(charlie): false positive until nested class matching is fixed. - - super(C, self).__init__(foo) # Should NOT trigger UP008 -351 + super().__init__(foo) # Should NOT trigger UP008 -352 | -353 | -354 | class HigherLevelsOfNesting: - -UP008 [*] Use `super()` instead of `super(__class__, self)` - --> UP008.py:359:22 - | -357 | def __init__(self, foo): -358 | # TODO(charlie): false positive until nested class matching is fixed. -359 | super(Inner.C, self).__init__(foo) # Should NOT trigger UP008 - | ^^^^^^^^^^^^^^^ - | -help: Remove `super()` parameters -356 | class C(Base): -357 | def __init__(self, foo): -358 | # TODO(charlie): false positive until nested class matching is fixed. - - super(Inner.C, self).__init__(foo) # Should NOT trigger UP008 -359 + super().__init__(foo) # Should NOT trigger UP008 -360 | -361 | -362 | # super() first arg is an attribute that only matches on the last segment, - -UP008 [*] Use `super()` instead of `super(__class__, self)` - --> UP008.py:379:22 - | -377 | class C(Base): -378 | def __init__(self, foo): -379 | super(A.B.C, self).__init__(foo) # UP008: matches full chain +375 | class C(Base): +376 | def __init__(self, foo): +377 | super(A.B.C, self).__init__(foo) # UP008: matches full chain | ^^^^^^^^^^^^^ -380 | -381 | # Mismatched middle segment: Wrong.Inner doesn't match Outer3.Inner +378 | +379 | # Mismatched middle segment: Wrong.Inner doesn't match Outer3.Inner | help: Remove `super()` parameters -376 | class B: -377 | class C(Base): -378 | def __init__(self, foo): +374 | class B: +375 | class C(Base): +376 | def __init__(self, foo): - super(A.B.C, self).__init__(foo) # UP008: matches full chain -379 + super().__init__(foo) # UP008: matches full chain -380 | -381 | # Mismatched middle segment: Wrong.Inner doesn't match Outer3.Inner -382 | class Outer3: +377 + super().__init__(foo) # UP008: matches full chain +378 | +379 | # Mismatched middle segment: Wrong.Inner doesn't match Outer3.Inner +380 | class Outer3: UP008 [*] Use `super()` instead of `super(__class__, self)` - --> UP008.py:390:15 + --> UP008.py:388:15 | -388 | class Whitespace(BaseClass): -389 | def f(self): -390 | super (Whitespace, self).f() # can use super() +386 | class Whitespace(BaseClass): +387 | def f(self): +388 | super (Whitespace, self).f() # can use super() | ^^^^^^^^^^^^^^^^^^ | help: Remove `super()` parameters -387 | -388 | class Whitespace(BaseClass): -389 | def f(self): +385 | +386 | class Whitespace(BaseClass): +387 | def f(self): - super (Whitespace, self).f() # can use super() -390 + super ().f() # can use super() -391 | -392 | -393 | def function_local(): +388 + super ().f() # can use super() +389 | +390 | +391 | def function_local(): UP008 [*] Use `super()` instead of `super(__class__, self)` - --> UP008.py:397:22 + --> UP008.py:395:22 | -395 | class LocalInner(BaseClass): -396 | def f(self): -397 | super(LocalOuter.LocalInner, self).f() # can use super() +393 | class LocalInner(BaseClass): +394 | def f(self): +395 | super(LocalOuter.LocalInner, self).f() # can use super() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: Remove `super()` parameters -394 | class LocalOuter: -395 | class LocalInner(BaseClass): -396 | def f(self): +392 | class LocalOuter: +393 | class LocalInner(BaseClass): +394 | def f(self): - super(LocalOuter.LocalInner, self).f() # can use super() -397 + super().f() # can use super() -398 | -399 | -400 | class LambdaMethod(BaseClass): +395 + super().f() # can use super() +396 | +397 | +398 | class LambdaMethod(BaseClass): UP008 [*] Use `super()` instead of `super(__class__, self)` - --> UP008.py:408:14 + --> UP008.py:406:14 | -406 | @classmethod -407 | def f(cls): -408 | super(ClassMethod, cls).f() # can use super() +404 | @classmethod +405 | def f(cls): +406 | super(ClassMethod, cls).f() # can use super() | ^^^^^^^^^^^^^^^^^^ | help: Remove `super()` parameters -405 | class ClassMethod(BaseClass): -406 | @classmethod -407 | def f(cls): +403 | class ClassMethod(BaseClass): +404 | @classmethod +405 | def f(cls): - super(ClassMethod, cls).f() # can use super() -408 + super().f() # can use super() -409 | -410 | -411 | class AsyncMethod(BaseClass): +406 + super().f() # can use super() +407 | +408 | +409 | class AsyncMethod(BaseClass): UP008 [*] Use `super()` instead of `super(__class__, self)` - --> UP008.py:413:14 + --> UP008.py:411:14 | -411 | class AsyncMethod(BaseClass): -412 | async def f(self): -413 | super(AsyncMethod, self).f() # can use super() +409 | class AsyncMethod(BaseClass): +410 | async def f(self): +411 | super(AsyncMethod, self).f() # can use super() | ^^^^^^^^^^^^^^^^^^^ | help: Remove `super()` parameters -410 | -411 | class AsyncMethod(BaseClass): -412 | async def f(self): +408 | +409 | class AsyncMethod(BaseClass): +410 | async def f(self): - super(AsyncMethod, self).f() # can use super() -413 + super().f() # can use super() -414 | -415 | -416 | class OuterWithWhitespace: +411 + super().f() # can use super() +412 | +413 | +414 | class OuterWithWhitespace: UP008 [*] Use `super()` instead of `super(__class__, self)` - --> UP008.py:419:19 + --> UP008.py:417:19 | -417 | class Inner(BaseClass): -418 | def f(self): -419 | super (OuterWithWhitespace.Inner, self).f() # can use super() +415 | class Inner(BaseClass): +416 | def f(self): +417 | super (OuterWithWhitespace.Inner, self).f() # can use super() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: Remove `super()` parameters -416 | class OuterWithWhitespace: -417 | class Inner(BaseClass): -418 | def f(self): +414 | class OuterWithWhitespace: +415 | class Inner(BaseClass): +416 | def f(self): - super (OuterWithWhitespace.Inner, self).f() # can use super() -419 + super ().f() # can use super() +417 + super ().f() # can use super()