From 0bf7683a3f1f5fec497ae169c848992b3aa53ea1 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 18 Dec 2023 11:19:07 -0500 Subject: [PATCH] Avoid `mutable-class-default` violations for Pydantic subclasses (#9187) Only applies to subclasses defined within the same file, as elsewhere. See: https://github.com/astral-sh/ruff/issues/5243#issuecomment-1860776975. --- .../resources/test/fixtures/ruff/RUF012.py | 8 ++++++ .../src/rules/ruff/rules/helpers.rs | 25 +++++++------------ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF012.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF012.py index aa9d54d8b7bdd..c1e84fb080b28 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF012.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF012.py @@ -59,3 +59,11 @@ class F(BaseSettings): without_annotation = [] class_variable: ClassVar[list[int]] = [] final_variable: Final[list[int]] = [] + + +class G(F): + mutable_default: list[int] = [] + immutable_annotation: Sequence[int] = [] + without_annotation = [] + class_variable: ClassVar[list[int]] = [] + final_variable: Final[list[int]] = [] diff --git a/crates/ruff_linter/src/rules/ruff/rules/helpers.rs b/crates/ruff_linter/src/rules/ruff/rules/helpers.rs index 6cf01db9db998..e7b4bfabc54a3 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/helpers.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/helpers.rs @@ -1,7 +1,6 @@ -use ruff_python_ast::{self as ast, Arguments, Expr}; - use ruff_python_ast::helpers::{map_callable, map_subscript}; -use ruff_python_semantic::{BindingKind, SemanticModel}; +use ruff_python_ast::{self as ast, Expr}; +use ruff_python_semantic::{analyze, BindingKind, SemanticModel}; /// Return `true` if the given [`Expr`] is a special class attribute, like `__slots__`. /// @@ -57,19 +56,13 @@ pub(super) fn has_default_copy_semantics( class_def: &ast::StmtClassDef, semantic: &SemanticModel, ) -> bool { - let Some(Arguments { args: bases, .. }) = class_def.arguments.as_deref() else { - return false; - }; - - bases.iter().any(|expr| { - semantic.resolve_call_path(expr).is_some_and(|call_path| { - matches!( - call_path.as_slice(), - ["pydantic", "BaseModel" | "BaseSettings"] - | ["pydantic_settings", "BaseSettings"] - | ["msgspec", "Struct"] - ) - }) + analyze::class::any_over_body(class_def, semantic, &|call_path| { + matches!( + call_path.as_slice(), + ["pydantic", "BaseModel" | "BaseSettings"] + | ["pydantic_settings", "BaseSettings"] + | ["msgspec", "Struct"] + ) }) }