Avoid recommending no-argument super in slots=True dataclasses (#12530)

## Summary

Closes https://github.com/astral-sh/ruff/issues/12506.
This commit is contained in:
Charlie Marsh 2024-07-26 10:09:51 -04:00 committed by GitHub
parent 6f4db8675b
commit 998bfe0847
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 67 additions and 1 deletions

View file

@ -63,3 +63,19 @@ class MyClass(BaseClass):
InnerClass().method() InnerClass().method()
defined_outside = defined_outside 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)

View file

@ -102,7 +102,9 @@ pub(crate) fn super_call_with_parameters(checker: &mut Checker, call: &ast::Expr
// Find the enclosing class definition (if any). // Find the enclosing class definition (if any).
let Some(Stmt::ClassDef(ast::StmtClassDef { let Some(Stmt::ClassDef(ast::StmtClassDef {
name: parent_name, .. name: parent_name,
decorator_list,
..
})) = parents.find(|stmt| stmt.is_class_def_stmt()) })) = parents.find(|stmt| stmt.is_class_def_stmt())
else { else {
return; return;
@ -126,6 +128,36 @@ pub(crate) fn super_call_with_parameters(checker: &mut Checker, call: &ast::Expr
drop(parents); 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()); let mut diagnostic = Diagnostic::new(SuperCallWithParameters, call.arguments.range());
diagnostic.set_fix(Fix::unsafe_edit(Edit::deletion( diagnostic.set_fix(Fix::unsafe_edit(Edit::deletion(
call.arguments.start() + TextSize::new(1), call.arguments.start() + TextSize::new(1),

View file

@ -107,4 +107,22 @@ UP008.py:50:18: UP008 [*] Use `super()` instead of `super(__class__, self)`
52 52 | 52 52 |
53 53 | outer_argument() 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 |