From ea990f9ac4de758b6069e4c24c1f5d9c26375a5c Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 21 Jun 2023 19:50:48 -0400 Subject: [PATCH] Allow typing.Final for mutable-class-default annotations (RUF012) --- .../resources/test/fixtures/ruff/RUF012.py | 6 +++- .../function_call_in_dataclass_default.rs | 2 +- crates/ruff/src/rules/ruff/rules/helpers.rs | 8 +++++ .../rules/ruff/rules/mutable_class_default.rs | 3 +- ..._rules__ruff__tests__RUF012_RUF012.py.snap | 34 +++++++++---------- 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/crates/ruff/resources/test/fixtures/ruff/RUF012.py b/crates/ruff/resources/test/fixtures/ruff/RUF012.py index 83cb33045b5ff3..081c13bac3a7d7 100644 --- a/crates/ruff/resources/test/fixtures/ruff/RUF012.py +++ b/crates/ruff/resources/test/fixtures/ruff/RUF012.py @@ -1,5 +1,5 @@ import typing -from typing import ClassVar, Sequence +from typing import ClassVar, Sequence, Final KNOWINGLY_MUTABLE_DEFAULT = [] @@ -10,6 +10,7 @@ class A: without_annotation = [] correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT class_variable: typing.ClassVar[list[int]] = [] + final_variable: typing.Final[list[int]] = [] class B: @@ -18,6 +19,7 @@ class B: without_annotation = [] correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT class_variable: ClassVar[list[int]] = [] + final_variable: Final[list[int]] = [] from dataclasses import dataclass, field @@ -31,6 +33,7 @@ class C: correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT perfectly_fine: list[int] = field(default_factory=list) class_variable: ClassVar[list[int]] = [] + final_variable: Final[list[int]] = [] from pydantic import BaseModel @@ -43,3 +46,4 @@ class D(BaseModel): correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT perfectly_fine: list[int] = field(default_factory=list) class_variable: ClassVar[list[int]] = [] + final_variable: Final[list[int]] = [] diff --git a/crates/ruff/src/rules/ruff/rules/function_call_in_dataclass_default.rs b/crates/ruff/src/rules/ruff/rules/function_call_in_dataclass_default.rs index f2bea235a776df..93961572ae5c59 100644 --- a/crates/ruff/src/rules/ruff/rules/function_call_in_dataclass_default.rs +++ b/crates/ruff/src/rules/ruff/rules/function_call_in_dataclass_default.rs @@ -55,7 +55,7 @@ use crate::rules::ruff::rules::helpers::{ /// - `flake8-bugbear.extend-immutable-calls` #[violation] pub struct FunctionCallInDataclassDefaultArgument { - pub name: Option, + name: Option, } impl Violation for FunctionCallInDataclassDefaultArgument { diff --git a/crates/ruff/src/rules/ruff/rules/helpers.rs b/crates/ruff/src/rules/ruff/rules/helpers.rs index 83cf0db919529f..9643508cb75684 100644 --- a/crates/ruff/src/rules/ruff/rules/helpers.rs +++ b/crates/ruff/src/rules/ruff/rules/helpers.rs @@ -18,6 +18,14 @@ pub(super) fn is_class_var_annotation(annotation: &Expr, semantic: &SemanticMode semantic.match_typing_expr(value, "ClassVar") } +/// Returns `true` if the given [`Expr`] is a `typing.Final` annotation. +pub(super) fn is_final_annotation(annotation: &Expr, semantic: &SemanticModel) -> bool { + let Expr::Subscript(ast::ExprSubscript { value, .. }) = &annotation else { + return false; + }; + semantic.match_typing_expr(value, "Final") +} + /// Returns `true` if the given class is a dataclass. pub(super) fn is_dataclass(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool { class_def.decorator_list.iter().any(|decorator| { diff --git a/crates/ruff/src/rules/ruff/rules/mutable_class_default.rs b/crates/ruff/src/rules/ruff/rules/mutable_class_default.rs index 83b02fe3c24128..2c5955e95bbf8f 100644 --- a/crates/ruff/src/rules/ruff/rules/mutable_class_default.rs +++ b/crates/ruff/src/rules/ruff/rules/mutable_class_default.rs @@ -6,7 +6,7 @@ use ruff_python_semantic::analyze::typing::{is_immutable_annotation, is_mutable_ use crate::checkers::ast::Checker; use crate::rules::ruff::rules::helpers::{ - is_class_var_annotation, is_dataclass, is_pydantic_model, + is_class_var_annotation, is_dataclass, is_final_annotation, is_pydantic_model, }; /// ## What it does @@ -56,6 +56,7 @@ pub(crate) fn mutable_class_default(checker: &mut Checker, class_def: &ast::Stmt }) => { if is_mutable_expr(value, checker.semantic()) && !is_class_var_annotation(annotation, checker.semantic()) + && !is_final_annotation(annotation, checker.semantic()) && !is_immutable_annotation(annotation, checker.semantic()) && !is_dataclass(class_def, checker.semantic()) { diff --git a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF012_RUF012.py.snap b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF012_RUF012.py.snap index 55536d8dc28e72..285c0c6acc9c12 100644 --- a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF012_RUF012.py.snap +++ b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF012_RUF012.py.snap @@ -20,33 +20,33 @@ RUF012.py:10:26: RUF012 Mutable class attributes should be annotated with `typin 12 | class_variable: typing.ClassVar[list[int]] = [] | -RUF012.py:16:34: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` +RUF012.py:17:34: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` | -15 | class B: -16 | mutable_default: list[int] = [] +16 | class B: +17 | mutable_default: list[int] = [] | ^^ RUF012 -17 | immutable_annotation: Sequence[int] = [] -18 | without_annotation = [] +18 | immutable_annotation: Sequence[int] = [] +19 | without_annotation = [] | -RUF012.py:18:26: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` +RUF012.py:19:26: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` | -16 | mutable_default: list[int] = [] -17 | immutable_annotation: Sequence[int] = [] -18 | without_annotation = [] +17 | mutable_default: list[int] = [] +18 | immutable_annotation: Sequence[int] = [] +19 | without_annotation = [] | ^^ RUF012 -19 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT -20 | class_variable: ClassVar[list[int]] = [] +20 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT +21 | class_variable: ClassVar[list[int]] = [] | -RUF012.py:30:26: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` +RUF012.py:32:26: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` | -28 | mutable_default: list[int] = [] -29 | immutable_annotation: Sequence[int] = [] -30 | without_annotation = [] +30 | mutable_default: list[int] = [] +31 | immutable_annotation: Sequence[int] = [] +32 | without_annotation = [] | ^^ RUF012 -31 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT -32 | perfectly_fine: list[int] = field(default_factory=list) +33 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT +34 | perfectly_fine: list[int] = field(default_factory=list) |