Skip to content

Commit 37483f3

Browse files
authored
Ignore ClassVar annotation for RUF008, RUF009 (#4081)
1 parent 4d3a1e0 commit 37483f3

5 files changed

Lines changed: 61 additions & 38 deletions

File tree

crates/ruff/resources/test/fixtures/ruff/RUF008.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import typing
22
from dataclasses import dataclass, field
3-
from typing import Sequence
3+
from typing import ClassVar, Sequence
44

55
KNOWINGLY_MUTABLE_DEFAULT = []
66

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

1718

1819
@dataclass
@@ -23,3 +24,4 @@ class B:
2324
ignored_via_comment: list[int] = [] # noqa: RUF008
2425
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
2526
perfectly_fine: list[int] = field(default_factory=list)
27+
class_variable: ClassVar[list[int]] = []

crates/ruff/resources/test/fixtures/ruff/RUF009.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import typing
12
from dataclasses import dataclass
2-
from typing import NamedTuple
3+
from typing import ClassVar, NamedTuple
34

45

56
def default_function() -> list[int]:
@@ -13,6 +14,8 @@ class ImmutableType(NamedTuple):
1314
@dataclass()
1415
class A:
1516
hidden_mutable_default: list[int] = default_function()
17+
class_variable: typing.ClassVar[list[int]] = default_function()
18+
another_class_var: ClassVar[list[int]] = default_function()
1619

1720

1821
DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES = ImmutableType(40)

crates/ruff/src/rules/ruff/rules/mutable_defaults_in_dataclass_fields.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,26 @@ fn is_allowed_func(context: &Context, func: &Expr) -> bool {
151151
})
152152
}
153153

154+
/// Returns `true` if the given [`Expr`] is a `typing.ClassVar` annotation.
155+
fn is_class_var_annotation(context: &Context, annotation: &Expr) -> bool {
156+
let ExprKind::Subscript { value, .. } = &annotation.node else {
157+
return false;
158+
};
159+
context.match_typing_expr(value, "ClassVar")
160+
}
161+
154162
/// RUF009
155163
pub fn function_call_in_dataclass_defaults(checker: &mut Checker, body: &[Stmt]) {
156164
for statement in body {
157165
if let StmtKind::AnnAssign {
158-
value: Some(expr), ..
166+
annotation,
167+
value: Some(expr),
168+
..
159169
} = &statement.node
160170
{
171+
if is_class_var_annotation(&checker.ctx, annotation) {
172+
continue;
173+
}
161174
if let ExprKind::Call { func, .. } = &expr.node {
162175
if !is_allowed_func(&checker.ctx, func) {
163176
checker.diagnostics.push(Diagnostic::new(
@@ -181,7 +194,10 @@ pub fn mutable_dataclass_default(checker: &mut Checker, body: &[Stmt]) {
181194
value: Some(value),
182195
..
183196
} => {
184-
if !is_immutable_annotation(&checker.ctx, annotation) && is_mutable_expr(value) {
197+
if !is_class_var_annotation(&checker.ctx, annotation)
198+
&& !is_immutable_annotation(&checker.ctx, annotation)
199+
&& is_mutable_expr(value)
200+
{
185201
checker
186202
.diagnostics
187203
.push(Diagnostic::new(MutableDataclassDefault, Range::from(value)));

crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF008_RUF008.py.snap

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,24 @@ RUF008.py:12:26: RUF008 Do not use mutable default values for dataclass attribut
2121
16 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
2222
|
2323

24-
RUF008.py:20:34: RUF008 Do not use mutable default values for dataclass attributes
24+
RUF008.py:21:34: RUF008 Do not use mutable default values for dataclass attributes
2525
|
26-
20 | @dataclass
27-
21 | class B:
28-
22 | mutable_default: list[int] = []
26+
21 | @dataclass
27+
22 | class B:
28+
23 | mutable_default: list[int] = []
2929
| ^^ RUF008
30-
23 | immutable_annotation: Sequence[int] = []
31-
24 | without_annotation = []
30+
24 | immutable_annotation: Sequence[int] = []
31+
25 | without_annotation = []
3232
|
3333

34-
RUF008.py:22:26: RUF008 Do not use mutable default values for dataclass attributes
34+
RUF008.py:23:26: RUF008 Do not use mutable default values for dataclass attributes
3535
|
36-
22 | mutable_default: list[int] = []
37-
23 | immutable_annotation: Sequence[int] = []
38-
24 | without_annotation = []
36+
23 | mutable_default: list[int] = []
37+
24 | immutable_annotation: Sequence[int] = []
38+
25 | without_annotation = []
3939
| ^^ RUF008
40-
25 | ignored_via_comment: list[int] = [] # noqa: RUF008
41-
26 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
40+
26 | ignored_via_comment: list[int] = [] # noqa: RUF008
41+
27 | correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
4242
|
4343

4444

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,44 @@
11
---
22
source: crates/ruff/src/rules/ruff/mod.rs
33
---
4-
RUF009.py:15:41: RUF009 Do not perform function call `default_function` in dataclass defaults
4+
RUF009.py:16:41: RUF009 Do not perform function call `default_function` in dataclass defaults
55
|
6-
15 | @dataclass()
7-
16 | class A:
8-
17 | hidden_mutable_default: list[int] = default_function()
6+
16 | @dataclass()
7+
17 | class A:
8+
18 | hidden_mutable_default: list[int] = default_function()
99
| ^^^^^^^^^^^^^^^^^^ RUF009
10+
19 | class_variable: typing.ClassVar[list[int]] = default_function()
11+
20 | another_class_var: ClassVar[list[int]] = default_function()
1012
|
1113

12-
RUF009.py:24:41: RUF009 Do not perform function call `default_function` in dataclass defaults
14+
RUF009.py:27:41: RUF009 Do not perform function call `default_function` in dataclass defaults
1315
|
14-
24 | @dataclass
15-
25 | class B:
16-
26 | hidden_mutable_default: list[int] = default_function()
16+
27 | @dataclass
17+
28 | class B:
18+
29 | hidden_mutable_default: list[int] = default_function()
1719
| ^^^^^^^^^^^^^^^^^^ RUF009
18-
27 | another_dataclass: A = A()
19-
28 | not_optimal: ImmutableType = ImmutableType(20)
20+
30 | another_dataclass: A = A()
21+
31 | not_optimal: ImmutableType = ImmutableType(20)
2022
|
2123

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

32-
RUF009.py:26:34: RUF009 Do not perform function call `ImmutableType` in dataclass defaults
34+
RUF009.py:29:34: RUF009 Do not perform function call `ImmutableType` in dataclass defaults
3335
|
34-
26 | hidden_mutable_default: list[int] = default_function()
35-
27 | another_dataclass: A = A()
36-
28 | not_optimal: ImmutableType = ImmutableType(20)
36+
29 | hidden_mutable_default: list[int] = default_function()
37+
30 | another_dataclass: A = A()
38+
31 | not_optimal: ImmutableType = ImmutableType(20)
3739
| ^^^^^^^^^^^^^^^^^ RUF009
38-
29 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
39-
30 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES
40+
32 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
41+
33 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES
4042
|
4143

4244

0 commit comments

Comments
 (0)