diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_slots/SLOT000.py b/crates/ruff_linter/resources/test/fixtures/flake8_slots/SLOT000.py index 425fc3b9013ef..c3bb79b633804 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_slots/SLOT000.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_slots/SLOT000.py @@ -11,3 +11,11 @@ class Good(str): # Ok class Fine(str, Enum): # Ok __slots__ = ["foo"] + + +class SubEnum(Enum): + pass + + +class Ok(str, SubEnum): # Ok + pass diff --git a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs index 4c9006ff03f79..a0ffe23e3b01f 100644 --- a/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs +++ b/crates/ruff_linter/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs @@ -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" + ] + ) + }) }