Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore ClassVar annotation for RUF008, RUF009 #4081

Merged
merged 2 commits into from
Apr 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
|