diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py index 047d90f30d..e9d68ebc3a 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP008.py @@ -63,3 +63,19 @@ class MyClass(BaseClass): InnerClass().method() defined_outside = defined_outside + + +from dataclasses import dataclass + + +@dataclass +class DataClass: + def normal(self): + super(DataClass, self).f() # Error + super().f() # OK + + +@dataclass(slots=True) +def normal(self): + super(DataClass, self).f() # OK + super().f() # OK (`TypeError` in practice) 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 a01934676b..06fb6ee050 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 @@ -102,7 +102,9 @@ pub(crate) fn super_call_with_parameters(checker: &mut Checker, call: &ast::Expr // Find the enclosing class definition (if any). let Some(Stmt::ClassDef(ast::StmtClassDef { - name: parent_name, .. + name: parent_name, + decorator_list, + .. })) = parents.find(|stmt| stmt.is_class_def_stmt()) else { return; @@ -126,6 +128,36 @@ pub(crate) fn super_call_with_parameters(checker: &mut Checker, call: &ast::Expr drop(parents); + // If the class is an `@dataclass` with `slots=True`, calling `super()` without arguments raises + // a `TypeError`. + // + // See: https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass + if decorator_list.iter().any(|decorator| { + let Expr::Call(ast::ExprCall { + func, arguments, .. + }) = &decorator.expression + else { + return false; + }; + + if checker + .semantic() + .resolve_qualified_name(func) + .is_some_and(|name| name.segments() == ["dataclasses", "dataclass"]) + { + arguments.find_keyword("slots").map_or(false, |keyword| { + matches!( + keyword.value, + Expr::BooleanLiteral(ast::ExprBooleanLiteral { value: true, .. }) + ) + }) + } else { + false + } + }) { + return; + } + let mut diagnostic = Diagnostic::new(SuperCallWithParameters, call.arguments.range()); diagnostic.set_fix(Fix::unsafe_edit(Edit::deletion( call.arguments.start() + TextSize::new(1), 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 04c5276bed..da6e68284d 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 @@ -107,4 +107,22 @@ UP008.py:50:18: UP008 [*] Use `super()` instead of `super(__class__, self)` 52 52 | 53 53 | outer_argument() +UP008.py:74:14: UP008 [*] Use `super()` instead of `super(__class__, self)` + | +72 | class DataClass: +73 | def normal(self): +74 | super(DataClass, self).f() # Error + | ^^^^^^^^^^^^^^^^^ UP008 +75 | super().f() # OK + | + = help: Remove `__super__` parameters +ℹ Unsafe fix +71 71 | @dataclass +72 72 | class DataClass: +73 73 | def normal(self): +74 |- super(DataClass, self).f() # Error + 74 |+ super().f() # Error +75 75 | super().f() # OK +76 76 | +77 77 |