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

[ruff_linter] ignore function call as default value for immutable annotation (RUF009) #15845

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
13 changes: 11 additions & 2 deletions crates/ruff_linter/resources/test/fixtures/ruff/RUF009.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@

def default_function() -> list[int]:
return []

def default_int() -> int:
return 1337

class ImmutableType(NamedTuple):
something: int = 8


@dataclass()
class A:
hidden_mutable_default: list[int] = default_function()
Expand Down Expand Up @@ -42,10 +42,12 @@ class A:
class B:
hidden_mutable_default: list[int] = default_function()
another_dataclass: A = A()

not_optimal: ImmutableType = ImmutableType(20)
good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES

fine_immutable_function_result: int = default_int()
fine_dataclass_function: list[int] = field(default_factory=list)


Expand Down Expand Up @@ -97,3 +99,10 @@ class DataclassWithNewTypeFields:
# No errors
e: SpecialString = SpecialString("Lorem ipsum")
f: NegativeInteger = NegativeInteger(-110)

# Test expected behavior for:
# https://github.com/astral-sh/ruff/issues/15772
@dataclass
class _:
this_is_not_fine: list[int] = f()
this_should_be: int = f()
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

def default_function() -> list[int]:
return []
def default_int() -> int:
return 1337


class ImmutableType(NamedTuple):
Expand Down Expand Up @@ -49,6 +51,7 @@ class B:
good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES

fine_immutable_function_result: int = default_int()
fine_dataclass_function: list[int] = field(default_factory=list)
attrs_factory: dict[str, str] = Factory(OrderedDict)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::name::{QualifiedName, UnqualifiedName};
use ruff_python_semantic::analyze::typing::{is_immutable_func, is_immutable_newtype_call};
use ruff_python_semantic::analyze::typing::{
is_immutable_annotation, is_immutable_func, is_immutable_newtype_call,
};
use ruff_text_size::Ranged;

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -115,6 +117,8 @@ pub(crate) fn function_call_in_dataclass_default(
.map(|target| QualifiedName::from_dotted_name(target))
.collect();

extend_immutable_calls.iter().for_each(|f| println!("{f}"));

for statement in &class_def.body {
let Stmt::AnnAssign(ast::StmtAnnAssign {
annotation,
Expand All @@ -127,7 +131,6 @@ pub(crate) fn function_call_in_dataclass_default(
let Expr::Call(ast::ExprCall { func, .. }) = expr.as_ref() else {
continue;
};

let is_field = is_dataclass_field(func, checker.semantic(), dataclass_kind);

// Non-explicit fields in an `attrs` dataclass
Expand All @@ -138,6 +141,7 @@ pub(crate) fn function_call_in_dataclass_default(

if is_field
|| is_class_var_annotation(annotation, checker.semantic())
|| is_immutable_annotation(annotation, checker.semantic(), &extend_immutable_calls)
|| is_immutable_func(func, checker.semantic(), &extend_immutable_calls)
|| is_descriptor_class(func, checker.semantic())
|| func.as_name_expr().is_some_and(|name| {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ RUF009.py:43:41: RUF009 Do not perform function call `default_function` in datac
43 | hidden_mutable_default: list[int] = default_function()
| ^^^^^^^^^^^^^^^^^^ RUF009
44 | another_dataclass: A = A()
45 | not_optimal: ImmutableType = ImmutableType(20)
|

RUF009.py:44:28: RUF009 Do not perform function call `A` in dataclass defaults
Expand All @@ -27,65 +26,74 @@ RUF009.py:44:28: RUF009 Do not perform function call `A` in dataclass defaults
43 | hidden_mutable_default: list[int] = default_function()
44 | another_dataclass: A = A()
| ^^^ RUF009
45 | not_optimal: ImmutableType = ImmutableType(20)
46 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
45 |
46 | not_optimal: ImmutableType = ImmutableType(20)
|

RUF009.py:45:34: RUF009 Do not perform function call `ImmutableType` in dataclass defaults
RUF009.py:46:34: RUF009 Do not perform function call `ImmutableType` in dataclass defaults
|
43 | hidden_mutable_default: list[int] = default_function()
44 | another_dataclass: A = A()
45 | not_optimal: ImmutableType = ImmutableType(20)
45 |
46 | not_optimal: ImmutableType = ImmutableType(20)
| ^^^^^^^^^^^^^^^^^ RUF009
46 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
47 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES
47 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
48 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES
|

RUF009.py:91:24: RUF009 Do not perform function call `ListOfStrings` in dataclass defaults
RUF009.py:93:24: RUF009 Do not perform function call `ListOfStrings` in dataclass defaults
|
89 | class DataclassWithNewTypeFields:
90 | # Errors
91 | a: ListOfStrings = ListOfStrings([])
91 | class DataclassWithNewTypeFields:
92 | # Errors
93 | a: ListOfStrings = ListOfStrings([])
| ^^^^^^^^^^^^^^^^^ RUF009
92 | b: StringsToInts = StringsToInts()
93 | c: Invalid1 = Invalid1()
94 | b: StringsToInts = StringsToInts()
95 | c: Invalid1 = Invalid1()
|

RUF009.py:92:24: RUF009 Do not perform function call `StringsToInts` in dataclass defaults
RUF009.py:94:24: RUF009 Do not perform function call `StringsToInts` in dataclass defaults
|
90 | # Errors
91 | a: ListOfStrings = ListOfStrings([])
92 | b: StringsToInts = StringsToInts()
92 | # Errors
93 | a: ListOfStrings = ListOfStrings([])
94 | b: StringsToInts = StringsToInts()
| ^^^^^^^^^^^^^^^ RUF009
93 | c: Invalid1 = Invalid1()
94 | d: Invalid2 = Invalid2()
95 | c: Invalid1 = Invalid1()
96 | d: Invalid2 = Invalid2()
|

RUF009.py:93:19: RUF009 Do not perform function call `Invalid1` in dataclass defaults
RUF009.py:95:19: RUF009 Do not perform function call `Invalid1` in dataclass defaults
|
91 | a: ListOfStrings = ListOfStrings([])
92 | b: StringsToInts = StringsToInts()
93 | c: Invalid1 = Invalid1()
93 | a: ListOfStrings = ListOfStrings([])
94 | b: StringsToInts = StringsToInts()
95 | c: Invalid1 = Invalid1()
| ^^^^^^^^^^ RUF009
94 | d: Invalid2 = Invalid2()
95 | e: Invalid3 = Invalid3()
96 | d: Invalid2 = Invalid2()
97 | e: Invalid3 = Invalid3()
|

RUF009.py:94:19: RUF009 Do not perform function call `Invalid2` in dataclass defaults
RUF009.py:96:19: RUF009 Do not perform function call `Invalid2` in dataclass defaults
|
92 | b: StringsToInts = StringsToInts()
93 | c: Invalid1 = Invalid1()
94 | d: Invalid2 = Invalid2()
94 | b: StringsToInts = StringsToInts()
95 | c: Invalid1 = Invalid1()
96 | d: Invalid2 = Invalid2()
| ^^^^^^^^^^ RUF009
95 | e: Invalid3 = Invalid3()
97 | e: Invalid3 = Invalid3()
|

RUF009.py:95:19: RUF009 Do not perform function call `Invalid3` in dataclass defaults
RUF009.py:97:19: RUF009 Do not perform function call `Invalid3` in dataclass defaults
|
93 | c: Invalid1 = Invalid1()
94 | d: Invalid2 = Invalid2()
95 | e: Invalid3 = Invalid3()
95 | c: Invalid1 = Invalid1()
96 | d: Invalid2 = Invalid2()
97 | e: Invalid3 = Invalid3()
| ^^^^^^^^^^ RUF009
96 |
97 | # No errors
98 |
99 | # No errors
|

RUF009.py:107:35: RUF009 Do not perform function call `f` in dataclass defaults
|
105 | @dataclass
106 | class _:
107 | this_is_not_fine: list[int] = f()
| ^^^ RUF009
108 | this_should_be: int = f()
|
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
snapshot_kind: text
---
RUF009_attrs.py:46:41: RUF009 Do not perform function call `default_function` in dataclass defaults
RUF009_attrs.py:48:41: RUF009 Do not perform function call `default_function` in dataclass defaults
|
44 | @define
45 | class B:
46 | hidden_mutable_default: list[int] = default_function()
46 | @define
47 | class B:
48 | hidden_mutable_default: list[int] = default_function()
| ^^^^^^^^^^^^^^^^^^ RUF009
47 | another_dataclass: A = A()
48 | not_optimal: ImmutableType = ImmutableType(20)
49 | another_dataclass: A = A()
50 | not_optimal: ImmutableType = ImmutableType(20)
|

RUF009_attrs.py:47:28: RUF009 Do not perform function call `A` in dataclass defaults
RUF009_attrs.py:49:28: RUF009 Do not perform function call `A` in dataclass defaults
|
45 | class B:
46 | hidden_mutable_default: list[int] = default_function()
47 | another_dataclass: A = A()
47 | class B:
48 | hidden_mutable_default: list[int] = default_function()
49 | another_dataclass: A = A()
| ^^^ RUF009
48 | not_optimal: ImmutableType = ImmutableType(20)
49 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
50 | not_optimal: ImmutableType = ImmutableType(20)
51 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
|

RUF009_attrs.py:48:34: RUF009 Do not perform function call `ImmutableType` in dataclass defaults
RUF009_attrs.py:50:34: RUF009 Do not perform function call `ImmutableType` in dataclass defaults
|
46 | hidden_mutable_default: list[int] = default_function()
47 | another_dataclass: A = A()
48 | not_optimal: ImmutableType = ImmutableType(20)
48 | hidden_mutable_default: list[int] = default_function()
49 | another_dataclass: A = A()
50 | not_optimal: ImmutableType = ImmutableType(20)
| ^^^^^^^^^^^^^^^^^ RUF009
49 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
50 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES
51 | good_variant: ImmutableType = DEFAULT_IMMUTABLETYPE_FOR_ALL_DATACLASSES
52 | okay_variant: A = DEFAULT_A_FOR_ALL_DATACLASSES
|
Original file line number Diff line number Diff line change
@@ -1,110 +1,4 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---
RUF009_attrs_auto_attribs.py:12:14: RUF009 Do not perform function call `foo` in dataclass defaults
|
10 | a: str = 0
11 | b = field()
12 | c: int = foo()
| ^^^^^ RUF009
13 | d = list()
|

RUF009_attrs_auto_attribs.py:20:14: RUF009 Do not perform function call `foo` in dataclass defaults
|
18 | a: str = 0
19 | b = field()
20 | c: int = foo()
| ^^^^^ RUF009
21 | d = list()
|

RUF009_attrs_auto_attribs.py:28:14: RUF009 Do not perform function call `foo` in dataclass defaults
|
26 | a: str = 0
27 | b = field()
28 | c: int = foo()
| ^^^^^ RUF009
29 | d = list()
|

RUF009_attrs_auto_attribs.py:36:14: RUF009 Do not perform function call `foo` in dataclass defaults
|
34 | a: str = 0
35 | b = field()
36 | c: int = foo()
| ^^^^^ RUF009
37 | d = list()
|

RUF009_attrs_auto_attribs.py:44:14: RUF009 Do not perform function call `foo` in dataclass defaults
|
42 | a: str = 0
43 | b = field()
44 | c: int = foo()
| ^^^^^ RUF009
45 | d = list()
|

RUF009_attrs_auto_attribs.py:52:14: RUF009 Do not perform function call `foo` in dataclass defaults
|
50 | a: str = 0
51 | b = field()
52 | c: int = foo()
| ^^^^^ RUF009
53 | d = list()
|

RUF009_attrs_auto_attribs.py:60:14: RUF009 Do not perform function call `foo` in dataclass defaults
|
58 | a: str = 0
59 | b = field()
60 | c: int = foo()
| ^^^^^ RUF009
61 | d = list()
|

RUF009_attrs_auto_attribs.py:68:14: RUF009 Do not perform function call `foo` in dataclass defaults
|
66 | a: str = 0
67 | b = field()
68 | c: int = foo()
| ^^^^^ RUF009
69 | d = list()
|

RUF009_attrs_auto_attribs.py:76:14: RUF009 Do not perform function call `foo` in dataclass defaults
|
74 | a: str = 0
75 | b = field()
76 | c: int = foo()
| ^^^^^ RUF009
77 | d = list()
|

RUF009_attrs_auto_attribs.py:92:14: RUF009 Do not perform function call `foo` in dataclass defaults
|
90 | a: str = 0
91 | b = field()
92 | c: int = foo()
| ^^^^^ RUF009
93 | d = list()
|

RUF009_attrs_auto_attribs.py:100:14: RUF009 Do not perform function call `foo` in dataclass defaults
|
98 | a: str = 0
99 | b = field()
100 | c: int = foo()
| ^^^^^ RUF009
101 | d = list()
|

RUF009_attrs_auto_attribs.py:116:14: RUF009 Do not perform function call `foo` in dataclass defaults
|
114 | a: str = 0
115 | b = field()
116 | c: int = foo()
| ^^^^^ RUF009
117 | d = list()
|
Loading