Skip to content

Commit

Permalink
Ignore ClassVar annotation for RUF008, RUF009 (#4081)
Browse files Browse the repository at this point in the history
  • Loading branch information
dhruvmanila authored Apr 24, 2023
1 parent 4d3a1e0 commit 37483f3
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 38 deletions.
4 changes: 3 additions & 1 deletion crates/ruff/resources/test/fixtures/ruff/RUF008.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import typing
from dataclasses import dataclass, field
from typing import Sequence
from typing import ClassVar, Sequence

KNOWINGLY_MUTABLE_DEFAULT = []

Expand All @@ -13,6 +13,7 @@ class A:
ignored_via_comment: list[int] = [] # noqa: RUF008
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
perfectly_fine: list[int] = field(default_factory=list)
class_variable: typing.ClassVar[list[int]] = []


@dataclass
Expand All @@ -23,3 +24,4 @@ class B:
ignored_via_comment: list[int] = [] # noqa: RUF008
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
perfectly_fine: list[int] = field(default_factory=list)
class_variable: ClassVar[list[int]] = []
5 changes: 4 additions & 1 deletion crates/ruff/resources/test/fixtures/ruff/RUF009.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import typing
from dataclasses import dataclass
from typing import NamedTuple
from typing import ClassVar, NamedTuple


def default_function() -> list[int]:
Expand All @@ -13,6 +14,8 @@ class ImmutableType(NamedTuple):
@dataclass()
class A:
hidden_mutable_default: list[int] = default_function()
class_variable: typing.ClassVar[list[int]] = default_function()
another_class_var: ClassVar[list[int]] = default_function()


DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES = ImmutableType(40)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,26 @@ fn is_allowed_func(context: &Context, func: &Expr) -> bool {
})
}

/// Returns `true` if the given [`Expr`] is a `typing.ClassVar` annotation.
fn is_class_var_annotation(context: &Context, annotation: &Expr) -> bool {
let ExprKind::Subscript { value, .. } = &annotation.node else {
return false;
};
context.match_typing_expr(value, "ClassVar")
}

/// RUF009
pub fn function_call_in_dataclass_defaults(checker: &mut Checker, body: &[Stmt]) {
for statement in body {
if let StmtKind::AnnAssign {
value: Some(expr), ..
annotation,
value: Some(expr),
..
} = &statement.node
{
if is_class_var_annotation(&checker.ctx, annotation) {
continue;
}
if let ExprKind::Call { func, .. } = &expr.node {
if !is_allowed_func(&checker.ctx, func) {
checker.diagnostics.push(Diagnostic::new(
Expand All @@ -181,7 +194,10 @@ pub fn mutable_dataclass_default(checker: &mut Checker, body: &[Stmt]) {
value: Some(value),
..
} => {
if !is_immutable_annotation(&checker.ctx, annotation) && is_mutable_expr(value) {
if !is_class_var_annotation(&checker.ctx, annotation)
&& !is_immutable_annotation(&checker.ctx, annotation)
&& is_mutable_expr(value)
{
checker
.diagnostics
.push(Diagnostic::new(MutableDataclassDefault, Range::from(value)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,24 @@ RUF008.py:12:26: RUF008 Do not use mutable default values for dataclass attribut
16 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
|

RUF008.py:20:34: RUF008 Do not use mutable default values for dataclass attributes
RUF008.py:21:34: RUF008 Do not use mutable default values for dataclass attributes
|
20 | @dataclass
21 | class B:
22 | mutable_default: list[int] = []
21 | @dataclass
22 | class B:
23 | mutable_default: list[int] = []
| ^^ RUF008
23 | immutable_annotation: Sequence[int] = []
24 | without_annotation = []
24 | immutable_annotation: Sequence[int] = []
25 | without_annotation = []
|

RUF008.py:22:26: RUF008 Do not use mutable default values for dataclass attributes
RUF008.py:23:26: RUF008 Do not use mutable default values for dataclass attributes
|
22 | mutable_default: list[int] = []
23 | immutable_annotation: Sequence[int] = []
24 | without_annotation = []
23 | mutable_default: list[int] = []
24 | immutable_annotation: Sequence[int] = []
25 | without_annotation = []
| ^^ RUF008
25 | ignored_via_comment: list[int] = [] # noqa: RUF008
26 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
26 | ignored_via_comment: list[int] = [] # noqa: RUF008
27 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
|


Original file line number Diff line number Diff line change
@@ -1,42 +1,44 @@
---
source: crates/ruff/src/rules/ruff/mod.rs
---
RUF009.py:15:41: RUF009 Do not perform function call `default_function` in dataclass defaults
RUF009.py:16:41: RUF009 Do not perform function call `default_function` in dataclass defaults
|
15 | @dataclass()
16 | class A:
17 | hidden_mutable_default: list[int] = default_function()
16 | @dataclass()
17 | class A:
18 | hidden_mutable_default: list[int] = default_function()
| ^^^^^^^^^^^^^^^^^^ RUF009
19 | class_variable: typing.ClassVar[list[int]] = default_function()
20 | another_class_var: ClassVar[list[int]] = default_function()
|

RUF009.py:24:41: RUF009 Do not perform function call `default_function` in dataclass defaults
RUF009.py:27:41: RUF009 Do not perform function call `default_function` in dataclass defaults
|
24 | @dataclass
25 | class B:
26 | hidden_mutable_default: list[int] = default_function()
27 | @dataclass
28 | class B:
29 | hidden_mutable_default: list[int] = default_function()
| ^^^^^^^^^^^^^^^^^^ RUF009
27 | another_dataclass: A = A()
28 | not_optimal: ImmutableType = ImmutableType(20)
30 | another_dataclass: A = A()
31 | not_optimal: ImmutableType = ImmutableType(20)
|

RUF009.py:25:28: RUF009 Do not perform function call `A` in dataclass defaults
RUF009.py:28:28: RUF009 Do not perform function call `A` in dataclass defaults
|
25 | class B:
26 | hidden_mutable_default: list[int] = default_function()
27 | another_dataclass: A = A()
28 | class B:
29 | hidden_mutable_default: list[int] = default_function()
30 | another_dataclass: A = A()
| ^^^ RUF009
28 | not_optimal: ImmutableType = ImmutableType(20)
29 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
31 | not_optimal: ImmutableType = ImmutableType(20)
32 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
|

RUF009.py:26:34: RUF009 Do not perform function call `ImmutableType` in dataclass defaults
RUF009.py:29:34: RUF009 Do not perform function call `ImmutableType` in dataclass defaults
|
26 | hidden_mutable_default: list[int] = default_function()
27 | another_dataclass: A = A()
28 | not_optimal: ImmutableType = ImmutableType(20)
29 | hidden_mutable_default: list[int] = default_function()
30 | another_dataclass: A = A()
31 | not_optimal: ImmutableType = ImmutableType(20)
| ^^^^^^^^^^^^^^^^^ RUF009
29 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
30 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES
32 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
33 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES
|


0 comments on commit 37483f3

Please sign in to comment.