Avoid recommending __slots__ for classes that inherit from more than namedtuple (#12531)

## Summary

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

View file

@ -1,4 +1,5 @@
from collections import namedtuple from collections import namedtuple
from enum import Enum
from typing import NamedTuple from typing import NamedTuple
@ -20,3 +21,15 @@ class Good(namedtuple("foo", ["str", "int"])): # OK
class Good(NamedTuple): # Ok class Good(NamedTuple): # Ok
pass pass
class Good(namedtuple("foo", ["str", "int"]), Enum):
pass
class UnusualButStillBad(namedtuple("foo", ["str", "int"]), NamedTuple("foo", [("x", int, "y", int)])):
pass
class UnusualButStillBad(namedtuple("foo", ["str", "int"]), object):
pass

View file

@ -92,23 +92,25 @@ pub(crate) fn no_slots_in_namedtuple_subclass(
} }
} }
/// If the class has a call-based namedtuple in its bases, /// If the class's bases consist solely of named tuples, return the kind of named tuple
/// return the kind of namedtuple it is /// (either `collections.namedtuple()`, or `typing.NamedTuple()`). Otherwise, return `None`.
/// (either `collections.namedtuple()`, or `typing.NamedTuple()`).
/// Else, return `None`.
fn namedtuple_base(bases: &[Expr], semantic: &SemanticModel) -> Option<NamedTupleKind> { fn namedtuple_base(bases: &[Expr], semantic: &SemanticModel) -> Option<NamedTupleKind> {
let mut kind = None;
for base in bases { for base in bases {
let Expr::Call(ast::ExprCall { func, .. }) = base else { if let Expr::Call(ast::ExprCall { func, .. }) = base {
continue; // Ex) `collections.namedtuple()`
}; let qualified_name = semantic.resolve_qualified_name(func)?;
let Some(qualified_name) = semantic.resolve_qualified_name(func) else { match qualified_name.segments() {
continue; ["collections", "namedtuple"] => kind = kind.or(Some(NamedTupleKind::Collections)),
}; ["typing", "NamedTuple"] => kind = kind.or(Some(NamedTupleKind::Typing)),
match qualified_name.segments() { // Ex) `enum.Enum`
["collections", "namedtuple"] => return Some(NamedTupleKind::Collections), _ => return None,
["typing", "NamedTuple"] => return Some(NamedTupleKind::Typing), }
_ => continue, } else if !semantic.match_builtin_expr(base, "object") {
// Allow inheriting from `object`.
return None;
} }
} }
None kind
} }

View file

@ -1,16 +1,30 @@
--- ---
source: crates/ruff_linter/src/rules/flake8_slots/mod.rs source: crates/ruff_linter/src/rules/flake8_slots/mod.rs
--- ---
SLOT002.py:5:7: SLOT002 Subclasses of `collections.namedtuple()` should define `__slots__` SLOT002.py:6:7: SLOT002 Subclasses of `collections.namedtuple()` should define `__slots__`
| |
5 | class Bad(namedtuple("foo", ["str", "int"])): # SLOT002 6 | class Bad(namedtuple("foo", ["str", "int"])): # SLOT002
| ^^^ SLOT002 | ^^^ SLOT002
6 | pass 7 | pass
| |
SLOT002.py:9:7: SLOT002 Subclasses of call-based `typing.NamedTuple()` should define `__slots__` SLOT002.py:10:7: SLOT002 Subclasses of call-based `typing.NamedTuple()` should define `__slots__`
| |
9 | class UnusualButStillBad(NamedTuple("foo", [("x", int, "y", int)])): # SLOT002 10 | class UnusualButStillBad(NamedTuple("foo", [("x", int, "y", int)])): # SLOT002
| ^^^^^^^^^^^^^^^^^^ SLOT002 | ^^^^^^^^^^^^^^^^^^ SLOT002
10 | pass 11 | pass
|
SLOT002.py:30:7: SLOT002 Subclasses of `collections.namedtuple()` should define `__slots__`
|
30 | class UnusualButStillBad(namedtuple("foo", ["str", "int"]), NamedTuple("foo", [("x", int, "y", int)])):
| ^^^^^^^^^^^^^^^^^^ SLOT002
31 | pass
|
SLOT002.py:34:7: SLOT002 Subclasses of `collections.namedtuple()` should define `__slots__`
|
34 | class UnusualButStillBad(namedtuple("foo", ["str", "int"]), object):
| ^^^^^^^^^^^^^^^^^^ SLOT002
35 | pass
| |