mirror of https://github.com/astral-sh/ruff
[`ruff`] Fix `RUF033` breaking with named default expressions (#19115)
## Summary
The generated fix for `RUF033` would cause a syntax error for named
expressions as parameter defaults.
```python
from dataclasses import InitVar, dataclass
@dataclass
class Foo:
def __post_init__(self, bar: int = (x := 1)) -> None:
pass
```
would be turned into
```python
from dataclasses import InitVar, dataclass
@dataclass
class Foo:
x: InitVar[int] = x := 1
def __post_init__(self, bar: int = (x := 1)) -> None:
pass
```
instead of the syntactically correct
```python
# ...
x: InitVar[int] = (x := 1)
# ...
```
## Test Plan
Test reproducer (plus some extra tests) have been added to the test
suite.
## Related
Fixes: https://github.com/astral-sh/ruff/issues/18950
This commit is contained in:
parent
39eb0f6c6c
commit
1079975b35
|
|
@ -65,3 +65,62 @@ class Foo:
|
||||||
bar = "should've used attrs"
|
bar = "should've used attrs"
|
||||||
|
|
||||||
def __post_init__(self, bar: str = "ahhh", baz: str = "hmm") -> None: ...
|
def __post_init__(self, bar: str = "ahhh", baz: str = "hmm") -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
# https://github.com/astral-sh/ruff/issues/18950
|
||||||
|
@dataclass
|
||||||
|
class Foo:
|
||||||
|
def __post_init__(self, bar: int = (x := 1)) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Foo:
|
||||||
|
def __post_init__(
|
||||||
|
self,
|
||||||
|
bar: int = (x := 1) # comment
|
||||||
|
,
|
||||||
|
baz: int = (y := 2), # comment
|
||||||
|
foo = (a := 1) # comment
|
||||||
|
,
|
||||||
|
faz = (b := 2), # comment
|
||||||
|
) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Foo:
|
||||||
|
def __post_init__(
|
||||||
|
self,
|
||||||
|
bar: int = 1, # comment
|
||||||
|
baz: int = 2, # comment
|
||||||
|
) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Foo:
|
||||||
|
def __post_init__(
|
||||||
|
self,
|
||||||
|
arg1: int = (1) # comment
|
||||||
|
,
|
||||||
|
arg2: int = ((1)) # comment
|
||||||
|
,
|
||||||
|
arg2: int = (i for i in range(10)) # comment
|
||||||
|
,
|
||||||
|
) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# makes little sense, but is valid syntax
|
||||||
|
def fun_with_python_syntax():
|
||||||
|
@dataclass
|
||||||
|
class Foo:
|
||||||
|
def __post_init__(
|
||||||
|
self,
|
||||||
|
bar: (int) = (yield from range(5)) # comment
|
||||||
|
,
|
||||||
|
) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
return Foo
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use anyhow::Context;
|
||||||
|
|
||||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
|
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||||
use ruff_python_semantic::{Scope, ScopeKind};
|
use ruff_python_semantic::{Scope, ScopeKind};
|
||||||
use ruff_python_trivia::{indentation_at_offset, textwrap};
|
use ruff_python_trivia::{indentation_at_offset, textwrap};
|
||||||
use ruff_source_file::LineRanges;
|
use ruff_source_file::LineRanges;
|
||||||
|
|
@ -117,13 +118,7 @@ pub(crate) fn post_init_default(checker: &Checker, function_def: &ast::StmtFunct
|
||||||
|
|
||||||
if !stopped_fixes {
|
if !stopped_fixes {
|
||||||
diagnostic.try_set_fix(|| {
|
diagnostic.try_set_fix(|| {
|
||||||
use_initvar(
|
use_initvar(current_scope, function_def, parameter, default, checker)
|
||||||
current_scope,
|
|
||||||
function_def,
|
|
||||||
¶meter.parameter,
|
|
||||||
default,
|
|
||||||
checker,
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
// Need to stop fixes as soon as there is a parameter we cannot fix.
|
// Need to stop fixes as soon as there is a parameter we cannot fix.
|
||||||
// Otherwise, we risk a syntax error (a parameter without a default
|
// Otherwise, we risk a syntax error (a parameter without a default
|
||||||
|
|
@ -138,10 +133,11 @@ pub(crate) fn post_init_default(checker: &Checker, function_def: &ast::StmtFunct
|
||||||
fn use_initvar(
|
fn use_initvar(
|
||||||
current_scope: &Scope,
|
current_scope: &Scope,
|
||||||
post_init_def: &ast::StmtFunctionDef,
|
post_init_def: &ast::StmtFunctionDef,
|
||||||
parameter: &ast::Parameter,
|
parameter_with_default: &ast::ParameterWithDefault,
|
||||||
default: &ast::Expr,
|
default: &ast::Expr,
|
||||||
checker: &Checker,
|
checker: &Checker,
|
||||||
) -> anyhow::Result<Fix> {
|
) -> anyhow::Result<Fix> {
|
||||||
|
let parameter = ¶meter_with_default.parameter;
|
||||||
if current_scope.has(¶meter.name) {
|
if current_scope.has(¶meter.name) {
|
||||||
return Err(anyhow::anyhow!(
|
return Err(anyhow::anyhow!(
|
||||||
"Cannot add a `{}: InitVar` field to the class body, as a field by that name already exists",
|
"Cannot add a `{}: InitVar` field to the class body, as a field by that name already exists",
|
||||||
|
|
@ -157,17 +153,25 @@ fn use_initvar(
|
||||||
checker.semantic(),
|
checker.semantic(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let locator = checker.locator();
|
||||||
|
|
||||||
|
let default_loc = parenthesized_range(
|
||||||
|
default.into(),
|
||||||
|
parameter_with_default.into(),
|
||||||
|
checker.comment_ranges(),
|
||||||
|
checker.source(),
|
||||||
|
)
|
||||||
|
.unwrap_or(default.range());
|
||||||
|
|
||||||
// Delete the default value. For example,
|
// Delete the default value. For example,
|
||||||
// - def __post_init__(self, foo: int = 0) -> None: ...
|
// - def __post_init__(self, foo: int = 0) -> None: ...
|
||||||
// + def __post_init__(self, foo: int) -> None: ...
|
// + def __post_init__(self, foo: int) -> None: ...
|
||||||
let default_edit = Edit::deletion(parameter.end(), default.end());
|
let default_edit = Edit::deletion(parameter.end(), default_loc.end());
|
||||||
|
|
||||||
// Add `dataclasses.InitVar` field to class body.
|
// Add `dataclasses.InitVar` field to class body.
|
||||||
let locator = checker.locator();
|
|
||||||
|
|
||||||
let content = {
|
let content = {
|
||||||
|
let default = locator.slice(default_loc);
|
||||||
let parameter_name = locator.slice(¶meter.name);
|
let parameter_name = locator.slice(¶meter.name);
|
||||||
let default = locator.slice(default);
|
|
||||||
let line_ending = checker.stylist().line_ending().as_str();
|
let line_ending = checker.stylist().line_ending().as_str();
|
||||||
|
|
||||||
if let Some(annotation) = ¶meter
|
if let Some(annotation) = ¶meter
|
||||||
|
|
|
||||||
|
|
@ -156,3 +156,281 @@ RUF033.py:67:59: RUF033 `__post_init__` method with argument defaults
|
||||||
| ^^^^^ RUF033
|
| ^^^^^ RUF033
|
||||||
|
|
|
|
||||||
= help: Use `dataclasses.InitVar` instead
|
= help: Use `dataclasses.InitVar` instead
|
||||||
|
|
||||||
|
RUF033.py:73:41: RUF033 [*] `__post_init__` method with argument defaults
|
||||||
|
|
|
||||||
|
71 | @dataclass
|
||||||
|
72 | class Foo:
|
||||||
|
73 | def __post_init__(self, bar: int = (x := 1)) -> None:
|
||||||
|
| ^^^^^^ RUF033
|
||||||
|
74 | pass
|
||||||
|
|
|
||||||
|
= help: Use `dataclasses.InitVar` instead
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
70 70 | # https://github.com/astral-sh/ruff/issues/18950
|
||||||
|
71 71 | @dataclass
|
||||||
|
72 72 | class Foo:
|
||||||
|
73 |- def __post_init__(self, bar: int = (x := 1)) -> None:
|
||||||
|
73 |+ bar: InitVar[int] = (x := 1)
|
||||||
|
74 |+ def __post_init__(self, bar: int) -> None:
|
||||||
|
74 75 | pass
|
||||||
|
75 76 |
|
||||||
|
76 77 |
|
||||||
|
|
||||||
|
RUF033.py:81:21: RUF033 [*] `__post_init__` method with argument defaults
|
||||||
|
|
|
||||||
|
79 | def __post_init__(
|
||||||
|
80 | self,
|
||||||
|
81 | bar: int = (x := 1) # comment
|
||||||
|
| ^^^^^^ RUF033
|
||||||
|
82 | ,
|
||||||
|
83 | baz: int = (y := 2), # comment
|
||||||
|
|
|
||||||
|
= help: Use `dataclasses.InitVar` instead
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
76 76 |
|
||||||
|
77 77 | @dataclass
|
||||||
|
78 78 | class Foo:
|
||||||
|
79 |+ bar: InitVar[int] = (x := 1)
|
||||||
|
79 80 | def __post_init__(
|
||||||
|
80 81 | self,
|
||||||
|
81 |- bar: int = (x := 1) # comment
|
||||||
|
82 |+ bar: int # comment
|
||||||
|
82 83 | ,
|
||||||
|
83 84 | baz: int = (y := 2), # comment
|
||||||
|
84 85 | foo = (a := 1) # comment
|
||||||
|
|
||||||
|
RUF033.py:83:21: RUF033 [*] `__post_init__` method with argument defaults
|
||||||
|
|
|
||||||
|
81 | bar: int = (x := 1) # comment
|
||||||
|
82 | ,
|
||||||
|
83 | baz: int = (y := 2), # comment
|
||||||
|
| ^^^^^^ RUF033
|
||||||
|
84 | foo = (a := 1) # comment
|
||||||
|
85 | ,
|
||||||
|
|
|
||||||
|
= help: Use `dataclasses.InitVar` instead
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
76 76 |
|
||||||
|
77 77 | @dataclass
|
||||||
|
78 78 | class Foo:
|
||||||
|
79 |+ baz: InitVar[int] = (y := 2)
|
||||||
|
79 80 | def __post_init__(
|
||||||
|
80 81 | self,
|
||||||
|
81 82 | bar: int = (x := 1) # comment
|
||||||
|
82 83 | ,
|
||||||
|
83 |- baz: int = (y := 2), # comment
|
||||||
|
84 |+ baz: int, # comment
|
||||||
|
84 85 | foo = (a := 1) # comment
|
||||||
|
85 86 | ,
|
||||||
|
86 87 | faz = (b := 2), # comment
|
||||||
|
|
||||||
|
RUF033.py:84:16: RUF033 [*] `__post_init__` method with argument defaults
|
||||||
|
|
|
||||||
|
82 | ,
|
||||||
|
83 | baz: int = (y := 2), # comment
|
||||||
|
84 | foo = (a := 1) # comment
|
||||||
|
| ^^^^^^ RUF033
|
||||||
|
85 | ,
|
||||||
|
86 | faz = (b := 2), # comment
|
||||||
|
|
|
||||||
|
= help: Use `dataclasses.InitVar` instead
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
76 76 |
|
||||||
|
77 77 | @dataclass
|
||||||
|
78 78 | class Foo:
|
||||||
|
79 |+ foo: InitVar = (a := 1)
|
||||||
|
79 80 | def __post_init__(
|
||||||
|
80 81 | self,
|
||||||
|
81 82 | bar: int = (x := 1) # comment
|
||||||
|
82 83 | ,
|
||||||
|
83 84 | baz: int = (y := 2), # comment
|
||||||
|
84 |- foo = (a := 1) # comment
|
||||||
|
85 |+ foo # comment
|
||||||
|
85 86 | ,
|
||||||
|
86 87 | faz = (b := 2), # comment
|
||||||
|
87 88 | ) -> None:
|
||||||
|
|
||||||
|
RUF033.py:86:16: RUF033 [*] `__post_init__` method with argument defaults
|
||||||
|
|
|
||||||
|
84 | foo = (a := 1) # comment
|
||||||
|
85 | ,
|
||||||
|
86 | faz = (b := 2), # comment
|
||||||
|
| ^^^^^^ RUF033
|
||||||
|
87 | ) -> None:
|
||||||
|
88 | pass
|
||||||
|
|
|
||||||
|
= help: Use `dataclasses.InitVar` instead
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
76 76 |
|
||||||
|
77 77 | @dataclass
|
||||||
|
78 78 | class Foo:
|
||||||
|
79 |+ faz: InitVar = (b := 2)
|
||||||
|
79 80 | def __post_init__(
|
||||||
|
80 81 | self,
|
||||||
|
81 82 | bar: int = (x := 1) # comment
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
83 84 | baz: int = (y := 2), # comment
|
||||||
|
84 85 | foo = (a := 1) # comment
|
||||||
|
85 86 | ,
|
||||||
|
86 |- faz = (b := 2), # comment
|
||||||
|
87 |+ faz, # comment
|
||||||
|
87 88 | ) -> None:
|
||||||
|
88 89 | pass
|
||||||
|
89 90 |
|
||||||
|
|
||||||
|
RUF033.py:95:20: RUF033 [*] `__post_init__` method with argument defaults
|
||||||
|
|
|
||||||
|
93 | def __post_init__(
|
||||||
|
94 | self,
|
||||||
|
95 | bar: int = 1, # comment
|
||||||
|
| ^ RUF033
|
||||||
|
96 | baz: int = 2, # comment
|
||||||
|
97 | ) -> None:
|
||||||
|
|
|
||||||
|
= help: Use `dataclasses.InitVar` instead
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
90 90 |
|
||||||
|
91 91 | @dataclass
|
||||||
|
92 92 | class Foo:
|
||||||
|
93 |+ bar: InitVar[int] = 1
|
||||||
|
93 94 | def __post_init__(
|
||||||
|
94 95 | self,
|
||||||
|
95 |- bar: int = 1, # comment
|
||||||
|
96 |+ bar: int, # comment
|
||||||
|
96 97 | baz: int = 2, # comment
|
||||||
|
97 98 | ) -> None:
|
||||||
|
98 99 | pass
|
||||||
|
|
||||||
|
RUF033.py:96:20: RUF033 [*] `__post_init__` method with argument defaults
|
||||||
|
|
|
||||||
|
94 | self,
|
||||||
|
95 | bar: int = 1, # comment
|
||||||
|
96 | baz: int = 2, # comment
|
||||||
|
| ^ RUF033
|
||||||
|
97 | ) -> None:
|
||||||
|
98 | pass
|
||||||
|
|
|
||||||
|
= help: Use `dataclasses.InitVar` instead
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
90 90 |
|
||||||
|
91 91 | @dataclass
|
||||||
|
92 92 | class Foo:
|
||||||
|
93 |+ baz: InitVar[int] = 2
|
||||||
|
93 94 | def __post_init__(
|
||||||
|
94 95 | self,
|
||||||
|
95 96 | bar: int = 1, # comment
|
||||||
|
96 |- baz: int = 2, # comment
|
||||||
|
97 |+ baz: int, # comment
|
||||||
|
97 98 | ) -> None:
|
||||||
|
98 99 | pass
|
||||||
|
99 100 |
|
||||||
|
|
||||||
|
RUF033.py:105:22: RUF033 [*] `__post_init__` method with argument defaults
|
||||||
|
|
|
||||||
|
103 | def __post_init__(
|
||||||
|
104 | self,
|
||||||
|
105 | arg1: int = (1) # comment
|
||||||
|
| ^ RUF033
|
||||||
|
106 | ,
|
||||||
|
107 | arg2: int = ((1)) # comment
|
||||||
|
|
|
||||||
|
= help: Use `dataclasses.InitVar` instead
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
100 100 |
|
||||||
|
101 101 | @dataclass
|
||||||
|
102 102 | class Foo:
|
||||||
|
103 |+ arg1: InitVar[int] = (1)
|
||||||
|
103 104 | def __post_init__(
|
||||||
|
104 105 | self,
|
||||||
|
105 |- arg1: int = (1) # comment
|
||||||
|
106 |+ arg1: int # comment
|
||||||
|
106 107 | ,
|
||||||
|
107 108 | arg2: int = ((1)) # comment
|
||||||
|
108 109 | ,
|
||||||
|
|
||||||
|
RUF033.py:107:23: RUF033 [*] `__post_init__` method with argument defaults
|
||||||
|
|
|
||||||
|
105 | arg1: int = (1) # comment
|
||||||
|
106 | ,
|
||||||
|
107 | arg2: int = ((1)) # comment
|
||||||
|
| ^ RUF033
|
||||||
|
108 | ,
|
||||||
|
109 | arg2: int = (i for i in range(10)) # comment
|
||||||
|
|
|
||||||
|
= help: Use `dataclasses.InitVar` instead
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
100 100 |
|
||||||
|
101 101 | @dataclass
|
||||||
|
102 102 | class Foo:
|
||||||
|
103 |+ arg2: InitVar[int] = ((1))
|
||||||
|
103 104 | def __post_init__(
|
||||||
|
104 105 | self,
|
||||||
|
105 106 | arg1: int = (1) # comment
|
||||||
|
106 107 | ,
|
||||||
|
107 |- arg2: int = ((1)) # comment
|
||||||
|
108 |+ arg2: int # comment
|
||||||
|
108 109 | ,
|
||||||
|
109 110 | arg2: int = (i for i in range(10)) # comment
|
||||||
|
110 111 | ,
|
||||||
|
|
||||||
|
RUF033.py:109:21: RUF033 [*] `__post_init__` method with argument defaults
|
||||||
|
|
|
||||||
|
107 | arg2: int = ((1)) # comment
|
||||||
|
108 | ,
|
||||||
|
109 | arg2: int = (i for i in range(10)) # comment
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^ RUF033
|
||||||
|
110 | ,
|
||||||
|
111 | ) -> None:
|
||||||
|
|
|
||||||
|
= help: Use `dataclasses.InitVar` instead
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
100 100 |
|
||||||
|
101 101 | @dataclass
|
||||||
|
102 102 | class Foo:
|
||||||
|
103 |+ arg2: InitVar[int] = (i for i in range(10))
|
||||||
|
103 104 | def __post_init__(
|
||||||
|
104 105 | self,
|
||||||
|
105 106 | arg1: int = (1) # comment
|
||||||
|
106 107 | ,
|
||||||
|
107 108 | arg2: int = ((1)) # comment
|
||||||
|
108 109 | ,
|
||||||
|
109 |- arg2: int = (i for i in range(10)) # comment
|
||||||
|
110 |+ arg2: int # comment
|
||||||
|
110 111 | ,
|
||||||
|
111 112 | ) -> None:
|
||||||
|
112 113 | pass
|
||||||
|
|
||||||
|
RUF033.py:121:27: RUF033 [*] `__post_init__` method with argument defaults
|
||||||
|
|
|
||||||
|
119 | def __post_init__(
|
||||||
|
120 | self,
|
||||||
|
121 | bar: (int) = (yield from range(5)) # comment
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^ RUF033
|
||||||
|
122 | ,
|
||||||
|
123 | ) -> None:
|
||||||
|
|
|
||||||
|
= help: Use `dataclasses.InitVar` instead
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
116 116 | def fun_with_python_syntax():
|
||||||
|
117 117 | @dataclass
|
||||||
|
118 118 | class Foo:
|
||||||
|
119 |+ bar: InitVar[int] = (yield from range(5))
|
||||||
|
119 120 | def __post_init__(
|
||||||
|
120 121 | self,
|
||||||
|
121 |- bar: (int) = (yield from range(5)) # comment
|
||||||
|
122 |+ bar: (int) # comment
|
||||||
|
122 123 | ,
|
||||||
|
123 124 | ) -> None:
|
||||||
|
124 125 | ...
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue