[`flake8-slots`] Respect same-file `Enum` subclasses (#11006)

Closes https://github.com/astral-sh/ruff/issues/9890.
This commit is contained in:
Charlie Marsh 2024-04-17 21:15:52 -04:00 committed by GitHub
parent 16cc9bd78d
commit e8b1125b30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 42 additions and 28 deletions

View File

@ -11,3 +11,11 @@ from enum import Enum
class Fine(str, Enum): # Ok
__slots__ = ["foo"]
class SubEnum(Enum):
pass
class Ok(str, SubEnum): # Ok
pass

View File

@ -1,9 +1,8 @@
use ruff_python_ast::{Arguments, Expr, Stmt, StmtClassDef};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::identifier::Identifier;
use ruff_python_semantic::SemanticModel;
use ruff_python_ast::{Arguments, Expr, Stmt, StmtClassDef};
use ruff_python_semantic::{analyze, SemanticModel};
use crate::checkers::ast::Checker;
use crate::rules::flake8_slots::rules::helpers::has_slots;
@ -55,33 +54,40 @@ pub(crate) fn no_slots_in_str_subclass(checker: &mut Checker, stmt: &Stmt, class
return;
};
if is_str_subclass(bases, checker.semantic()) {
if !has_slots(&class.body) {
checker
.diagnostics
.push(Diagnostic::new(NoSlotsInStrSubclass, stmt.identifier()));
}
if !is_str_subclass(bases, checker.semantic()) {
return;
}
// Ignore subclasses of `enum.Enum` et al.
if is_enum_subclass(class, checker.semantic()) {
return;
}
if has_slots(&class.body) {
return;
}
checker
.diagnostics
.push(Diagnostic::new(NoSlotsInStrSubclass, stmt.identifier()));
}
/// Return `true` if the class is a subclass of `str`, but _not_ a subclass of `enum.Enum`,
/// `enum.IntEnum`, etc.
/// Return `true` if the class is a subclass of `str`.
fn is_str_subclass(bases: &[Expr], semantic: &SemanticModel) -> bool {
let mut is_str_subclass = false;
for base in bases {
if let Some(qualified_name) = semantic.resolve_qualified_name(base) {
match qualified_name.segments() {
["" | "builtins", "str"] => {
is_str_subclass = true;
}
["enum", "Enum" | "IntEnum" | "StrEnum" | "Flag" | "IntFlag" | "ReprEnum" | "EnumCheck"] =>
{
// Ignore enum classes.
return false;
}
_ => {}
}
}
}
is_str_subclass
bases
.iter()
.any(|base| semantic.match_builtin_expr(base, "str"))
}
/// Returns `true` if the class is an enum subclass, at any depth.
fn is_enum_subclass(class_def: &StmtClassDef, semantic: &SemanticModel) -> bool {
analyze::class::any_qualified_name(class_def, semantic, &|qualified_name| {
matches!(
qualified_name.segments(),
[
"enum",
"Enum" | "IntEnum" | "StrEnum" | "Flag" | "IntFlag" | "ReprEnum" | "EnumCheck"
]
)
})
}