mirror of https://github.com/astral-sh/ruff
[`flake8-slots`] Respect same-file `Enum` subclasses (#11006)
Closes https://github.com/astral-sh/ruff/issues/9890.
This commit is contained in:
parent
16cc9bd78d
commit
e8b1125b30
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
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`.
|
||||
fn is_str_subclass(bases: &[Expr], semantic: &SemanticModel) -> bool {
|
||||
bases
|
||||
.iter()
|
||||
.any(|base| semantic.match_builtin_expr(base, "str"))
|
||||
}
|
||||
|
||||
/// Return `true` if the class is a subclass of `str`, but _not_ a subclass of `enum.Enum`,
|
||||
/// `enum.IntEnum`, etc.
|
||||
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
|
||||
/// 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"
|
||||
]
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue