Skip to content

Commit

Permalink
Allow typing.Final for mutable-class-default annotations (RUF012)
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Jun 21, 2023
1 parent 507fee5 commit ea990f9
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 20 deletions.
6 changes: 5 additions & 1 deletion 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 = []

Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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]] = []
Expand Up @@ -55,7 +55,7 @@ use crate::rules::ruff::rules::helpers::{
/// - `flake8-bugbear.extend-immutable-calls`
#[violation]
pub struct FunctionCallInDataclassDefaultArgument {
pub name: Option<String>,
name: Option<String>,
}

impl Violation for FunctionCallInDataclassDefaultArgument {
Expand Down
8 changes: 8 additions & 0 deletions crates/ruff/src/rules/ruff/rules/helpers.rs
Expand Up @@ -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| {
Expand Down
3 changes: 2 additions & 1 deletion crates/ruff/src/rules/ruff/rules/mutable_class_default.rs
Expand Up @@ -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
Expand Down Expand Up @@ -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())
{
Expand Down
Expand Up @@ -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)
|


0 comments on commit ea990f9

Please sign in to comment.