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"
|
||||
|
||||
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_python_ast as ast;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_semantic::{Scope, ScopeKind};
|
||||
use ruff_python_trivia::{indentation_at_offset, textwrap};
|
||||
use ruff_source_file::LineRanges;
|
||||
|
|
@ -117,13 +118,7 @@ pub(crate) fn post_init_default(checker: &Checker, function_def: &ast::StmtFunct
|
|||
|
||||
if !stopped_fixes {
|
||||
diagnostic.try_set_fix(|| {
|
||||
use_initvar(
|
||||
current_scope,
|
||||
function_def,
|
||||
¶meter.parameter,
|
||||
default,
|
||||
checker,
|
||||
)
|
||||
use_initvar(current_scope, function_def, parameter, default, checker)
|
||||
});
|
||||
// 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
|
||||
|
|
@ -138,10 +133,11 @@ pub(crate) fn post_init_default(checker: &Checker, function_def: &ast::StmtFunct
|
|||
fn use_initvar(
|
||||
current_scope: &Scope,
|
||||
post_init_def: &ast::StmtFunctionDef,
|
||||
parameter: &ast::Parameter,
|
||||
parameter_with_default: &ast::ParameterWithDefault,
|
||||
default: &ast::Expr,
|
||||
checker: &Checker,
|
||||
) -> anyhow::Result<Fix> {
|
||||
let parameter = ¶meter_with_default.parameter;
|
||||
if current_scope.has(¶meter.name) {
|
||||
return Err(anyhow::anyhow!(
|
||||
"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(),
|
||||
)?;
|
||||
|
||||
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,
|
||||
// - def __post_init__(self, foo: int = 0) -> 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.
|
||||
let locator = checker.locator();
|
||||
|
||||
let content = {
|
||||
let default = locator.slice(default_loc);
|
||||
let parameter_name = locator.slice(¶meter.name);
|
||||
let default = locator.slice(default);
|
||||
let line_ending = checker.stylist().line_ending().as_str();
|
||||
|
||||
if let Some(annotation) = ¶meter
|
||||
|
|
|
|||
|
|
@ -156,3 +156,281 @@ RUF033.py:67:59: RUF033 `__post_init__` method with argument defaults
|
|||
| ^^^^^ RUF033
|
||||
|
|
||||
= 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