-
Notifications
You must be signed in to change notification settings - Fork 2.3k
[ruff] Analyze deferred annotations before enforcing mutable-(data)class-default and function-call-in-dataclass-default-argument (RUF008,RUF009,RUF012)
#15921
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # Lint should account for deferred annotations | ||
| # See https://github.com/astral-sh/ruff/issues/15857 | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import typing | ||
| from dataclasses import dataclass | ||
|
|
||
|
|
||
| @dataclass | ||
| class Example(): | ||
| """Class that uses ClassVar.""" | ||
|
|
||
| options: ClassVar[dict[str, str]] = {} | ||
|
|
||
|
|
||
| if typing.TYPE_CHECKING: | ||
| from typing import ClassVar | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| from dataclasses import dataclass | ||
| from typing import TYPE_CHECKING | ||
|
|
||
|
|
||
| def default_function() ->list[int]: | ||
| return [] | ||
|
|
||
| @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() | ||
|
|
||
| if TYPE_CHECKING: | ||
| from typing import ClassVar |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # Lint should account for deferred annotations | ||
| # See https://github.com/astral-sh/ruff/issues/15857 | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import typing | ||
|
|
||
|
|
||
| class Example(): | ||
| """Class that uses ClassVar.""" | ||
|
|
||
| options: ClassVar[dict[str, str]] = {} | ||
|
|
||
|
|
||
| if typing.TYPE_CHECKING: | ||
| from typing import ClassVar |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| --- | ||
| source: crates/ruff_linter/src/rules/ruff/mod.rs | ||
| --- | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| --- | ||
| source: crates/ruff_linter/src/rules/ruff/mod.rs | ||
| --- | ||
| RUF009_deferred.py:10:41: RUF009 Do not perform function call `default_function` in dataclass defaults | ||
| | | ||
| 8 | @dataclass() | ||
| 9 | class A: | ||
| 10 | hidden_mutable_default: list[int] = default_function() | ||
| | ^^^^^^^^^^^^^^^^^^ RUF009 | ||
| 11 | class_variable: typing.ClassVar[list[int]] = default_function() | ||
| 12 | another_class_var: ClassVar[list[int]] = default_function() | ||
| | | ||
|
|
||
| RUF009_deferred.py:11:50: RUF009 Do not perform function call `default_function` in dataclass defaults | ||
| | | ||
| 9 | class A: | ||
| 10 | hidden_mutable_default: list[int] = default_function() | ||
| 11 | class_variable: typing.ClassVar[list[int]] = default_function() | ||
| | ^^^^^^^^^^^^^^^^^^ RUF009 | ||
| 12 | another_class_var: ClassVar[list[int]] = default_function() | ||
| | |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -40,3 +40,69 @@ RUF012.py:89:38: RUF012 Mutable class attributes should be annotated with `typin | |||||||||||||||||||||||||||||||||||||||
| 90 | | ||||||||||||||||||||||||||||||||||||||||
| 91 | from sqlmodel import SQLModel | ||||||||||||||||||||||||||||||||||||||||
| | | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| RUF012.py:114:36: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` | ||||||||||||||||||||||||||||||||||||||||
| | | ||||||||||||||||||||||||||||||||||||||||
| 112 | } | ||||||||||||||||||||||||||||||||||||||||
| 113 | | ||||||||||||||||||||||||||||||||||||||||
| 114 | mutable_default: 'list[int]' = [] | ||||||||||||||||||||||||||||||||||||||||
| | ^^ RUF012 | ||||||||||||||||||||||||||||||||||||||||
| 115 | immutable_annotation: 'Sequence[int]'= [] | ||||||||||||||||||||||||||||||||||||||||
| 116 | without_annotation = [] | ||||||||||||||||||||||||||||||||||||||||
| | | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| RUF012.py:115:44: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` | ||||||||||||||||||||||||||||||||||||||||
| | | ||||||||||||||||||||||||||||||||||||||||
| 114 | mutable_default: 'list[int]' = [] | ||||||||||||||||||||||||||||||||||||||||
| 115 | immutable_annotation: 'Sequence[int]'= [] | ||||||||||||||||||||||||||||||||||||||||
| | ^^ RUF012 | ||||||||||||||||||||||||||||||||||||||||
| 116 | without_annotation = [] | ||||||||||||||||||||||||||||||||||||||||
| 117 | class_variable: 'ClassVar[list[int]]' = [] | ||||||||||||||||||||||||||||||||||||||||
| | | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| RUF012.py:116:26: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` | ||||||||||||||||||||||||||||||||||||||||
| | | ||||||||||||||||||||||||||||||||||||||||
| 114 | mutable_default: 'list[int]' = [] | ||||||||||||||||||||||||||||||||||||||||
| 115 | immutable_annotation: 'Sequence[int]'= [] | ||||||||||||||||||||||||||||||||||||||||
| 116 | without_annotation = [] | ||||||||||||||||||||||||||||||||||||||||
| | ^^ RUF012 | ||||||||||||||||||||||||||||||||||||||||
| 117 | class_variable: 'ClassVar[list[int]]' = [] | ||||||||||||||||||||||||||||||||||||||||
| 118 | final_variable: 'Final[list[int]]' = [] | ||||||||||||||||||||||||||||||||||||||||
| | | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| RUF012.py:117:45: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` | ||||||||||||||||||||||||||||||||||||||||
| | | ||||||||||||||||||||||||||||||||||||||||
| 115 | immutable_annotation: 'Sequence[int]'= [] | ||||||||||||||||||||||||||||||||||||||||
| 116 | without_annotation = [] | ||||||||||||||||||||||||||||||||||||||||
| 117 | class_variable: 'ClassVar[list[int]]' = [] | ||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, why are we emitting an error here? It looks like it already is annotated with ClassVar?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Somehow I forgot to actually check that stringized annotations were working... and they aren't! It turns out that fixing that is gonna require other changes (and I noticed a few other affected rules). So I'm gonna leave that as a followup PR and just solve the problem in the linked issue for this PR. I did move one other related rule to
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh, and I see this isn't a regression; we also have this false negative on Probably a lot more rules should be using this helper function when they inspect annotations: ruff/crates/ruff_linter/src/checkers/ast/mod.rs Lines 453 to 471 in eb08345
(I introduced the helper in #12951 ;)
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes exactly! Originally I started to use that helpful helper, but then noticed the number of places I had to put it was sort of exploding. So I wanted to take a step back in a separate PR and either see if there's a better way or if that failed then at least separate that change from the one in this PR. |
||||||||||||||||||||||||||||||||||||||||
| | ^^ RUF012 | ||||||||||||||||||||||||||||||||||||||||
| 118 | final_variable: 'Final[list[int]]' = [] | ||||||||||||||||||||||||||||||||||||||||
| 119 | class_variable_without_subscript: 'ClassVar' = [] | ||||||||||||||||||||||||||||||||||||||||
| | | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| RUF012.py:118:42: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` | ||||||||||||||||||||||||||||||||||||||||
| | | ||||||||||||||||||||||||||||||||||||||||
| 116 | without_annotation = [] | ||||||||||||||||||||||||||||||||||||||||
| 117 | class_variable: 'ClassVar[list[int]]' = [] | ||||||||||||||||||||||||||||||||||||||||
| 118 | final_variable: 'Final[list[int]]' = [] | ||||||||||||||||||||||||||||||||||||||||
| | ^^ RUF012 | ||||||||||||||||||||||||||||||||||||||||
| 119 | class_variable_without_subscript: 'ClassVar' = [] | ||||||||||||||||||||||||||||||||||||||||
| 120 | final_variable_without_subscript: 'Final' = [] | ||||||||||||||||||||||||||||||||||||||||
| | | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| RUF012.py:119:52: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` | ||||||||||||||||||||||||||||||||||||||||
| | | ||||||||||||||||||||||||||||||||||||||||
| 117 | class_variable: 'ClassVar[list[int]]' = [] | ||||||||||||||||||||||||||||||||||||||||
| 118 | final_variable: 'Final[list[int]]' = [] | ||||||||||||||||||||||||||||||||||||||||
| 119 | class_variable_without_subscript: 'ClassVar' = [] | ||||||||||||||||||||||||||||||||||||||||
| | ^^ RUF012 | ||||||||||||||||||||||||||||||||||||||||
| 120 | final_variable_without_subscript: 'Final' = [] | ||||||||||||||||||||||||||||||||||||||||
| | | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| RUF012.py:120:49: RUF012 Mutable class attributes should be annotated with `typing.ClassVar` | ||||||||||||||||||||||||||||||||||||||||
| | | ||||||||||||||||||||||||||||||||||||||||
| 118 | final_variable: 'Final[list[int]]' = [] | ||||||||||||||||||||||||||||||||||||||||
| 119 | class_variable_without_subscript: 'ClassVar' = [] | ||||||||||||||||||||||||||||||||||||||||
| 120 | final_variable_without_subscript: 'Final' = [] | ||||||||||||||||||||||||||||||||||||||||
| | ^^ RUF012 | ||||||||||||||||||||||||||||||||||||||||
| | | ||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| --- | ||
| source: crates/ruff_linter/src/rules/ruff/mod.rs | ||
| --- | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated to this PR: It's sort of annoying that you had to change the method signature just because you can only get a read-only
Checkerhere. I think we should start refactoringCheckerso that:Checker::push_diagnosticorreport_diagnosticmethodChecker::extend_diagnosticsorreport_diagnosticsmethodChecker::diagnosticsto aRefCell<Vec<Diagnostic>>Doing so has a few advantages:
&mut Checkeronly to push diagnosticsObviously, this isn't something for this PR but maybe a fun refactor for another day ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, please. Every single time I've run into lifetime issues or borrow checker complaints, it was because of a mutable borrow of
Checker. This also would allow us to pass the checker directly into some of the helper functions, instead of having to pass five separate arguments that are all just borrowed from the checker.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like fun I'll give it a shot!