From af2a087806cfd27ff4c431823e04a362389b93ab Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 13 Jul 2023 16:12:16 -0400 Subject: [PATCH] Ignore `Enum`-and-`str` subclasses for slots enforcement (#5749) ## Summary Matches the behavior of the upstream plugin. Closes #5748. --- .../test/fixtures/flake8_slots/SLOT000.py | 7 ++++ .../rules/no_slots_in_str_subclass.rs | 34 ++++++++++++++----- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/flake8_slots/SLOT000.py b/crates/ruff/resources/test/fixtures/flake8_slots/SLOT000.py index 5436ad13caa88..425fc3b9013ef 100644 --- a/crates/ruff/resources/test/fixtures/flake8_slots/SLOT000.py +++ b/crates/ruff/resources/test/fixtures/flake8_slots/SLOT000.py @@ -4,3 +4,10 @@ class Bad(str): # SLOT000 class Good(str): # Ok __slots__ = ["foo"] + + +from enum import Enum + + +class Fine(str, Enum): # Ok + __slots__ = ["foo"] diff --git a/crates/ruff/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs b/crates/ruff/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs index 2df55351886bd..8a486b70e5b37 100644 --- a/crates/ruff/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs +++ b/crates/ruff/src/rules/flake8_slots/rules/no_slots_in_str_subclass.rs @@ -1,8 +1,9 @@ -use rustpython_parser::ast::{Stmt, StmtClassDef}; +use rustpython_parser::ast::{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 crate::checkers::ast::Checker; use crate::rules::flake8_slots::rules::helpers::has_slots; @@ -50,14 +51,7 @@ impl Violation for NoSlotsInStrSubclass { /// SLOT000 pub(crate) fn no_slots_in_str_subclass(checker: &mut Checker, stmt: &Stmt, class: &StmtClassDef) { - if class.bases.iter().any(|base| { - checker - .semantic() - .resolve_call_path(base) - .map_or(false, |call_path| { - matches!(call_path.as_slice(), ["" | "builtins", "str"]) - }) - }) { + if is_str_subclass(&class.bases, checker.semantic()) { if !has_slots(&class.body) { checker .diagnostics @@ -65,3 +59,25 @@ pub(crate) fn no_slots_in_str_subclass(checker: &mut Checker, stmt: &Stmt, class } } } + +/// 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], model: &SemanticModel) -> bool { + let mut is_str_subclass = false; + for base in bases { + if let Some(call_path) = model.resolve_call_path(base) { + match call_path.as_slice() { + ["" | "builtins", "str"] => { + is_str_subclass = true; + } + ["enum", "Enum" | "IntEnum" | "StrEnum" | "Flag" | "IntFlag" | "ReprEnum" | "EnumCheck"] => + { + // Ignore enum classes. + return false; + } + _ => {} + } + } + } + is_str_subclass +}