mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-29 19:17:20 +00:00
[flake8-type-checking, pyupgrade, ruff] Add from __future__ import annotations when it would allow new fixes (TC001, TC002, TC003, UP037, RUF013) (#19100)
## Summary
This is a second attempt at addressing
https://github.com/astral-sh/ruff/issues/18502 instead of reusing
`FA100` (#18919).
This PR:
- adds a new `lint.allow-importing-future-annotations` option
- uses the option to add a `__future__` import when it would trigger
`TC001`, `TC002`, or `TC003`
- uses the option to add an import when it would allow unquoting more
annotations in [quoted-annotation
(UP037)](https://docs.astral.sh/ruff/rules/quoted-annotation/#quoted-annotation-up037)
- uses the option to allow the `|` union syntax before 3.10 in
[implicit-optional
(RUF013)](https://docs.astral.sh/ruff/rules/implicit-optional/#implicit-optional-ruf013)
I started adding a fix for [runtime-string-union
(TC010)](https://docs.astral.sh/ruff/rules/runtime-string-union/#runtime-string-union-tc010)
too, as mentioned in my previous
[comment](https://github.com/astral-sh/ruff/issues/18502#issuecomment-3005238092),
but some of the existing tests already imported `from __future__ import
annotations`, so I think we intentionally flag these cases for the user
to inspect. Adding the import is _a_ fix but probably not the best one.
## Test Plan
Existing `TC` tests, new copies of them with the option enabled, and new
tests based on ideas in
https://github.com/astral-sh/ruff/pull/18919#discussion_r2166292705 and
the following thread. For UP037 and RUF013, the new tests are also
copies of the existing tests, with the new option enabled. The easiest
way to review them is probably by their diffs from the existing
snapshots:
### UP037
`UP037_0.py` and `UP037_2.pyi` have no diffs. The diff for `UP037_1.py`
is below. It correctly unquotes an annotation in module scope that would
otherwise be invalid.
<details><summary>UP037_1.py</summary>
```diff
3d2
< snapshot_kind: text
23c22,42
< 12 12 |
---
> 12 12 |
>
> UP037_1.py:14:4: UP037 [*] Remove quotes from type annotation
> |
> 13 | # OK
> 14 | X: "Tuple[int, int]" = (0, 0)
> | ^^^^^^^^^^^^^^^^^ UP037
> |
> = help: Remove quotes
>
> ℹ Unsafe fix
> 1 |+from __future__ import annotations
> 1 2 | from typing import TYPE_CHECKING
> 2 3 |
> 3 4 | if TYPE_CHECKING:
> --------------------------------------------------------------------------------
> 11 12 |
> 12 13 |
> 13 14 | # OK
> 14 |-X: "Tuple[int, int]" = (0, 0)
> 15 |+X: Tuple[int, int] = (0, 0)
```
</details>
### RUF013
The diffs here are mostly just the imports because the original snaps
were on 3.13. So we're getting the same fixes now on 3.9.
<details><summary>RUF013_0.py</summary>
```diff
3d2
< snapshot_kind: text
14,16c13,20
< 17 17 | pass
< 18 18 |
< 19 19 |
---
> 1 |+from __future__ import annotations
> 1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 17 18 | pass
> 18 19 |
> 19 20 |
18,21c22,25
< 20 |+def f(arg: int | None = None): # RUF013
< 21 21 | pass
< 22 22 |
< 23 23 |
---
> 21 |+def f(arg: int | None = None): # RUF013
> 21 22 | pass
> 22 23 |
> 23 24 |
32,34c36,43
< 21 21 | pass
< 22 22 |
< 23 23 |
---
> 1 |+from __future__ import annotations
> 1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 21 22 | pass
> 22 23 |
> 23 24 |
36,39c45,48
< 24 |+def f(arg: str | None = None): # RUF013
< 25 25 | pass
< 26 26 |
< 27 27 |
---
> 25 |+def f(arg: str | None = None): # RUF013
> 25 26 | pass
> 26 27 |
> 27 28 |
50,52c59,66
< 25 25 | pass
< 26 26 |
< 27 27 |
---
> 1 |+from __future__ import annotations
> 1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 25 26 | pass
> 26 27 |
> 27 28 |
54,57c68,71
< 28 |+def f(arg: Tuple[str] | None = None): # RUF013
< 29 29 | pass
< 30 30 |
< 31 31 |
---
> 29 |+def f(arg: Tuple[str] | None = None): # RUF013
> 29 30 | pass
> 30 31 |
> 31 32 |
68,70c82,89
< 55 55 | pass
< 56 56 |
< 57 57 |
---
> 1 |+from __future__ import annotations
> 1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 55 56 | pass
> 56 57 |
> 57 58 |
72,75c91,94
< 58 |+def f(arg: Union | None = None): # RUF013
< 59 59 | pass
< 60 60 |
< 61 61 |
---
> 59 |+def f(arg: Union | None = None): # RUF013
> 59 60 | pass
> 60 61 |
> 61 62 |
86,88c105,112
< 59 59 | pass
< 60 60 |
< 61 61 |
---
> 1 |+from __future__ import annotations
> 1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 59 60 | pass
> 60 61 |
> 61 62 |
90,93c114,117
< 62 |+def f(arg: Union[int] | None = None): # RUF013
< 63 63 | pass
< 64 64 |
< 65 65 |
---
> 63 |+def f(arg: Union[int] | None = None): # RUF013
> 63 64 | pass
> 64 65 |
> 65 66 |
104,106c128,135
< 63 63 | pass
< 64 64 |
< 65 65 |
---
> 1 |+from __future__ import annotations
> 1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 63 64 | pass
> 64 65 |
> 65 66 |
108,111c137,140
< 66 |+def f(arg: Union[int, str] | None = None): # RUF013
< 67 67 | pass
< 68 68 |
< 69 69 |
---
> 67 |+def f(arg: Union[int, str] | None = None): # RUF013
> 67 68 | pass
> 68 69 |
> 69 70 |
122,124c151,158
< 82 82 | pass
< 83 83 |
< 84 84 |
---
> 1 |+from __future__ import annotations
> 1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 82 83 | pass
> 83 84 |
> 84 85 |
126,129c160,163
< 85 |+def f(arg: int | float | None = None): # RUF013
< 86 86 | pass
< 87 87 |
< 88 88 |
---
> 86 |+def f(arg: int | float | None = None): # RUF013
> 86 87 | pass
> 87 88 |
> 88 89 |
140,142c174,181
< 86 86 | pass
< 87 87 |
< 88 88 |
---
> 1 |+from __future__ import annotations
> 1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 86 87 | pass
> 87 88 |
> 88 89 |
144,147c183,186
< 89 |+def f(arg: int | float | str | bytes | None = None): # RUF013
< 90 90 | pass
< 91 91 |
< 92 92 |
---
> 90 |+def f(arg: int | float | str | bytes | None = None): # RUF013
> 90 91 | pass
> 91 92 |
> 92 93 |
158,160c197,204
< 105 105 | pass
< 106 106 |
< 107 107 |
---
> 1 |+from __future__ import annotations
> 1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 105 106 | pass
> 106 107 |
> 107 108 |
162,165c206,209
< 108 |+def f(arg: Literal[1] | None = None): # RUF013
< 109 109 | pass
< 110 110 |
< 111 111 |
---
> 109 |+def f(arg: Literal[1] | None = None): # RUF013
> 109 110 | pass
> 110 111 |
> 111 112 |
176,178c220,227
< 109 109 | pass
< 110 110 |
< 111 111 |
---
> 1 |+from __future__ import annotations
> 1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 109 110 | pass
> 110 111 |
> 111 112 |
180,183c229,232
< 112 |+def f(arg: Literal[1, "foo"] | None = None): # RUF013
< 113 113 | pass
< 114 114 |
< 115 115 |
---
> 113 |+def f(arg: Literal[1, "foo"] | None = None): # RUF013
> 113 114 | pass
> 114 115 |
> 115 116 |
194,196c243,250
< 128 128 | pass
< 129 129 |
< 130 130 |
---
> 1 |+from __future__ import annotations
> 1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 128 129 | pass
> 129 130 |
> 130 131 |
198,201c252,255
< 131 |+def f(arg: Annotated[int | None, ...] = None): # RUF013
< 132 132 | pass
< 133 133 |
< 134 134 |
---
> 132 |+def f(arg: Annotated[int | None, ...] = None): # RUF013
> 132 133 | pass
> 133 134 |
> 134 135 |
212,214c266,273
< 132 132 | pass
< 133 133 |
< 134 134 |
---
> 1 |+from __future__ import annotations
> 1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 132 133 | pass
> 133 134 |
> 134 135 |
216,219c275,278
< 135 |+def f(arg: Annotated[Annotated[int | str | None, ...], ...] = None): # RUF013
< 136 136 | pass
< 137 137 |
< 138 138 |
---
> 136 |+def f(arg: Annotated[Annotated[int | str | None, ...], ...] = None): # RUF013
> 136 137 | pass
> 137 138 |
> 138 139 |
232,234c291,298
< 148 148 |
< 149 149 |
< 150 150 | def f(
---
> 1 |+from __future__ import annotations
> 1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 148 149 |
> 149 150 |
> 150 151 | def f(
236,239c300,303
< 151 |+ arg1: int | None = None, # RUF013
< 152 152 | arg2: Union[int, float] = None, # RUF013
< 153 153 | arg3: Literal[1, 2, 3] = None, # RUF013
< 154 154 | ):
---
> 152 |+ arg1: int | None = None, # RUF013
> 152 153 | arg2: Union[int, float] = None, # RUF013
> 153 154 | arg3: Literal[1, 2, 3] = None, # RUF013
> 154 155 | ):
253,255c317,324
< 149 149 |
< 150 150 | def f(
< 151 151 | arg1: int = None, # RUF013
---
> 1 |+from __future__ import annotations
> 1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 149 150 |
> 150 151 | def f(
> 151 152 | arg1: int = None, # RUF013
257,260c326,329
< 152 |+ arg2: Union[int, float] | None = None, # RUF013
< 153 153 | arg3: Literal[1, 2, 3] = None, # RUF013
< 154 154 | ):
< 155 155 | pass
---
> 153 |+ arg2: Union[int, float] | None = None, # RUF013
> 153 154 | arg3: Literal[1, 2, 3] = None, # RUF013
> 154 155 | ):
> 155 156 | pass
274,276c343,350
< 150 150 | def f(
< 151 151 | arg1: int = None, # RUF013
< 152 152 | arg2: Union[int, float] = None, # RUF013
---
> 1 |+from __future__ import annotations
> 1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 150 151 | def f(
> 151 152 | arg1: int = None, # RUF013
> 152 153 | arg2: Union[int, float] = None, # RUF013
278,281c352,355
< 153 |+ arg3: Literal[1, 2, 3] | None = None, # RUF013
< 154 154 | ):
< 155 155 | pass
< 156 156 |
---
> 154 |+ arg3: Literal[1, 2, 3] | None = None, # RUF013
> 154 155 | ):
> 155 156 | pass
> 156 157 |
292,294c366,373
< 178 178 | pass
< 179 179 |
< 180 180 |
---
> 1 |+from __future__ import annotations
> 1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 178 179 | pass
> 179 180 |
> 180 181 |
296,299c375,378
< 181 |+def f(arg: Union[Annotated[int, ...], Union[str, bytes]] | None = None): # RUF013
< 182 182 | pass
< 183 183 |
< 184 184 |
---
> 182 |+def f(arg: Union[Annotated[int, ...], Union[str, bytes]] | None = None): # RUF013
> 182 183 | pass
> 183 184 |
> 184 185 |
307c386
< = help: Convert to `T | None`
---
> = help: Convert to `Optional[T]`
314c393
< 188 |+def f(arg: "int | None" = None): # RUF013
---
> 188 |+def f(arg: "Optional[int]" = None): # RUF013
325c404
< = help: Convert to `T | None`
---
> = help: Convert to `Optional[T]`
332c411
< 192 |+def f(arg: "str | None" = None): # RUF013
---
> 192 |+def f(arg: "Optional[str]" = None): # RUF013
343c422
< = help: Convert to `T | None`
---
> = help: Convert to `Optional[T]`
354,356c433,440
< 201 201 | pass
< 202 202 |
< 203 203 |
---
> 1 |+from __future__ import annotations
> 1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 201 202 | pass
> 202 203 |
> 203 204 |
358,361c442,445
< 204 |+def f(arg: Union["int", "str"] | None = None): # RUF013
< 205 205 | pass
< 206 206 |
< 207 207 |
---
> 205 |+def f(arg: Union["int", "str"] | None = None): # RUF013
> 205 206 | pass
> 206 207 |
> 207 208 |
```
</details>
<details><summary>RUF013_1.py</summary>
```diff
3d2
< snapshot_kind: text
15,16c14,16
< 2 2 |
< 3 3 |
---
> 2 |+from __future__ import annotations
> 2 3 |
> 3 4 |
18,19c18,19
< 4 |+def f(arg: int | None = None): # RUF013
< 5 5 | pass
---
> 5 |+def f(arg: int | None = None): # RUF013
> 5 6 | pass
```
</details>
<details><summary>RUF013_3.py</summary>
```diff
3d2
< snapshot_kind: text
14,16c13,16
< 1 1 | import typing
< 2 2 |
< 3 3 |
---
> 1 |+from __future__ import annotations
> 1 2 | import typing
> 2 3 |
> 3 4 |
18,21c18,21
< 4 |+def f(arg: typing.List[str] | None = None): # RUF013
< 5 5 | pass
< 6 6 |
< 7 7 |
---
> 5 |+def f(arg: typing.List[str] | None = None): # RUF013
> 5 6 | pass
> 6 7 |
> 7 8 |
32,34c32,39
< 19 19 | pass
< 20 20 |
< 21 21 |
---
> 1 |+from __future__ import annotations
> 1 2 | import typing
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 19 20 | pass
> 20 21 |
> 21 22 |
36,39c41,44
< 22 |+def f(arg: typing.Union[int, str] | None = None): # RUF013
< 23 23 | pass
< 24 24 |
< 25 25 |
---
> 23 |+def f(arg: typing.Union[int, str] | None = None): # RUF013
> 23 24 | pass
> 24 25 |
> 25 26 |
50,52c55,62
< 26 26 | # Literal
< 27 27 |
< 28 28 |
---
> 1 |+from __future__ import annotations
> 1 2 | import typing
> 2 3 |
> 3 4 |
> --------------------------------------------------------------------------------
> 26 27 | # Literal
> 27 28 |
> 28 29 |
54,55c64,65
< 29 |+def f(arg: typing.Literal[1, "foo", True] | None = None): # RUF013
< 30 30 | pass
---
> 30 |+def f(arg: typing.Literal[1, "foo", True] | None = None): # RUF013
> 30 31 | pass
```
</details>
<details><summary>RUF013_4.py</summary>
```diff
3d2
< snapshot_kind: text
13,15c12,20
< 12 12 | def multiple_1(arg1: Optional, arg2: Optional = None): ...
< 13 13 |
< 14 14 |
---
> 1 1 | # https://github.com/astral-sh/ruff/issues/13833
> 2 |+from __future__ import annotations
> 2 3 |
> 3 4 | from typing import Optional
> 4 5 |
> --------------------------------------------------------------------------------
> 12 13 | def multiple_1(arg1: Optional, arg2: Optional = None): ...
> 13 14 |
> 14 15 |
17,20c22,25
< 15 |+def multiple_2(arg1: Optional, arg2: Optional = None, arg3: int | None = None): ...
< 16 16 |
< 17 17 |
< 18 18 | def return_type(arg: Optional = None) -> Optional: ...
---
> 16 |+def multiple_2(arg1: Optional, arg2: Optional = None, arg3: int | None = None): ...
> 16 17 |
> 17 18 |
> 18 19 | def return_type(arg: Optional = None) -> Optional: ...
```
</details>
## Future work
This PR does not touch UP006, UP007, or UP045, which are currently
coupled to FA100. If this new approach turns out well, we may eventually
want to deprecate FA100 and add a `__future__` import in those rules'
fixes too.
---------
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
b8dddd514f
commit
893f5727e5
37 changed files with 2487 additions and 169 deletions
|
|
@ -993,6 +993,7 @@ fn value_given_to_table_key_is_not_inline_table_2() {
|
|||
- `lint.exclude`
|
||||
- `lint.preview`
|
||||
- `lint.typing-extensions`
|
||||
- `lint.future-annotations`
|
||||
|
||||
For more information, try '--help'.
|
||||
");
|
||||
|
|
@ -5744,3 +5745,25 @@ match 42: # invalid-syntax
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn future_annotations_preview_warning() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", "lint.future-annotations = true"])
|
||||
.args(["--select", "F"])
|
||||
.arg("--no-preview")
|
||||
.arg("-")
|
||||
.pass_stdin("1"),
|
||||
@r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
warning: The `lint.future-annotations` setting will have no effect because `preview` is disabled
|
||||
",
|
||||
);
|
||||
}
|
||||
|
|
|
|||
10
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC001-3_future.py
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC001-3_future.py
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
from collections import Counter
|
||||
|
||||
from elsewhere import third_party
|
||||
|
||||
from . import first_party
|
||||
|
||||
|
||||
def f(x: first_party.foo): ...
|
||||
def g(x: third_party.bar): ...
|
||||
def h(x: Counter): ...
|
||||
68
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC001_future.py
vendored
Normal file
68
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC001_future.py
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
def f():
|
||||
from . import first_party
|
||||
|
||||
def f(x: first_party.foo): ...
|
||||
|
||||
|
||||
# Type parameter bounds
|
||||
def g():
|
||||
from . import foo
|
||||
|
||||
class C[T: foo.Ty]: ...
|
||||
|
||||
|
||||
def h():
|
||||
from . import foo
|
||||
|
||||
def f[T: foo.Ty](x: T): ...
|
||||
|
||||
|
||||
def i():
|
||||
from . import foo
|
||||
|
||||
type Alias[T: foo.Ty] = list[T]
|
||||
|
||||
|
||||
# Type parameter defaults
|
||||
def j():
|
||||
from . import foo
|
||||
|
||||
class C[T = foo.Ty]: ...
|
||||
|
||||
|
||||
def k():
|
||||
from . import foo
|
||||
|
||||
def f[T = foo.Ty](x: T): ...
|
||||
|
||||
|
||||
def l():
|
||||
from . import foo
|
||||
|
||||
type Alias[T = foo.Ty] = list[T]
|
||||
|
||||
|
||||
# non-generic type alias
|
||||
def m():
|
||||
from . import foo
|
||||
|
||||
type Alias = foo.Ty
|
||||
|
||||
|
||||
# unions
|
||||
from typing import Union
|
||||
|
||||
|
||||
def n():
|
||||
from . import foo
|
||||
|
||||
def f(x: Union[foo.Ty, int]): ...
|
||||
def g(x: foo.Ty | int): ...
|
||||
|
||||
|
||||
# runtime and typing usage
|
||||
def o():
|
||||
from . import foo
|
||||
|
||||
def f(x: foo.Ty):
|
||||
return foo.Ty()
|
||||
6
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC001_future_present.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC001_future_present.py
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from . import first_party
|
||||
|
||||
|
||||
def f(x: first_party.foo): ...
|
||||
|
|
@ -71,7 +71,7 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
|
|||
flake8_type_checking::helpers::is_valid_runtime_import(
|
||||
binding,
|
||||
&checker.semantic,
|
||||
&checker.settings().flake8_type_checking,
|
||||
checker.settings(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
|
|
|
|||
|
|
@ -2770,11 +2770,10 @@ impl<'a> Checker<'a> {
|
|||
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
if self.semantic.in_typing_only_annotation() {
|
||||
if self.is_rule_enabled(Rule::QuotedAnnotation) {
|
||||
pyupgrade::rules::quoted_annotation(self, annotation, range);
|
||||
}
|
||||
}
|
||||
|
||||
if self.source_type.is_stub() {
|
||||
if self.is_rule_enabled(Rule::QuotedAnnotationInStub) {
|
||||
flake8_pyi::rules::quoted_annotation_in_stub(
|
||||
|
|
|
|||
|
|
@ -527,6 +527,17 @@ impl<'a> Importer<'a> {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a `from __future__ import annotations` import.
|
||||
pub(crate) fn add_future_import(&self) -> Edit {
|
||||
let import = &NameImport::ImportFrom(MemberNameImport::member(
|
||||
"__future__".to_string(),
|
||||
"annotations".to_string(),
|
||||
));
|
||||
// Note that `TextSize::default` should ensure that the import is added at the very
|
||||
// beginning of the file via `Insertion::start_of_file`.
|
||||
self.add_import(import, TextSize::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// An edit to the top-level of a module, making it available at runtime.
|
||||
|
|
|
|||
|
|
@ -195,3 +195,8 @@ pub(crate) const fn is_safe_super_call_with_parameters_fix_enabled(
|
|||
pub(crate) const fn is_assert_raises_exception_call_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19100
|
||||
pub(crate) const fn is_add_future_annotations_imports_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ use std::fmt;
|
|||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_python_semantic::{MemberNameImport, NameImport};
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::{AlwaysFixableViolation, Fix};
|
||||
|
|
@ -85,15 +84,7 @@ impl AlwaysFixableViolation for FutureRequiredTypeAnnotation {
|
|||
|
||||
/// FA102
|
||||
pub(crate) fn future_required_type_annotation(checker: &Checker, expr: &Expr, reason: Reason) {
|
||||
let mut diagnostic =
|
||||
checker.report_diagnostic(FutureRequiredTypeAnnotation { reason }, expr.range());
|
||||
let required_import = NameImport::ImportFrom(MemberNameImport::member(
|
||||
"__future__".to_string(),
|
||||
"annotations".to_string(),
|
||||
));
|
||||
diagnostic.set_fix(Fix::unsafe_edit(
|
||||
checker
|
||||
.importer()
|
||||
.add_import(&required_import, TextSize::default()),
|
||||
));
|
||||
.report_diagnostic(FutureRequiredTypeAnnotation { reason }, expr.range())
|
||||
.set_fix(Fix::unsafe_edit(checker.importer().add_future_import()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
use ruff_diagnostics::Fix;
|
||||
use ruff_python_ast::Expr;
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_semantic::{MemberNameImport, NameImport};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::AlwaysFixableViolation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::{AlwaysFixableViolation, Fix};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for missing `from __future__ import annotations` imports upon
|
||||
|
|
@ -95,15 +93,7 @@ pub(crate) fn future_rewritable_type_annotation(checker: &Checker, expr: &Expr)
|
|||
|
||||
let Some(name) = name else { return };
|
||||
|
||||
let import = &NameImport::ImportFrom(MemberNameImport::member(
|
||||
"__future__".to_string(),
|
||||
"annotations".to_string(),
|
||||
));
|
||||
checker
|
||||
.report_diagnostic(FutureRewritableTypeAnnotation { name }, expr.range())
|
||||
.set_fix(Fix::unsafe_edit(
|
||||
checker
|
||||
.importer()
|
||||
.add_import(import, ruff_text_size::TextSize::default()),
|
||||
));
|
||||
.set_fix(Fix::unsafe_edit(checker.importer().add_future_import()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,41 +8,110 @@ use ruff_python_ast::{self as ast, Decorator, Expr, StringLiteralFlags};
|
|||
use ruff_python_codegen::{Generator, Stylist};
|
||||
use ruff_python_parser::typing::parse_type_annotation;
|
||||
use ruff_python_semantic::{
|
||||
Binding, BindingKind, Modules, NodeId, ResolvedReference, ScopeKind, SemanticModel, analyze,
|
||||
Binding, BindingKind, Modules, NodeId, ScopeKind, SemanticModel, analyze,
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::Edit;
|
||||
use crate::Locator;
|
||||
use crate::rules::flake8_type_checking::settings::Settings;
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
/// Represents the kind of an existing or potential typing-only annotation.
|
||||
///
|
||||
/// Note that the order of variants is important here. `Runtime` has the highest precedence when
|
||||
/// calling [`TypingReference::combine`] on two references, followed by `Future`, `Quote`, and
|
||||
/// `TypingOnly` with the lowest precedence.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub(crate) enum TypingReference {
|
||||
/// The reference is in a runtime-evaluated context.
|
||||
Runtime,
|
||||
/// The reference is in a runtime-evaluated context, but the
|
||||
/// `lint.future-annotations` setting is enabled.
|
||||
///
|
||||
/// This takes precedence if both quoting and future imports are enabled.
|
||||
Future,
|
||||
/// The reference is in a runtime-evaluated context, but the
|
||||
/// `lint.flake8-type-checking.quote-annotations` setting is enabled.
|
||||
Quote,
|
||||
/// The reference is in a typing-only context.
|
||||
TypingOnly,
|
||||
}
|
||||
|
||||
impl TypingReference {
|
||||
/// Determine the kind of [`TypingReference`] for all references to a binding.
|
||||
pub(crate) fn from_references(
|
||||
binding: &Binding,
|
||||
semantic: &SemanticModel,
|
||||
settings: &LinterSettings,
|
||||
) -> Self {
|
||||
let references = binding
|
||||
.references()
|
||||
.map(|reference_id| semantic.reference(reference_id));
|
||||
let mut kind = Self::TypingOnly;
|
||||
for reference in references {
|
||||
if reference.in_type_checking_block() {
|
||||
kind = kind.combine(Self::TypingOnly);
|
||||
continue;
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`ResolvedReference`] is in a typing-only context _or_ a runtime-evaluated
|
||||
/// context (with quoting enabled).
|
||||
pub(crate) fn is_typing_reference(reference: &ResolvedReference, settings: &Settings) -> bool {
|
||||
reference.in_type_checking_block()
|
||||
// if we're not in a type checking block, we necessarily need to be within a
|
||||
// type definition to be considered a typing reference
|
||||
|| (reference.in_type_definition()
|
||||
&& (reference.in_typing_only_annotation()
|
||||
|| reference.in_string_type_definition()
|
||||
|| (settings.quote_annotations && reference.in_runtime_evaluated_annotation())))
|
||||
if !reference.in_type_definition() {
|
||||
return Self::Runtime;
|
||||
}
|
||||
|
||||
if reference.in_typing_only_annotation() || reference.in_string_type_definition() {
|
||||
kind = kind.combine(Self::TypingOnly);
|
||||
continue;
|
||||
}
|
||||
|
||||
// prefer `from __future__ import annotations` to quoting
|
||||
if settings.future_annotations()
|
||||
&& !reference.in_typing_only_annotation()
|
||||
&& reference.in_runtime_evaluated_annotation()
|
||||
{
|
||||
kind = kind.combine(Self::Future);
|
||||
continue;
|
||||
}
|
||||
|
||||
if settings.flake8_type_checking.quote_annotations
|
||||
&& reference.in_runtime_evaluated_annotation()
|
||||
{
|
||||
kind = kind.combine(Self::Quote);
|
||||
continue;
|
||||
}
|
||||
|
||||
return Self::Runtime;
|
||||
}
|
||||
|
||||
kind
|
||||
}
|
||||
|
||||
/// Logically combine two `TypingReference`s into one.
|
||||
///
|
||||
/// `TypingReference::Runtime` has the highest precedence, followed by
|
||||
/// `TypingReference::Future`, `TypingReference::Quote`, and then `TypingReference::TypingOnly`.
|
||||
fn combine(self, other: TypingReference) -> TypingReference {
|
||||
self.min(other)
|
||||
}
|
||||
|
||||
fn is_runtime(self) -> bool {
|
||||
matches!(self, Self::Runtime)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`Binding`] represents a runtime-required import.
|
||||
pub(crate) fn is_valid_runtime_import(
|
||||
binding: &Binding,
|
||||
semantic: &SemanticModel,
|
||||
settings: &Settings,
|
||||
settings: &LinterSettings,
|
||||
) -> bool {
|
||||
if matches!(
|
||||
binding.kind,
|
||||
BindingKind::Import(..) | BindingKind::FromImport(..) | BindingKind::SubmoduleImport(..)
|
||||
) {
|
||||
binding.context.is_runtime()
|
||||
&& binding
|
||||
.references()
|
||||
.map(|reference_id| semantic.reference(reference_id))
|
||||
.any(|reference| !is_typing_reference(reference, settings))
|
||||
&& TypingReference::from_references(binding, semantic, settings).is_runtime()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ pub(crate) struct ImportBinding<'a> {
|
|||
pub(crate) range: TextRange,
|
||||
/// The range of the import's parent statement.
|
||||
pub(crate) parent_range: Option<TextRange>,
|
||||
/// Whether the binding needs `from __future__ import annotations` to be imported.
|
||||
pub(crate) needs_future_import: bool,
|
||||
}
|
||||
|
||||
impl Ranged for ImportBinding<'_> {
|
||||
|
|
|
|||
|
|
@ -9,10 +9,12 @@ mod tests {
|
|||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use itertools::Itertools;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::registry::{Linter, Rule};
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::test::{test_path, test_snippet};
|
||||
use crate::{assert_diagnostics, settings};
|
||||
|
||||
|
|
@ -64,6 +66,40 @@ mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(&[Rule::TypingOnlyFirstPartyImport], Path::new("TC001.py"))]
|
||||
#[test_case(&[Rule::TypingOnlyThirdPartyImport], Path::new("TC002.py"))]
|
||||
#[test_case(&[Rule::TypingOnlyStandardLibraryImport], Path::new("TC003.py"))]
|
||||
#[test_case(
|
||||
&[
|
||||
Rule::TypingOnlyFirstPartyImport,
|
||||
Rule::TypingOnlyThirdPartyImport,
|
||||
Rule::TypingOnlyStandardLibraryImport,
|
||||
],
|
||||
Path::new("TC001-3_future.py")
|
||||
)]
|
||||
#[test_case(&[Rule::TypingOnlyFirstPartyImport], Path::new("TC001_future.py"))]
|
||||
#[test_case(&[Rule::TypingOnlyFirstPartyImport], Path::new("TC001_future_present.py"))]
|
||||
fn add_future_import(rules: &[Rule], path: &Path) -> Result<()> {
|
||||
let name = rules.iter().map(Rule::noqa_code).join("-");
|
||||
let snapshot = format!("add_future_import__{}_{}", name, path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_type_checking").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
future_annotations: true,
|
||||
preview: PreviewMode::Enabled,
|
||||
// also enable quoting annotations to check the interaction. the future import
|
||||
// should take precedence.
|
||||
flake8_type_checking: super::settings::Settings {
|
||||
quote_annotations: true,
|
||||
..Default::default()
|
||||
},
|
||||
..settings::LinterSettings::for_rules(rules.iter().copied())
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// we test these rules as a pair, since they're opposites of one another
|
||||
// so we want to make sure their fixes are not going around in circles.
|
||||
#[test_case(Rule::UnquotedTypeAlias, Path::new("TC007.py"))]
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ pub(crate) fn runtime_import_in_type_checking_block(checker: &Checker, scope: &S
|
|||
binding,
|
||||
range: binding.range(),
|
||||
parent_range: binding.parent_range(checker.semantic()),
|
||||
needs_future_import: false, // TODO(brent) See #19359.
|
||||
};
|
||||
|
||||
if checker.rule_is_ignored(Rule::RuntimeImportInTypeCheckingBlock, import.start())
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use crate::fix;
|
|||
use crate::importer::ImportedMembers;
|
||||
use crate::preview::is_full_path_match_source_strategy_enabled;
|
||||
use crate::rules::flake8_type_checking::helpers::{
|
||||
filter_contained, is_typing_reference, quote_annotation,
|
||||
TypingReference, filter_contained, quote_annotation,
|
||||
};
|
||||
use crate::rules::flake8_type_checking::imports::ImportBinding;
|
||||
use crate::rules::isort::categorize::MatchSourceStrategy;
|
||||
|
|
@ -71,12 +71,19 @@ use crate::{Fix, FixAvailability, Violation};
|
|||
/// the criterion for determining whether an import is first-party
|
||||
/// is stricter, which could affect whether this lint is triggered vs [`TC001`](https://docs.astral.sh/ruff/rules/typing-only-third-party-import/). See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) for more details.
|
||||
///
|
||||
/// If [`lint.future-annotations`] is set to `true`, `from __future__ import
|
||||
/// annotations` will be added if doing so would enable an import to be moved into an `if
|
||||
/// TYPE_CHECKING:` block. This takes precedence over the
|
||||
/// [`lint.flake8-type-checking.quote-annotations`] setting described above if both settings are
|
||||
/// enabled.
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.flake8-type-checking.quote-annotations`
|
||||
/// - `lint.flake8-type-checking.runtime-evaluated-base-classes`
|
||||
/// - `lint.flake8-type-checking.runtime-evaluated-decorators`
|
||||
/// - `lint.flake8-type-checking.strict`
|
||||
/// - `lint.typing-modules`
|
||||
/// - `lint.future-annotations`
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 563: Runtime annotation resolution and `TYPE_CHECKING`](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
|
||||
|
|
@ -151,12 +158,19 @@ impl Violation for TypingOnlyFirstPartyImport {
|
|||
/// the criterion for determining whether an import is first-party
|
||||
/// is stricter, which could affect whether this lint is triggered vs [`TC001`](https://docs.astral.sh/ruff/rules/typing-only-first-party-import/). See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) for more details.
|
||||
///
|
||||
/// If [`lint.future-annotations`] is set to `true`, `from __future__ import
|
||||
/// annotations` will be added if doing so would enable an import to be moved into an `if
|
||||
/// TYPE_CHECKING:` block. This takes precedence over the
|
||||
/// [`lint.flake8-type-checking.quote-annotations`] setting described above if both settings are
|
||||
/// enabled.
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.flake8-type-checking.quote-annotations`
|
||||
/// - `lint.flake8-type-checking.runtime-evaluated-base-classes`
|
||||
/// - `lint.flake8-type-checking.runtime-evaluated-decorators`
|
||||
/// - `lint.flake8-type-checking.strict`
|
||||
/// - `lint.typing-modules`
|
||||
/// - `lint.future-annotations`
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 563: Runtime annotation resolution and `TYPE_CHECKING`](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
|
||||
|
|
@ -226,12 +240,22 @@ impl Violation for TypingOnlyThirdPartyImport {
|
|||
/// return str(path)
|
||||
/// ```
|
||||
///
|
||||
/// ## Preview
|
||||
///
|
||||
/// When [preview](https://docs.astral.sh/ruff/preview/) is enabled, if
|
||||
/// [`lint.future-annotations`] is set to `true`, `from __future__ import
|
||||
/// annotations` will be added if doing so would enable an import to be moved into an `if
|
||||
/// TYPE_CHECKING:` block. This takes precedence over the
|
||||
/// [`lint.flake8-type-checking.quote-annotations`] setting described above if both settings are
|
||||
/// enabled.
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.flake8-type-checking.quote-annotations`
|
||||
/// - `lint.flake8-type-checking.runtime-evaluated-base-classes`
|
||||
/// - `lint.flake8-type-checking.runtime-evaluated-decorators`
|
||||
/// - `lint.flake8-type-checking.strict`
|
||||
/// - `lint.typing-modules`
|
||||
/// - `lint.future-annotations`
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 563: Runtime annotation resolution and `TYPE_CHECKING`](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
|
||||
|
|
@ -271,9 +295,10 @@ pub(crate) fn typing_only_runtime_import(
|
|||
for binding_id in scope.binding_ids() {
|
||||
let binding = checker.semantic().binding(binding_id);
|
||||
|
||||
// If we're in un-strict mode, don't flag typing-only imports that are
|
||||
// implicitly loaded by way of a valid runtime import.
|
||||
if !checker.settings().flake8_type_checking.strict
|
||||
// If we can't add a `__future__` import and in un-strict mode, don't flag typing-only
|
||||
// imports that are implicitly loaded by way of a valid runtime import.
|
||||
if !checker.settings().future_annotations()
|
||||
&& !checker.settings().flake8_type_checking.strict
|
||||
&& runtime_imports
|
||||
.iter()
|
||||
.any(|import| is_implicit_import(binding, import))
|
||||
|
|
@ -289,14 +314,21 @@ pub(crate) fn typing_only_runtime_import(
|
|||
continue;
|
||||
};
|
||||
|
||||
if binding.context.is_runtime()
|
||||
&& binding
|
||||
.references()
|
||||
.map(|reference_id| checker.semantic().reference(reference_id))
|
||||
.all(|reference| {
|
||||
is_typing_reference(reference, &checker.settings().flake8_type_checking)
|
||||
})
|
||||
{
|
||||
if !binding.context.is_runtime() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let typing_reference =
|
||||
TypingReference::from_references(binding, checker.semantic(), checker.settings());
|
||||
|
||||
let needs_future_import = match typing_reference {
|
||||
TypingReference::Runtime => continue,
|
||||
// We can only get the `Future` variant if `future_annotations` is
|
||||
// enabled, so we can unconditionally set this here.
|
||||
TypingReference::Future => true,
|
||||
TypingReference::TypingOnly | TypingReference::Quote => false,
|
||||
};
|
||||
|
||||
let qualified_name = import.qualified_name();
|
||||
|
||||
if is_exempt(
|
||||
|
|
@ -361,6 +393,7 @@ pub(crate) fn typing_only_runtime_import(
|
|||
binding,
|
||||
range: binding.range(),
|
||||
parent_range: binding.parent_range(checker.semantic()),
|
||||
needs_future_import,
|
||||
};
|
||||
|
||||
if checker.rule_is_ignored(rule_for(import_type), import.start())
|
||||
|
|
@ -379,7 +412,6 @@ pub(crate) fn typing_only_runtime_import(
|
|||
.push(import);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a diagnostic for every import, but share a fix across all imports within the same
|
||||
// statement (excluding those that are ignored).
|
||||
|
|
@ -509,6 +541,8 @@ fn fix_imports(checker: &Checker, node_id: NodeId, imports: &[ImportBinding]) ->
|
|||
.min()
|
||||
.expect("Expected at least one import");
|
||||
|
||||
let add_future_import = imports.iter().any(|binding| binding.needs_future_import);
|
||||
|
||||
// Step 1) Remove the import.
|
||||
let remove_import_edit = fix::edits::remove_unused_imports(
|
||||
member_names.iter().map(AsRef::as_ref),
|
||||
|
|
@ -532,7 +566,21 @@ fn fix_imports(checker: &Checker, node_id: NodeId, imports: &[ImportBinding]) ->
|
|||
)?
|
||||
.into_edits();
|
||||
|
||||
// Step 3) Quote any runtime usages of the referenced symbol.
|
||||
// Step 3) Either add a `__future__` import or quote any runtime usages of the referenced
|
||||
// symbol.
|
||||
let fix = if add_future_import {
|
||||
let future_import = checker.importer().add_future_import();
|
||||
|
||||
// The order here is very important. We first need to add the `__future__` import, if
|
||||
// needed, since it's a syntax error to come later. Then `type_checking_edit` imports
|
||||
// `TYPE_CHECKING`, if available. Then we can add and/or remove existing imports.
|
||||
Fix::unsafe_edits(
|
||||
future_import,
|
||||
std::iter::once(type_checking_edit)
|
||||
.chain(add_import_edit)
|
||||
.chain(std::iter::once(remove_import_edit)),
|
||||
)
|
||||
} else {
|
||||
let quote_reference_edits = filter_contained(
|
||||
imports
|
||||
.iter()
|
||||
|
|
@ -554,15 +602,16 @@ fn fix_imports(checker: &Checker, node_id: NodeId, imports: &[ImportBinding]) ->
|
|||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
Ok(Fix::unsafe_edits(
|
||||
Fix::unsafe_edits(
|
||||
type_checking_edit,
|
||||
add_import_edit
|
||||
.into_iter()
|
||||
.chain(std::iter::once(remove_import_edit))
|
||||
.chain(quote_reference_edits),
|
||||
)
|
||||
.isolate(Checker::isolation(
|
||||
};
|
||||
|
||||
Ok(fix.isolate(Checker::isolation(
|
||||
checker.semantic().parent_statement_id(node_id),
|
||||
)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
|
||||
---
|
||||
TC001-3_future.py:1:25: TC003 [*] Move standard library import `collections.Counter` into a type-checking block
|
||||
|
|
||||
1 | from collections import Counter
|
||||
| ^^^^^^^ TC003
|
||||
2 |
|
||||
3 | from elsewhere import third_party
|
||||
|
|
||||
= help: Move into type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |-from collections import Counter
|
||||
1 |+from __future__ import annotations
|
||||
2 2 |
|
||||
3 3 | from elsewhere import third_party
|
||||
4 4 |
|
||||
5 5 | from . import first_party
|
||||
6 |+from typing import TYPE_CHECKING
|
||||
7 |+
|
||||
8 |+if TYPE_CHECKING:
|
||||
9 |+ from collections import Counter
|
||||
6 10 |
|
||||
7 11 |
|
||||
8 12 | def f(x: first_party.foo): ...
|
||||
|
||||
TC001-3_future.py:3:23: TC002 [*] Move third-party import `elsewhere.third_party` into a type-checking block
|
||||
|
|
||||
1 | from collections import Counter
|
||||
2 |
|
||||
3 | from elsewhere import third_party
|
||||
| ^^^^^^^^^^^ TC002
|
||||
4 |
|
||||
5 | from . import first_party
|
||||
|
|
||||
= help: Move into type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from collections import Counter
|
||||
2 3 |
|
||||
3 |-from elsewhere import third_party
|
||||
4 4 |
|
||||
5 5 | from . import first_party
|
||||
6 |+from typing import TYPE_CHECKING
|
||||
7 |+
|
||||
8 |+if TYPE_CHECKING:
|
||||
9 |+ from elsewhere import third_party
|
||||
6 10 |
|
||||
7 11 |
|
||||
8 12 | def f(x: first_party.foo): ...
|
||||
|
||||
TC001-3_future.py:5:15: TC001 [*] Move application import `.first_party` into a type-checking block
|
||||
|
|
||||
3 | from elsewhere import third_party
|
||||
4 |
|
||||
5 | from . import first_party
|
||||
| ^^^^^^^^^^^ TC001
|
||||
|
|
||||
= help: Move into type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from collections import Counter
|
||||
2 3 |
|
||||
3 4 | from elsewhere import third_party
|
||||
4 5 |
|
||||
5 |-from . import first_party
|
||||
6 |+from typing import TYPE_CHECKING
|
||||
7 |+
|
||||
8 |+if TYPE_CHECKING:
|
||||
9 |+ from . import first_party
|
||||
6 10 |
|
||||
7 11 |
|
||||
8 12 | def f(x: first_party.foo): ...
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
|
||||
---
|
||||
TC001.py:20:19: TC001 [*] Move application import `.TYP001` into a type-checking block
|
||||
|
|
||||
19 | def f():
|
||||
20 | from . import TYP001
|
||||
| ^^^^^^ TC001
|
||||
21 |
|
||||
22 | x: TYP001
|
||||
|
|
||||
= help: Move into type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
2 2 |
|
||||
3 3 | For typing-only import detection tests, see `TC002.py`.
|
||||
4 4 | """
|
||||
5 |+from typing import TYPE_CHECKING
|
||||
6 |+
|
||||
7 |+if TYPE_CHECKING:
|
||||
8 |+ from . import TYP001
|
||||
5 9 |
|
||||
6 10 |
|
||||
7 11 | def f():
|
||||
--------------------------------------------------------------------------------
|
||||
17 21 |
|
||||
18 22 |
|
||||
19 23 | def f():
|
||||
20 |- from . import TYP001
|
||||
21 24 |
|
||||
22 25 | x: TYP001
|
||||
23 26 |
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
|
||||
---
|
||||
TC001_future.py:2:19: TC001 [*] Move application import `.first_party` into a type-checking block
|
||||
|
|
||||
1 | def f():
|
||||
2 | from . import first_party
|
||||
| ^^^^^^^^^^^ TC001
|
||||
3 |
|
||||
4 | def f(x: first_party.foo): ...
|
||||
|
|
||||
= help: Move into type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |-def f():
|
||||
1 |+from __future__ import annotations
|
||||
2 |+from typing import TYPE_CHECKING
|
||||
3 |+
|
||||
4 |+if TYPE_CHECKING:
|
||||
2 5 | from . import first_party
|
||||
6 |+def f():
|
||||
3 7 |
|
||||
4 8 | def f(x: first_party.foo): ...
|
||||
5 9 |
|
||||
|
||||
TC001_future.py:57:19: TC001 [*] Move application import `.foo` into a type-checking block
|
||||
|
|
||||
56 | def n():
|
||||
57 | from . import foo
|
||||
| ^^^ TC001
|
||||
58 |
|
||||
59 | def f(x: Union[foo.Ty, int]): ...
|
||||
|
|
||||
= help: Move into type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | def f():
|
||||
2 3 | from . import first_party
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
50 51 |
|
||||
51 52 |
|
||||
52 53 | # unions
|
||||
53 |-from typing import Union
|
||||
54 |+from typing import Union, TYPE_CHECKING
|
||||
54 55 |
|
||||
56 |+if TYPE_CHECKING:
|
||||
57 |+ from . import foo
|
||||
58 |+
|
||||
55 59 |
|
||||
56 60 | def n():
|
||||
57 |- from . import foo
|
||||
58 61 |
|
||||
59 62 | def f(x: Union[foo.Ty, int]): ...
|
||||
60 63 | def g(x: foo.Ty | int): ...
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
|
||||
---
|
||||
TC001_future_present.py:3:15: TC001 [*] Move application import `.first_party` into a type-checking block
|
||||
|
|
||||
1 | from __future__ import annotations
|
||||
2 |
|
||||
3 | from . import first_party
|
||||
| ^^^^^^^^^^^ TC001
|
||||
|
|
||||
= help: Move into type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | from __future__ import annotations
|
||||
2 2 |
|
||||
3 |-from . import first_party
|
||||
3 |+from typing import TYPE_CHECKING
|
||||
4 |+
|
||||
5 |+if TYPE_CHECKING:
|
||||
6 |+ from . import first_party
|
||||
4 7 |
|
||||
5 8 |
|
||||
6 9 | def f(x: first_party.foo): ...
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
|
||||
---
|
||||
TC002.py:5:22: TC002 [*] Move third-party import `pandas` into a type-checking block
|
||||
|
|
||||
4 | def f():
|
||||
5 | import pandas as pd # TC002
|
||||
| ^^ TC002
|
||||
6 |
|
||||
7 | x: pd.DataFrame
|
||||
|
|
||||
= help: Move into type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | """Tests to determine accurate detection of typing-only imports."""
|
||||
2 |+from typing import TYPE_CHECKING
|
||||
3 |+
|
||||
4 |+if TYPE_CHECKING:
|
||||
5 |+ import pandas as pd
|
||||
2 6 |
|
||||
3 7 |
|
||||
4 8 | def f():
|
||||
5 |- import pandas as pd # TC002
|
||||
6 9 |
|
||||
7 10 | x: pd.DataFrame
|
||||
8 11 |
|
||||
|
||||
TC002.py:11:24: TC002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
|
||||
|
|
||||
10 | def f():
|
||||
11 | from pandas import DataFrame # TC002
|
||||
| ^^^^^^^^^ TC002
|
||||
12 |
|
||||
13 | x: DataFrame
|
||||
|
|
||||
= help: Move into type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | """Tests to determine accurate detection of typing-only imports."""
|
||||
2 |+from typing import TYPE_CHECKING
|
||||
3 |+
|
||||
4 |+if TYPE_CHECKING:
|
||||
5 |+ from pandas import DataFrame
|
||||
2 6 |
|
||||
3 7 |
|
||||
4 8 | def f():
|
||||
--------------------------------------------------------------------------------
|
||||
8 12 |
|
||||
9 13 |
|
||||
10 14 | def f():
|
||||
11 |- from pandas import DataFrame # TC002
|
||||
12 15 |
|
||||
13 16 | x: DataFrame
|
||||
14 17 |
|
||||
|
||||
TC002.py:17:37: TC002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
|
||||
|
|
||||
16 | def f():
|
||||
17 | from pandas import DataFrame as df # TC002
|
||||
| ^^ TC002
|
||||
18 |
|
||||
19 | x: df
|
||||
|
|
||||
= help: Move into type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | """Tests to determine accurate detection of typing-only imports."""
|
||||
2 |+from typing import TYPE_CHECKING
|
||||
3 |+
|
||||
4 |+if TYPE_CHECKING:
|
||||
5 |+ from pandas import DataFrame as df
|
||||
2 6 |
|
||||
3 7 |
|
||||
4 8 | def f():
|
||||
--------------------------------------------------------------------------------
|
||||
14 18 |
|
||||
15 19 |
|
||||
16 20 | def f():
|
||||
17 |- from pandas import DataFrame as df # TC002
|
||||
18 21 |
|
||||
19 22 | x: df
|
||||
20 23 |
|
||||
|
||||
TC002.py:23:22: TC002 [*] Move third-party import `pandas` into a type-checking block
|
||||
|
|
||||
22 | def f():
|
||||
23 | import pandas as pd # TC002
|
||||
| ^^ TC002
|
||||
24 |
|
||||
25 | x: pd.DataFrame = 1
|
||||
|
|
||||
= help: Move into type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | """Tests to determine accurate detection of typing-only imports."""
|
||||
2 |+from typing import TYPE_CHECKING
|
||||
3 |+
|
||||
4 |+if TYPE_CHECKING:
|
||||
5 |+ import pandas as pd
|
||||
2 6 |
|
||||
3 7 |
|
||||
4 8 | def f():
|
||||
--------------------------------------------------------------------------------
|
||||
20 24 |
|
||||
21 25 |
|
||||
22 26 | def f():
|
||||
23 |- import pandas as pd # TC002
|
||||
24 27 |
|
||||
25 28 | x: pd.DataFrame = 1
|
||||
26 29 |
|
||||
|
||||
TC002.py:29:24: TC002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
|
||||
|
|
||||
28 | def f():
|
||||
29 | from pandas import DataFrame # TC002
|
||||
| ^^^^^^^^^ TC002
|
||||
30 |
|
||||
31 | x: DataFrame = 2
|
||||
|
|
||||
= help: Move into type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | """Tests to determine accurate detection of typing-only imports."""
|
||||
2 |+from typing import TYPE_CHECKING
|
||||
3 |+
|
||||
4 |+if TYPE_CHECKING:
|
||||
5 |+ from pandas import DataFrame
|
||||
2 6 |
|
||||
3 7 |
|
||||
4 8 | def f():
|
||||
--------------------------------------------------------------------------------
|
||||
26 30 |
|
||||
27 31 |
|
||||
28 32 | def f():
|
||||
29 |- from pandas import DataFrame # TC002
|
||||
30 33 |
|
||||
31 34 | x: DataFrame = 2
|
||||
32 35 |
|
||||
|
||||
TC002.py:35:37: TC002 [*] Move third-party import `pandas.DataFrame` into a type-checking block
|
||||
|
|
||||
34 | def f():
|
||||
35 | from pandas import DataFrame as df # TC002
|
||||
| ^^ TC002
|
||||
36 |
|
||||
37 | x: df = 3
|
||||
|
|
||||
= help: Move into type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | """Tests to determine accurate detection of typing-only imports."""
|
||||
2 |+from typing import TYPE_CHECKING
|
||||
3 |+
|
||||
4 |+if TYPE_CHECKING:
|
||||
5 |+ from pandas import DataFrame as df
|
||||
2 6 |
|
||||
3 7 |
|
||||
4 8 | def f():
|
||||
--------------------------------------------------------------------------------
|
||||
32 36 |
|
||||
33 37 |
|
||||
34 38 | def f():
|
||||
35 |- from pandas import DataFrame as df # TC002
|
||||
36 39 |
|
||||
37 40 | x: df = 3
|
||||
38 41 |
|
||||
|
||||
TC002.py:41:22: TC002 [*] Move third-party import `pandas` into a type-checking block
|
||||
|
|
||||
40 | def f():
|
||||
41 | import pandas as pd # TC002
|
||||
| ^^ TC002
|
||||
42 |
|
||||
43 | x: "pd.DataFrame" = 1
|
||||
|
|
||||
= help: Move into type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | """Tests to determine accurate detection of typing-only imports."""
|
||||
2 |+from typing import TYPE_CHECKING
|
||||
3 |+
|
||||
4 |+if TYPE_CHECKING:
|
||||
5 |+ import pandas as pd
|
||||
2 6 |
|
||||
3 7 |
|
||||
4 8 | def f():
|
||||
--------------------------------------------------------------------------------
|
||||
38 42 |
|
||||
39 43 |
|
||||
40 44 | def f():
|
||||
41 |- import pandas as pd # TC002
|
||||
42 45 |
|
||||
43 46 | x: "pd.DataFrame" = 1
|
||||
44 47 |
|
||||
|
||||
TC002.py:47:22: TC002 [*] Move third-party import `pandas` into a type-checking block
|
||||
|
|
||||
46 | def f():
|
||||
47 | import pandas as pd # TC002
|
||||
| ^^ TC002
|
||||
48 |
|
||||
49 | x = dict["pd.DataFrame", "pd.DataFrame"]
|
||||
|
|
||||
= help: Move into type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | """Tests to determine accurate detection of typing-only imports."""
|
||||
2 |+from typing import TYPE_CHECKING
|
||||
3 |+
|
||||
4 |+if TYPE_CHECKING:
|
||||
5 |+ import pandas as pd
|
||||
2 6 |
|
||||
3 7 |
|
||||
4 8 | def f():
|
||||
--------------------------------------------------------------------------------
|
||||
44 48 |
|
||||
45 49 |
|
||||
46 50 | def f():
|
||||
47 |- import pandas as pd # TC002
|
||||
48 51 |
|
||||
49 52 | x = dict["pd.DataFrame", "pd.DataFrame"]
|
||||
50 53 |
|
||||
|
||||
TC002.py:172:24: TC002 [*] Move third-party import `module.Member` into a type-checking block
|
||||
|
|
||||
170 | global Member
|
||||
171 |
|
||||
172 | from module import Member
|
||||
| ^^^^^^ TC002
|
||||
173 |
|
||||
174 | x: Member = 1
|
||||
|
|
||||
= help: Move into type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | """Tests to determine accurate detection of typing-only imports."""
|
||||
2 |+from typing import TYPE_CHECKING
|
||||
3 |+
|
||||
4 |+if TYPE_CHECKING:
|
||||
5 |+ from module import Member
|
||||
2 6 |
|
||||
3 7 |
|
||||
4 8 | def f():
|
||||
--------------------------------------------------------------------------------
|
||||
169 173 | def f():
|
||||
170 174 | global Member
|
||||
171 175 |
|
||||
172 |- from module import Member
|
||||
173 176 |
|
||||
174 177 | x: Member = 1
|
||||
175 178 |
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
|
||||
---
|
||||
TC003.py:8:12: TC003 [*] Move standard library import `os` into a type-checking block
|
||||
|
|
||||
7 | def f():
|
||||
8 | import os
|
||||
| ^^ TC003
|
||||
9 |
|
||||
10 | x: os
|
||||
|
|
||||
= help: Move into type-checking block
|
||||
|
||||
ℹ Unsafe fix
|
||||
2 2 |
|
||||
3 3 | For typing-only import detection tests, see `TC002.py`.
|
||||
4 4 | """
|
||||
5 |+from typing import TYPE_CHECKING
|
||||
6 |+
|
||||
7 |+if TYPE_CHECKING:
|
||||
8 |+ import os
|
||||
5 9 |
|
||||
6 10 |
|
||||
7 11 | def f():
|
||||
8 |- import os
|
||||
9 12 |
|
||||
10 13 | x: os
|
||||
11 14 |
|
||||
|
|
@ -136,6 +136,23 @@ mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::QuotedAnnotation, Path::new("UP037_0.py"))]
|
||||
#[test_case(Rule::QuotedAnnotation, Path::new("UP037_1.py"))]
|
||||
#[test_case(Rule::QuotedAnnotation, Path::new("UP037_2.pyi"))]
|
||||
fn up037_add_future_annotation(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("add_future_annotation_{}", path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("pyupgrade").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
future_annotations: true,
|
||||
..settings::LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn async_timeout_error_alias_not_applied_py310() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
|
|
|
|||
|
|
@ -57,6 +57,22 @@ use crate::{AlwaysFixableViolation, Edit, Fix};
|
|||
/// bar: Bar
|
||||
/// ```
|
||||
///
|
||||
/// ## Preview
|
||||
///
|
||||
/// When [preview] is enabled, if [`lint.future-annotations`] is set to `true`,
|
||||
/// `from __future__ import annotations` will be added if doing so would allow an annotation to be
|
||||
/// unquoted.
|
||||
///
|
||||
/// ## Fix safety
|
||||
///
|
||||
/// The rule's fix is marked as safe, unless [preview] and
|
||||
/// [`lint.future_annotations`] are enabled and a `from __future__ import
|
||||
/// annotations` import is added. Such an import may change the behavior of all annotations in the
|
||||
/// file.
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.future-annotations`
|
||||
///
|
||||
/// ## See also
|
||||
/// - [`quoted-annotation-in-stub`][PYI020]: A rule that
|
||||
/// removes all quoted annotations from stub files
|
||||
|
|
@ -69,6 +85,7 @@ use crate::{AlwaysFixableViolation, Edit, Fix};
|
|||
///
|
||||
/// [PYI020]: https://docs.astral.sh/ruff/rules/quoted-annotation-in-stub/
|
||||
/// [TC008]: https://docs.astral.sh/ruff/rules/quoted-type-alias/
|
||||
/// [preview]: https://docs.astral.sh/ruff/preview/
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct QuotedAnnotation;
|
||||
|
||||
|
|
@ -85,6 +102,13 @@ impl AlwaysFixableViolation for QuotedAnnotation {
|
|||
|
||||
/// UP037
|
||||
pub(crate) fn quoted_annotation(checker: &Checker, annotation: &str, range: TextRange) {
|
||||
let add_future_import = checker.settings().future_annotations()
|
||||
&& checker.semantic().in_runtime_evaluated_annotation();
|
||||
|
||||
if !(checker.semantic().in_typing_only_annotation() || add_future_import) {
|
||||
return;
|
||||
}
|
||||
|
||||
let placeholder_range = TextRange::up_to(annotation.text_len());
|
||||
let spans_multiple_lines = annotation.contains_line_break(placeholder_range);
|
||||
|
||||
|
|
@ -103,8 +127,14 @@ pub(crate) fn quoted_annotation(checker: &Checker, annotation: &str, range: Text
|
|||
(true, false) => format!("({annotation})"),
|
||||
(_, true) => format!("({annotation}\n)"),
|
||||
};
|
||||
let edit = Edit::range_replacement(new_content, range);
|
||||
let fix = Fix::safe_edit(edit);
|
||||
let unquote_edit = Edit::range_replacement(new_content, range);
|
||||
|
||||
let fix = if add_future_import {
|
||||
let import_edit = checker.importer().add_future_import();
|
||||
Fix::unsafe_edits(unquote_edit, [import_edit])
|
||||
} else {
|
||||
Fix::safe_edit(unquote_edit)
|
||||
};
|
||||
|
||||
checker
|
||||
.report_diagnostic(QuotedAnnotation, range)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,625 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
|
||||
---
|
||||
UP037_0.py:18:14: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
18 | def foo(var: "MyClass") -> "MyClass":
|
||||
| ^^^^^^^^^ UP037
|
||||
19 | x: "MyClass"
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
15 15 | from mypy_extensions import Arg, DefaultArg, DefaultNamedArg, NamedArg, VarArg
|
||||
16 16 |
|
||||
17 17 |
|
||||
18 |-def foo(var: "MyClass") -> "MyClass":
|
||||
18 |+def foo(var: MyClass) -> "MyClass":
|
||||
19 19 | x: "MyClass"
|
||||
20 20 |
|
||||
21 21 |
|
||||
|
||||
UP037_0.py:18:28: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
18 | def foo(var: "MyClass") -> "MyClass":
|
||||
| ^^^^^^^^^ UP037
|
||||
19 | x: "MyClass"
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
15 15 | from mypy_extensions import Arg, DefaultArg, DefaultNamedArg, NamedArg, VarArg
|
||||
16 16 |
|
||||
17 17 |
|
||||
18 |-def foo(var: "MyClass") -> "MyClass":
|
||||
18 |+def foo(var: "MyClass") -> MyClass:
|
||||
19 19 | x: "MyClass"
|
||||
20 20 |
|
||||
21 21 |
|
||||
|
||||
UP037_0.py:19:8: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
18 | def foo(var: "MyClass") -> "MyClass":
|
||||
19 | x: "MyClass"
|
||||
| ^^^^^^^^^ UP037
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
16 16 |
|
||||
17 17 |
|
||||
18 18 | def foo(var: "MyClass") -> "MyClass":
|
||||
19 |- x: "MyClass"
|
||||
19 |+ x: MyClass
|
||||
20 20 |
|
||||
21 21 |
|
||||
22 22 | def foo(*, inplace: "bool"):
|
||||
|
||||
UP037_0.py:22:21: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
22 | def foo(*, inplace: "bool"):
|
||||
| ^^^^^^ UP037
|
||||
23 | pass
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
19 19 | x: "MyClass"
|
||||
20 20 |
|
||||
21 21 |
|
||||
22 |-def foo(*, inplace: "bool"):
|
||||
22 |+def foo(*, inplace: bool):
|
||||
23 23 | pass
|
||||
24 24 |
|
||||
25 25 |
|
||||
|
||||
UP037_0.py:26:16: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
26 | def foo(*args: "str", **kwargs: "int"):
|
||||
| ^^^^^ UP037
|
||||
27 | pass
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
23 23 | pass
|
||||
24 24 |
|
||||
25 25 |
|
||||
26 |-def foo(*args: "str", **kwargs: "int"):
|
||||
26 |+def foo(*args: str, **kwargs: "int"):
|
||||
27 27 | pass
|
||||
28 28 |
|
||||
29 29 |
|
||||
|
||||
UP037_0.py:26:33: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
26 | def foo(*args: "str", **kwargs: "int"):
|
||||
| ^^^^^ UP037
|
||||
27 | pass
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
23 23 | pass
|
||||
24 24 |
|
||||
25 25 |
|
||||
26 |-def foo(*args: "str", **kwargs: "int"):
|
||||
26 |+def foo(*args: "str", **kwargs: int):
|
||||
27 27 | pass
|
||||
28 28 |
|
||||
29 29 |
|
||||
|
||||
UP037_0.py:30:10: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
30 | x: Tuple["MyClass"]
|
||||
| ^^^^^^^^^ UP037
|
||||
31 |
|
||||
32 | x: Callable[["MyClass"], None]
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
27 27 | pass
|
||||
28 28 |
|
||||
29 29 |
|
||||
30 |-x: Tuple["MyClass"]
|
||||
30 |+x: Tuple[MyClass]
|
||||
31 31 |
|
||||
32 32 | x: Callable[["MyClass"], None]
|
||||
33 33 |
|
||||
|
||||
UP037_0.py:32:14: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
30 | x: Tuple["MyClass"]
|
||||
31 |
|
||||
32 | x: Callable[["MyClass"], None]
|
||||
| ^^^^^^^^^ UP037
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
29 29 |
|
||||
30 30 | x: Tuple["MyClass"]
|
||||
31 31 |
|
||||
32 |-x: Callable[["MyClass"], None]
|
||||
32 |+x: Callable[[MyClass], None]
|
||||
33 33 |
|
||||
34 34 |
|
||||
35 35 | class Foo(NamedTuple):
|
||||
|
||||
UP037_0.py:36:8: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
35 | class Foo(NamedTuple):
|
||||
36 | x: "MyClass"
|
||||
| ^^^^^^^^^ UP037
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
33 33 |
|
||||
34 34 |
|
||||
35 35 | class Foo(NamedTuple):
|
||||
36 |- x: "MyClass"
|
||||
36 |+ x: MyClass
|
||||
37 37 |
|
||||
38 38 |
|
||||
39 39 | class D(TypedDict):
|
||||
|
||||
UP037_0.py:40:27: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
39 | class D(TypedDict):
|
||||
40 | E: TypedDict("E", foo="int", total=False)
|
||||
| ^^^^^ UP037
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
37 37 |
|
||||
38 38 |
|
||||
39 39 | class D(TypedDict):
|
||||
40 |- E: TypedDict("E", foo="int", total=False)
|
||||
40 |+ E: TypedDict("E", foo=int, total=False)
|
||||
41 41 |
|
||||
42 42 |
|
||||
43 43 | class D(TypedDict):
|
||||
|
||||
UP037_0.py:44:31: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
43 | class D(TypedDict):
|
||||
44 | E: TypedDict("E", {"foo": "int"})
|
||||
| ^^^^^ UP037
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
41 41 |
|
||||
42 42 |
|
||||
43 43 | class D(TypedDict):
|
||||
44 |- E: TypedDict("E", {"foo": "int"})
|
||||
44 |+ E: TypedDict("E", {"foo": int})
|
||||
45 45 |
|
||||
46 46 |
|
||||
47 47 | x: Annotated["str", "metadata"]
|
||||
|
||||
UP037_0.py:47:14: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
47 | x: Annotated["str", "metadata"]
|
||||
| ^^^^^ UP037
|
||||
48 |
|
||||
49 | x: Arg("str", "name")
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
44 44 | E: TypedDict("E", {"foo": "int"})
|
||||
45 45 |
|
||||
46 46 |
|
||||
47 |-x: Annotated["str", "metadata"]
|
||||
47 |+x: Annotated[str, "metadata"]
|
||||
48 48 |
|
||||
49 49 | x: Arg("str", "name")
|
||||
50 50 |
|
||||
|
||||
UP037_0.py:49:8: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
47 | x: Annotated["str", "metadata"]
|
||||
48 |
|
||||
49 | x: Arg("str", "name")
|
||||
| ^^^^^ UP037
|
||||
50 |
|
||||
51 | x: DefaultArg("str", "name")
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
46 46 |
|
||||
47 47 | x: Annotated["str", "metadata"]
|
||||
48 48 |
|
||||
49 |-x: Arg("str", "name")
|
||||
49 |+x: Arg(str, "name")
|
||||
50 50 |
|
||||
51 51 | x: DefaultArg("str", "name")
|
||||
52 52 |
|
||||
|
||||
UP037_0.py:51:15: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
49 | x: Arg("str", "name")
|
||||
50 |
|
||||
51 | x: DefaultArg("str", "name")
|
||||
| ^^^^^ UP037
|
||||
52 |
|
||||
53 | x: NamedArg("str", "name")
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
48 48 |
|
||||
49 49 | x: Arg("str", "name")
|
||||
50 50 |
|
||||
51 |-x: DefaultArg("str", "name")
|
||||
51 |+x: DefaultArg(str, "name")
|
||||
52 52 |
|
||||
53 53 | x: NamedArg("str", "name")
|
||||
54 54 |
|
||||
|
||||
UP037_0.py:53:13: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
51 | x: DefaultArg("str", "name")
|
||||
52 |
|
||||
53 | x: NamedArg("str", "name")
|
||||
| ^^^^^ UP037
|
||||
54 |
|
||||
55 | x: DefaultNamedArg("str", "name")
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
50 50 |
|
||||
51 51 | x: DefaultArg("str", "name")
|
||||
52 52 |
|
||||
53 |-x: NamedArg("str", "name")
|
||||
53 |+x: NamedArg(str, "name")
|
||||
54 54 |
|
||||
55 55 | x: DefaultNamedArg("str", "name")
|
||||
56 56 |
|
||||
|
||||
UP037_0.py:55:20: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
53 | x: NamedArg("str", "name")
|
||||
54 |
|
||||
55 | x: DefaultNamedArg("str", "name")
|
||||
| ^^^^^ UP037
|
||||
56 |
|
||||
57 | x: DefaultNamedArg("str", name="name")
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
52 52 |
|
||||
53 53 | x: NamedArg("str", "name")
|
||||
54 54 |
|
||||
55 |-x: DefaultNamedArg("str", "name")
|
||||
55 |+x: DefaultNamedArg(str, "name")
|
||||
56 56 |
|
||||
57 57 | x: DefaultNamedArg("str", name="name")
|
||||
58 58 |
|
||||
|
||||
UP037_0.py:57:20: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
55 | x: DefaultNamedArg("str", "name")
|
||||
56 |
|
||||
57 | x: DefaultNamedArg("str", name="name")
|
||||
| ^^^^^ UP037
|
||||
58 |
|
||||
59 | x: VarArg("str")
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
54 54 |
|
||||
55 55 | x: DefaultNamedArg("str", "name")
|
||||
56 56 |
|
||||
57 |-x: DefaultNamedArg("str", name="name")
|
||||
57 |+x: DefaultNamedArg(str, name="name")
|
||||
58 58 |
|
||||
59 59 | x: VarArg("str")
|
||||
60 60 |
|
||||
|
||||
UP037_0.py:59:11: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
57 | x: DefaultNamedArg("str", name="name")
|
||||
58 |
|
||||
59 | x: VarArg("str")
|
||||
| ^^^^^ UP037
|
||||
60 |
|
||||
61 | x: List[List[List["MyClass"]]]
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
56 56 |
|
||||
57 57 | x: DefaultNamedArg("str", name="name")
|
||||
58 58 |
|
||||
59 |-x: VarArg("str")
|
||||
59 |+x: VarArg(str)
|
||||
60 60 |
|
||||
61 61 | x: List[List[List["MyClass"]]]
|
||||
62 62 |
|
||||
|
||||
UP037_0.py:61:19: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
59 | x: VarArg("str")
|
||||
60 |
|
||||
61 | x: List[List[List["MyClass"]]]
|
||||
| ^^^^^^^^^ UP037
|
||||
62 |
|
||||
63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
58 58 |
|
||||
59 59 | x: VarArg("str")
|
||||
60 60 |
|
||||
61 |-x: List[List[List["MyClass"]]]
|
||||
61 |+x: List[List[List[MyClass]]]
|
||||
62 62 |
|
||||
63 63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
|
||||
64 64 |
|
||||
|
||||
UP037_0.py:63:29: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
61 | x: List[List[List["MyClass"]]]
|
||||
62 |
|
||||
63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
|
||||
| ^^^^^ UP037
|
||||
64 |
|
||||
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
60 60 |
|
||||
61 61 | x: List[List[List["MyClass"]]]
|
||||
62 62 |
|
||||
63 |-x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
|
||||
63 |+x: NamedTuple("X", [("foo", int), ("bar", "str")])
|
||||
64 64 |
|
||||
65 65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
66 66 |
|
||||
|
||||
UP037_0.py:63:45: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
61 | x: List[List[List["MyClass"]]]
|
||||
62 |
|
||||
63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
|
||||
| ^^^^^ UP037
|
||||
64 |
|
||||
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
60 60 |
|
||||
61 61 | x: List[List[List["MyClass"]]]
|
||||
62 62 |
|
||||
63 |-x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
|
||||
63 |+x: NamedTuple("X", [("foo", "int"), ("bar", str)])
|
||||
64 64 |
|
||||
65 65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
66 66 |
|
||||
|
||||
UP037_0.py:65:29: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
|
||||
64 |
|
||||
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
| ^^^^^ UP037
|
||||
66 |
|
||||
67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
62 62 |
|
||||
63 63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
|
||||
64 64 |
|
||||
65 |-x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
65 |+x: NamedTuple("X", fields=[(foo, "int"), ("bar", "str")])
|
||||
66 66 |
|
||||
67 67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
|
||||
68 68 |
|
||||
|
||||
UP037_0.py:65:36: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
|
||||
64 |
|
||||
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
| ^^^^^ UP037
|
||||
66 |
|
||||
67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
62 62 |
|
||||
63 63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
|
||||
64 64 |
|
||||
65 |-x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
65 |+x: NamedTuple("X", fields=[("foo", int), ("bar", "str")])
|
||||
66 66 |
|
||||
67 67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
|
||||
68 68 |
|
||||
|
||||
UP037_0.py:65:45: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
|
||||
64 |
|
||||
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
| ^^^^^ UP037
|
||||
66 |
|
||||
67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
62 62 |
|
||||
63 63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
|
||||
64 64 |
|
||||
65 |-x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
65 |+x: NamedTuple("X", fields=[("foo", "int"), (bar, "str")])
|
||||
66 66 |
|
||||
67 67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
|
||||
68 68 |
|
||||
|
||||
UP037_0.py:65:52: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
|
||||
64 |
|
||||
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
| ^^^^^ UP037
|
||||
66 |
|
||||
67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
62 62 |
|
||||
63 63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
|
||||
64 64 |
|
||||
65 |-x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
65 |+x: NamedTuple("X", fields=[("foo", "int"), ("bar", str)])
|
||||
66 66 |
|
||||
67 67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
|
||||
68 68 |
|
||||
|
||||
UP037_0.py:67:24: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
66 |
|
||||
67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
|
||||
| ^^^ UP037
|
||||
68 |
|
||||
69 | X: MyCallable("X")
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
64 64 |
|
||||
65 65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
66 66 |
|
||||
67 |-x: NamedTuple(typename="X", fields=[("foo", "int")])
|
||||
67 |+x: NamedTuple(typename=X, fields=[("foo", "int")])
|
||||
68 68 |
|
||||
69 69 | X: MyCallable("X")
|
||||
70 70 |
|
||||
|
||||
UP037_0.py:67:38: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
66 |
|
||||
67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
|
||||
| ^^^^^ UP037
|
||||
68 |
|
||||
69 | X: MyCallable("X")
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
64 64 |
|
||||
65 65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
66 66 |
|
||||
67 |-x: NamedTuple(typename="X", fields=[("foo", "int")])
|
||||
67 |+x: NamedTuple(typename="X", fields=[(foo, "int")])
|
||||
68 68 |
|
||||
69 69 | X: MyCallable("X")
|
||||
70 70 |
|
||||
|
||||
UP037_0.py:67:45: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
66 |
|
||||
67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
|
||||
| ^^^^^ UP037
|
||||
68 |
|
||||
69 | X: MyCallable("X")
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
64 64 |
|
||||
65 65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
66 66 |
|
||||
67 |-x: NamedTuple(typename="X", fields=[("foo", "int")])
|
||||
67 |+x: NamedTuple(typename="X", fields=[("foo", int)])
|
||||
68 68 |
|
||||
69 69 | X: MyCallable("X")
|
||||
70 70 |
|
||||
|
||||
UP037_0.py:112:12: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
110 | # Handle end of line comment in string annotation
|
||||
111 | # See https://github.com/astral-sh/ruff/issues/15816
|
||||
112 | def f() -> "Literal[0]#":
|
||||
| ^^^^^^^^^^^^^ UP037
|
||||
113 | return 0
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
109 109 |
|
||||
110 110 | # Handle end of line comment in string annotation
|
||||
111 111 | # See https://github.com/astral-sh/ruff/issues/15816
|
||||
112 |-def f() -> "Literal[0]#":
|
||||
112 |+def f() -> (Literal[0]#
|
||||
113 |+):
|
||||
113 114 | return 0
|
||||
114 115 |
|
||||
115 116 | def g(x: "Literal['abc']#") -> None:
|
||||
|
||||
UP037_0.py:115:10: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
113 | return 0
|
||||
114 |
|
||||
115 | def g(x: "Literal['abc']#") -> None:
|
||||
| ^^^^^^^^^^^^^^^^^ UP037
|
||||
116 | return
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
112 112 | def f() -> "Literal[0]#":
|
||||
113 113 | return 0
|
||||
114 114 |
|
||||
115 |-def g(x: "Literal['abc']#") -> None:
|
||||
115 |+def g(x: (Literal['abc']#
|
||||
116 |+)) -> None:
|
||||
116 117 | return
|
||||
117 118 |
|
||||
118 119 | def f() -> """Literal[0]
|
||||
|
||||
UP037_0.py:118:12: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
116 | return
|
||||
117 |
|
||||
118 | def f() -> """Literal[0]
|
||||
| ____________^
|
||||
119 | | #
|
||||
120 | |
|
||||
121 | | """:
|
||||
| |_______^ UP037
|
||||
122 | return 0
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
115 115 | def g(x: "Literal['abc']#") -> None:
|
||||
116 116 | return
|
||||
117 117 |
|
||||
118 |-def f() -> """Literal[0]
|
||||
118 |+def f() -> (Literal[0]
|
||||
119 119 | #
|
||||
120 120 |
|
||||
121 |- """:
|
||||
121 |+ ):
|
||||
122 122 | return 0
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
|
||||
---
|
||||
UP037_1.py:9:8: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
7 | def foo():
|
||||
8 | # UP037
|
||||
9 | x: "Tuple[int, int]" = (0, 0)
|
||||
| ^^^^^^^^^^^^^^^^^ UP037
|
||||
10 | print(x)
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
6 6 |
|
||||
7 7 | def foo():
|
||||
8 8 | # UP037
|
||||
9 |- x: "Tuple[int, int]" = (0, 0)
|
||||
9 |+ x: Tuple[int, int] = (0, 0)
|
||||
10 10 | print(x)
|
||||
11 11 |
|
||||
12 12 |
|
||||
|
||||
UP037_1.py:14:4: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
13 | # OK
|
||||
14 | X: "Tuple[int, int]" = (0, 0)
|
||||
| ^^^^^^^^^^^^^^^^^ UP037
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from typing import TYPE_CHECKING
|
||||
2 3 |
|
||||
3 4 | if TYPE_CHECKING:
|
||||
--------------------------------------------------------------------------------
|
||||
11 12 |
|
||||
12 13 |
|
||||
13 14 | # OK
|
||||
14 |-X: "Tuple[int, int]" = (0, 0)
|
||||
15 |+X: Tuple[int, int] = (0, 0)
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
|
||||
---
|
||||
UP037_2.pyi:3:14: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
1 | # https://github.com/astral-sh/ruff/issues/7102
|
||||
2 |
|
||||
3 | def f(a: Foo['SingleLine # Comment']): ...
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ UP037
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | # https://github.com/astral-sh/ruff/issues/7102
|
||||
2 2 |
|
||||
3 |-def f(a: Foo['SingleLine # Comment']): ...
|
||||
3 |+def f(a: Foo[(SingleLine # Comment
|
||||
4 |+)]): ...
|
||||
4 5 |
|
||||
5 6 |
|
||||
6 7 | def f(a: Foo['''Bar[
|
||||
|
||||
UP037_2.pyi:6:14: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
6 | def f(a: Foo['''Bar[
|
||||
| ______________^
|
||||
7 | | Multi |
|
||||
8 | | Line]''']): ...
|
||||
| |____________^ UP037
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
3 3 | def f(a: Foo['SingleLine # Comment']): ...
|
||||
4 4 |
|
||||
5 5 |
|
||||
6 |-def f(a: Foo['''Bar[
|
||||
6 |+def f(a: Foo[Bar[
|
||||
7 7 | Multi |
|
||||
8 |- Line]''']): ...
|
||||
8 |+ Line]]): ...
|
||||
9 9 |
|
||||
10 10 |
|
||||
11 11 | def f(a: Foo['''Bar[
|
||||
|
||||
UP037_2.pyi:11:14: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
11 | def f(a: Foo['''Bar[
|
||||
| ______________^
|
||||
12 | | Multi |
|
||||
13 | | Line # Comment
|
||||
14 | | ]''']): ...
|
||||
| |____^ UP037
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
8 8 | Line]''']): ...
|
||||
9 9 |
|
||||
10 10 |
|
||||
11 |-def f(a: Foo['''Bar[
|
||||
11 |+def f(a: Foo[Bar[
|
||||
12 12 | Multi |
|
||||
13 13 | Line # Comment
|
||||
14 |-]''']): ...
|
||||
14 |+]]): ...
|
||||
15 15 |
|
||||
16 16 |
|
||||
17 17 | def f(a: Foo['''Bar[
|
||||
|
||||
UP037_2.pyi:17:14: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
17 | def f(a: Foo['''Bar[
|
||||
| ______________^
|
||||
18 | | Multi |
|
||||
19 | | Line] # Comment''']): ...
|
||||
| |_______________________^ UP037
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
14 14 | ]''']): ...
|
||||
15 15 |
|
||||
16 16 |
|
||||
17 |-def f(a: Foo['''Bar[
|
||||
17 |+def f(a: Foo[(Bar[
|
||||
18 18 | Multi |
|
||||
19 |- Line] # Comment''']): ...
|
||||
19 |+ Line] # Comment
|
||||
20 |+)]): ...
|
||||
20 21 |
|
||||
21 22 |
|
||||
22 23 | def f(a: Foo['''
|
||||
|
||||
UP037_2.pyi:22:14: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
22 | def f(a: Foo['''
|
||||
| ______________^
|
||||
23 | | Bar[
|
||||
24 | | Multi |
|
||||
25 | | Line] # Comment''']): ...
|
||||
| |_______________________^ UP037
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
19 19 | Line] # Comment''']): ...
|
||||
20 20 |
|
||||
21 21 |
|
||||
22 |-def f(a: Foo['''
|
||||
22 |+def f(a: Foo[(
|
||||
23 23 | Bar[
|
||||
24 24 | Multi |
|
||||
25 |- Line] # Comment''']): ...
|
||||
25 |+ Line] # Comment
|
||||
26 |+)]): ...
|
||||
26 27 |
|
||||
27 28 |
|
||||
28 29 | def f(a: '''list[int]
|
||||
|
||||
UP037_2.pyi:28:10: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
28 | def f(a: '''list[int]
|
||||
| __________^
|
||||
29 | | ''' = []): ...
|
||||
| |_______^ UP037
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
25 25 | Line] # Comment''']): ...
|
||||
26 26 |
|
||||
27 27 |
|
||||
28 |-def f(a: '''list[int]
|
||||
29 |- ''' = []): ...
|
||||
28 |+def f(a: list[int]
|
||||
29 |+ = []): ...
|
||||
30 30 |
|
||||
31 31 |
|
||||
32 32 | a: '''\\
|
||||
|
||||
UP037_2.pyi:32:4: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
32 | a: '''\\
|
||||
| ____^
|
||||
33 | | list[int]''' = [42]
|
||||
| |____________^ UP037
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
29 29 | ''' = []): ...
|
||||
30 30 |
|
||||
31 31 |
|
||||
32 |-a: '''\\
|
||||
33 |-list[int]''' = [42]
|
||||
32 |+a: (\
|
||||
33 |+list[int]) = [42]
|
||||
34 34 |
|
||||
35 35 |
|
||||
36 36 | def f(a: '''
|
||||
|
||||
UP037_2.pyi:36:10: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
36 | def f(a: '''
|
||||
| __________^
|
||||
37 | | list[int]
|
||||
38 | | ''' = []): ...
|
||||
| |_______^ UP037
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
33 33 | list[int]''' = [42]
|
||||
34 34 |
|
||||
35 35 |
|
||||
36 |-def f(a: '''
|
||||
36 |+def f(a:
|
||||
37 37 | list[int]
|
||||
38 |- ''' = []): ...
|
||||
38 |+ = []): ...
|
||||
39 39 |
|
||||
40 40 |
|
||||
41 41 | def f(a: Foo['''
|
||||
|
||||
UP037_2.pyi:41:14: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
41 | def f(a: Foo['''
|
||||
| ______________^
|
||||
42 | | Bar
|
||||
43 | | [
|
||||
44 | | Multi |
|
||||
45 | | Line
|
||||
46 | | ] # Comment''']): ...
|
||||
| |___________________^ UP037
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
38 38 | ''' = []): ...
|
||||
39 39 |
|
||||
40 40 |
|
||||
41 |-def f(a: Foo['''
|
||||
41 |+def f(a: Foo[(
|
||||
42 42 | Bar
|
||||
43 43 | [
|
||||
44 44 | Multi |
|
||||
45 45 | Line
|
||||
46 |- ] # Comment''']): ...
|
||||
46 |+ ] # Comment
|
||||
47 |+)]): ...
|
||||
47 48 |
|
||||
48 49 |
|
||||
49 50 | a: '''list
|
||||
|
||||
UP037_2.pyi:49:4: UP037 [*] Remove quotes from type annotation
|
||||
|
|
||||
49 | a: '''list
|
||||
| ____^
|
||||
50 | | [int]''' = [42]
|
||||
| |________^ UP037
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
46 46 | ] # Comment''']): ...
|
||||
47 47 |
|
||||
48 48 |
|
||||
49 |-a: '''list
|
||||
50 |-[int]''' = [42]
|
||||
49 |+a: (list
|
||||
50 |+[int]) = [42]
|
||||
|
|
@ -599,4 +599,24 @@ mod tests {
|
|||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::ImplicitOptional, Path::new("RUF013_0.py"))]
|
||||
#[test_case(Rule::ImplicitOptional, Path::new("RUF013_1.py"))]
|
||||
#[test_case(Rule::ImplicitOptional, Path::new("RUF013_2.py"))]
|
||||
#[test_case(Rule::ImplicitOptional, Path::new("RUF013_3.py"))]
|
||||
#[test_case(Rule::ImplicitOptional, Path::new("RUF013_4.py"))]
|
||||
fn ruf013_add_future_import(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("add_future_import_{}", path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("ruff").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
future_annotations: true,
|
||||
unresolved_target_version: PythonVersion::PY39.into(),
|
||||
..settings::LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,13 @@ use crate::rules::ruff::typing::type_hint_explicitly_allows_none;
|
|||
///
|
||||
/// ## Options
|
||||
/// - `target-version`
|
||||
/// - `lint.future-annotations`
|
||||
///
|
||||
/// ## Preview
|
||||
///
|
||||
/// When [preview] is enabled, if [`lint.future-annotations`] is set to `true`,
|
||||
/// `from __future__ import annotations` will be added if doing so would allow using the `|`
|
||||
/// operator on a Python version before 3.10.
|
||||
///
|
||||
/// ## Fix safety
|
||||
///
|
||||
|
|
@ -136,10 +143,15 @@ fn generate_fix(checker: &Checker, conversion_type: ConversionType, expr: &Expr)
|
|||
node_index: ruff_python_ast::AtomicNodeIndex::dummy(),
|
||||
});
|
||||
let content = checker.generator().expr(&new_expr);
|
||||
Ok(Fix::unsafe_edit(Edit::range_replacement(
|
||||
content,
|
||||
expr.range(),
|
||||
)))
|
||||
let edit = Edit::range_replacement(content, expr.range());
|
||||
if checker.target_version() < PythonVersion::PY310 {
|
||||
Ok(Fix::unsafe_edits(
|
||||
edit,
|
||||
[checker.importer().add_future_import()],
|
||||
))
|
||||
} else {
|
||||
Ok(Fix::unsafe_edit(edit))
|
||||
}
|
||||
}
|
||||
ConversionType::Optional => {
|
||||
let importer = checker
|
||||
|
|
@ -187,6 +199,7 @@ pub(crate) fn implicit_optional(checker: &Checker, parameters: &Parameters) {
|
|||
) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let conversion_type = checker.target_version().into();
|
||||
|
||||
let mut diagnostic =
|
||||
|
|
@ -202,7 +215,14 @@ pub(crate) fn implicit_optional(checker: &Checker, parameters: &Parameters) {
|
|||
else {
|
||||
continue;
|
||||
};
|
||||
let conversion_type = checker.target_version().into();
|
||||
|
||||
let conversion_type = if checker.target_version() >= PythonVersion::PY310
|
||||
|| checker.settings().future_annotations()
|
||||
{
|
||||
ConversionType::BinOpOr
|
||||
} else {
|
||||
ConversionType::Optional
|
||||
};
|
||||
|
||||
let mut diagnostic =
|
||||
checker.report_diagnostic(ImplicitOptional { conversion_type }, expr.range());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,445 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF013_0.py:20:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
20 | def f(arg: int = None): # RUF013
|
||||
| ^^^ RUF013
|
||||
21 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
17 18 | pass
|
||||
18 19 |
|
||||
19 20 |
|
||||
20 |-def f(arg: int = None): # RUF013
|
||||
21 |+def f(arg: int | None = None): # RUF013
|
||||
21 22 | pass
|
||||
22 23 |
|
||||
23 24 |
|
||||
|
||||
RUF013_0.py:24:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
24 | def f(arg: str = None): # RUF013
|
||||
| ^^^ RUF013
|
||||
25 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
21 22 | pass
|
||||
22 23 |
|
||||
23 24 |
|
||||
24 |-def f(arg: str = None): # RUF013
|
||||
25 |+def f(arg: str | None = None): # RUF013
|
||||
25 26 | pass
|
||||
26 27 |
|
||||
27 28 |
|
||||
|
||||
RUF013_0.py:28:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
28 | def f(arg: Tuple[str] = None): # RUF013
|
||||
| ^^^^^^^^^^ RUF013
|
||||
29 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
25 26 | pass
|
||||
26 27 |
|
||||
27 28 |
|
||||
28 |-def f(arg: Tuple[str] = None): # RUF013
|
||||
29 |+def f(arg: Tuple[str] | None = None): # RUF013
|
||||
29 30 | pass
|
||||
30 31 |
|
||||
31 32 |
|
||||
|
||||
RUF013_0.py:58:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
58 | def f(arg: Union = None): # RUF013
|
||||
| ^^^^^ RUF013
|
||||
59 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
55 56 | pass
|
||||
56 57 |
|
||||
57 58 |
|
||||
58 |-def f(arg: Union = None): # RUF013
|
||||
59 |+def f(arg: Union | None = None): # RUF013
|
||||
59 60 | pass
|
||||
60 61 |
|
||||
61 62 |
|
||||
|
||||
RUF013_0.py:62:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
62 | def f(arg: Union[int] = None): # RUF013
|
||||
| ^^^^^^^^^^ RUF013
|
||||
63 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
59 60 | pass
|
||||
60 61 |
|
||||
61 62 |
|
||||
62 |-def f(arg: Union[int] = None): # RUF013
|
||||
63 |+def f(arg: Union[int] | None = None): # RUF013
|
||||
63 64 | pass
|
||||
64 65 |
|
||||
65 66 |
|
||||
|
||||
RUF013_0.py:66:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
66 | def f(arg: Union[int, str] = None): # RUF013
|
||||
| ^^^^^^^^^^^^^^^ RUF013
|
||||
67 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
63 64 | pass
|
||||
64 65 |
|
||||
65 66 |
|
||||
66 |-def f(arg: Union[int, str] = None): # RUF013
|
||||
67 |+def f(arg: Union[int, str] | None = None): # RUF013
|
||||
67 68 | pass
|
||||
68 69 |
|
||||
69 70 |
|
||||
|
||||
RUF013_0.py:85:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
85 | def f(arg: int | float = None): # RUF013
|
||||
| ^^^^^^^^^^^ RUF013
|
||||
86 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
82 83 | pass
|
||||
83 84 |
|
||||
84 85 |
|
||||
85 |-def f(arg: int | float = None): # RUF013
|
||||
86 |+def f(arg: int | float | None = None): # RUF013
|
||||
86 87 | pass
|
||||
87 88 |
|
||||
88 89 |
|
||||
|
||||
RUF013_0.py:89:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
89 | def f(arg: int | float | str | bytes = None): # RUF013
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF013
|
||||
90 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
86 87 | pass
|
||||
87 88 |
|
||||
88 89 |
|
||||
89 |-def f(arg: int | float | str | bytes = None): # RUF013
|
||||
90 |+def f(arg: int | float | str | bytes | None = None): # RUF013
|
||||
90 91 | pass
|
||||
91 92 |
|
||||
92 93 |
|
||||
|
||||
RUF013_0.py:108:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
108 | def f(arg: Literal[1] = None): # RUF013
|
||||
| ^^^^^^^^^^ RUF013
|
||||
109 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
105 106 | pass
|
||||
106 107 |
|
||||
107 108 |
|
||||
108 |-def f(arg: Literal[1] = None): # RUF013
|
||||
109 |+def f(arg: Literal[1] | None = None): # RUF013
|
||||
109 110 | pass
|
||||
110 111 |
|
||||
111 112 |
|
||||
|
||||
RUF013_0.py:112:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
112 | def f(arg: Literal[1, "foo"] = None): # RUF013
|
||||
| ^^^^^^^^^^^^^^^^^ RUF013
|
||||
113 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
109 110 | pass
|
||||
110 111 |
|
||||
111 112 |
|
||||
112 |-def f(arg: Literal[1, "foo"] = None): # RUF013
|
||||
113 |+def f(arg: Literal[1, "foo"] | None = None): # RUF013
|
||||
113 114 | pass
|
||||
114 115 |
|
||||
115 116 |
|
||||
|
||||
RUF013_0.py:131:22: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
131 | def f(arg: Annotated[int, ...] = None): # RUF013
|
||||
| ^^^ RUF013
|
||||
132 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
128 129 | pass
|
||||
129 130 |
|
||||
130 131 |
|
||||
131 |-def f(arg: Annotated[int, ...] = None): # RUF013
|
||||
132 |+def f(arg: Annotated[int | None, ...] = None): # RUF013
|
||||
132 133 | pass
|
||||
133 134 |
|
||||
134 135 |
|
||||
|
||||
RUF013_0.py:135:32: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
135 | def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF013
|
||||
| ^^^^^^^^^ RUF013
|
||||
136 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
132 133 | pass
|
||||
133 134 |
|
||||
134 135 |
|
||||
135 |-def f(arg: Annotated[Annotated[int | str, ...], ...] = None): # RUF013
|
||||
136 |+def f(arg: Annotated[Annotated[int | str | None, ...], ...] = None): # RUF013
|
||||
136 137 | pass
|
||||
137 138 |
|
||||
138 139 |
|
||||
|
||||
RUF013_0.py:151:11: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
150 | def f(
|
||||
151 | arg1: int = None, # RUF013
|
||||
| ^^^ RUF013
|
||||
152 | arg2: Union[int, float] = None, # RUF013
|
||||
153 | arg3: Literal[1, 2, 3] = None, # RUF013
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
148 149 |
|
||||
149 150 |
|
||||
150 151 | def f(
|
||||
151 |- arg1: int = None, # RUF013
|
||||
152 |+ arg1: int | None = None, # RUF013
|
||||
152 153 | arg2: Union[int, float] = None, # RUF013
|
||||
153 154 | arg3: Literal[1, 2, 3] = None, # RUF013
|
||||
154 155 | ):
|
||||
|
||||
RUF013_0.py:152:11: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
150 | def f(
|
||||
151 | arg1: int = None, # RUF013
|
||||
152 | arg2: Union[int, float] = None, # RUF013
|
||||
| ^^^^^^^^^^^^^^^^^ RUF013
|
||||
153 | arg3: Literal[1, 2, 3] = None, # RUF013
|
||||
154 | ):
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
149 150 |
|
||||
150 151 | def f(
|
||||
151 152 | arg1: int = None, # RUF013
|
||||
152 |- arg2: Union[int, float] = None, # RUF013
|
||||
153 |+ arg2: Union[int, float] | None = None, # RUF013
|
||||
153 154 | arg3: Literal[1, 2, 3] = None, # RUF013
|
||||
154 155 | ):
|
||||
155 156 | pass
|
||||
|
||||
RUF013_0.py:153:11: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
151 | arg1: int = None, # RUF013
|
||||
152 | arg2: Union[int, float] = None, # RUF013
|
||||
153 | arg3: Literal[1, 2, 3] = None, # RUF013
|
||||
| ^^^^^^^^^^^^^^^^ RUF013
|
||||
154 | ):
|
||||
155 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
150 151 | def f(
|
||||
151 152 | arg1: int = None, # RUF013
|
||||
152 153 | arg2: Union[int, float] = None, # RUF013
|
||||
153 |- arg3: Literal[1, 2, 3] = None, # RUF013
|
||||
154 |+ arg3: Literal[1, 2, 3] | None = None, # RUF013
|
||||
154 155 | ):
|
||||
155 156 | pass
|
||||
156 157 |
|
||||
|
||||
RUF013_0.py:181:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
181 | def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF013
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF013
|
||||
182 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
178 179 | pass
|
||||
179 180 |
|
||||
180 181 |
|
||||
181 |-def f(arg: Union[Annotated[int, ...], Union[str, bytes]] = None): # RUF013
|
||||
182 |+def f(arg: Union[Annotated[int, ...], Union[str, bytes]] | None = None): # RUF013
|
||||
182 183 | pass
|
||||
183 184 |
|
||||
184 185 |
|
||||
|
||||
RUF013_0.py:188:13: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
188 | def f(arg: "int" = None): # RUF013
|
||||
| ^^^ RUF013
|
||||
189 | pass
|
||||
|
|
||||
= help: Convert to `Optional[T]`
|
||||
|
||||
ℹ Unsafe fix
|
||||
185 185 | # Quoted
|
||||
186 186 |
|
||||
187 187 |
|
||||
188 |-def f(arg: "int" = None): # RUF013
|
||||
188 |+def f(arg: "Optional[int]" = None): # RUF013
|
||||
189 189 | pass
|
||||
190 190 |
|
||||
191 191 |
|
||||
|
||||
RUF013_0.py:192:13: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
192 | def f(arg: "str" = None): # RUF013
|
||||
| ^^^ RUF013
|
||||
193 | pass
|
||||
|
|
||||
= help: Convert to `Optional[T]`
|
||||
|
||||
ℹ Unsafe fix
|
||||
189 189 | pass
|
||||
190 190 |
|
||||
191 191 |
|
||||
192 |-def f(arg: "str" = None): # RUF013
|
||||
192 |+def f(arg: "Optional[str]" = None): # RUF013
|
||||
193 193 | pass
|
||||
194 194 |
|
||||
195 195 |
|
||||
|
||||
RUF013_0.py:196:12: RUF013 PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
196 | def f(arg: "st" "r" = None): # RUF013
|
||||
| ^^^^^^^^ RUF013
|
||||
197 | pass
|
||||
|
|
||||
= help: Convert to `Optional[T]`
|
||||
|
||||
RUF013_0.py:204:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
204 | def f(arg: Union["int", "str"] = None): # RUF013
|
||||
| ^^^^^^^^^^^^^^^^^^^ RUF013
|
||||
205 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
201 202 | pass
|
||||
202 203 |
|
||||
203 204 |
|
||||
204 |-def f(arg: Union["int", "str"] = None): # RUF013
|
||||
205 |+def f(arg: Union["int", "str"] | None = None): # RUF013
|
||||
205 206 | pass
|
||||
206 207 |
|
||||
207 208 |
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF013_1.py:4:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
4 | def f(arg: int = None): # RUF013
|
||||
| ^^^ RUF013
|
||||
5 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | # No `typing.Optional` import
|
||||
2 |+from __future__ import annotations
|
||||
2 3 |
|
||||
3 4 |
|
||||
4 |-def f(arg: int = None): # RUF013
|
||||
5 |+def f(arg: int | None = None): # RUF013
|
||||
5 6 | pass
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF013_3.py:4:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
4 | def f(arg: typing.List[str] = None): # RUF013
|
||||
| ^^^^^^^^^^^^^^^^ RUF013
|
||||
5 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | import typing
|
||||
2 3 |
|
||||
3 4 |
|
||||
4 |-def f(arg: typing.List[str] = None): # RUF013
|
||||
5 |+def f(arg: typing.List[str] | None = None): # RUF013
|
||||
5 6 | pass
|
||||
6 7 |
|
||||
7 8 |
|
||||
|
||||
RUF013_3.py:22:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
22 | def f(arg: typing.Union[int, str] = None): # RUF013
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ RUF013
|
||||
23 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | import typing
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
19 20 | pass
|
||||
20 21 |
|
||||
21 22 |
|
||||
22 |-def f(arg: typing.Union[int, str] = None): # RUF013
|
||||
23 |+def f(arg: typing.Union[int, str] | None = None): # RUF013
|
||||
23 24 | pass
|
||||
24 25 |
|
||||
25 26 |
|
||||
|
||||
RUF013_3.py:29:12: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
29 | def f(arg: typing.Literal[1, "foo", True] = None): # RUF013
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF013
|
||||
30 | pass
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from __future__ import annotations
|
||||
1 2 | import typing
|
||||
2 3 |
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
26 27 | # Literal
|
||||
27 28 |
|
||||
28 29 |
|
||||
29 |-def f(arg: typing.Literal[1, "foo", True] = None): # RUF013
|
||||
30 |+def f(arg: typing.Literal[1, "foo", True] | None = None): # RUF013
|
||||
30 31 | pass
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF013_4.py:15:61: RUF013 [*] PEP 484 prohibits implicit `Optional`
|
||||
|
|
||||
15 | def multiple_2(arg1: Optional, arg2: Optional = None, arg3: int = None): ...
|
||||
| ^^^ RUF013
|
||||
|
|
||||
= help: Convert to `T | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | # https://github.com/astral-sh/ruff/issues/13833
|
||||
2 |+from __future__ import annotations
|
||||
2 3 |
|
||||
3 4 | from typing import Optional
|
||||
4 5 |
|
||||
--------------------------------------------------------------------------------
|
||||
12 13 | def multiple_1(arg1: Optional, arg2: Optional = None): ...
|
||||
13 14 |
|
||||
14 15 |
|
||||
15 |-def multiple_2(arg1: Optional, arg2: Optional = None, arg3: int = None): ...
|
||||
16 |+def multiple_2(arg1: Optional, arg2: Optional = None, arg3: int | None = None): ...
|
||||
16 17 |
|
||||
17 18 |
|
||||
18 19 | def return_type(arg: Optional = None) -> Optional: ...
|
||||
|
|
@ -210,6 +210,7 @@ macro_rules! display_settings {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, CacheKey)]
|
||||
#[expect(clippy::struct_excessive_bools)]
|
||||
pub struct LinterSettings {
|
||||
pub exclude: FilePatternSet,
|
||||
pub extension: ExtensionMapping,
|
||||
|
|
@ -251,6 +252,7 @@ pub struct LinterSettings {
|
|||
pub task_tags: Vec<String>,
|
||||
pub typing_modules: Vec<String>,
|
||||
pub typing_extensions: bool,
|
||||
pub future_annotations: bool,
|
||||
|
||||
// Plugins
|
||||
pub flake8_annotations: flake8_annotations::settings::Settings,
|
||||
|
|
@ -453,6 +455,7 @@ impl LinterSettings {
|
|||
explicit_preview_rules: false,
|
||||
extension: ExtensionMapping::default(),
|
||||
typing_extensions: true,
|
||||
future_annotations: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -472,6 +475,11 @@ impl LinterSettings {
|
|||
.is_match(path)
|
||||
.map_or(self.unresolved_target_version, TargetVersion::from)
|
||||
}
|
||||
|
||||
pub fn future_annotations(&self) -> bool {
|
||||
// TODO(brent) we can just access the field directly once this is stabilized.
|
||||
self.future_annotations && crate::preview::is_add_future_annotations_imports_enabled(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LinterSettings {
|
||||
|
|
|
|||
|
|
@ -250,6 +250,14 @@ impl Configuration {
|
|||
|
||||
conflicting_import_settings(&isort, &flake8_import_conventions)?;
|
||||
|
||||
let future_annotations = lint.future_annotations.unwrap_or_default();
|
||||
if lint_preview.is_disabled() && future_annotations {
|
||||
warn_user_once!(
|
||||
"The `lint.future-annotations` setting will have no effect \
|
||||
because `preview` is disabled"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Settings {
|
||||
cache_dir: self
|
||||
.cache_dir
|
||||
|
|
@ -432,6 +440,7 @@ impl Configuration {
|
|||
.map(RuffOptions::into_settings)
|
||||
.unwrap_or_default(),
|
||||
typing_extensions: lint.typing_extensions.unwrap_or(true),
|
||||
future_annotations,
|
||||
},
|
||||
|
||||
formatter,
|
||||
|
|
@ -636,6 +645,7 @@ pub struct LintConfiguration {
|
|||
pub task_tags: Option<Vec<String>>,
|
||||
pub typing_modules: Option<Vec<String>>,
|
||||
pub typing_extensions: Option<bool>,
|
||||
pub future_annotations: Option<bool>,
|
||||
|
||||
// Plugins
|
||||
pub flake8_annotations: Option<Flake8AnnotationsOptions>,
|
||||
|
|
@ -752,6 +762,7 @@ impl LintConfiguration {
|
|||
logger_objects: options.common.logger_objects,
|
||||
typing_modules: options.common.typing_modules,
|
||||
typing_extensions: options.typing_extensions,
|
||||
future_annotations: options.future_annotations,
|
||||
|
||||
// Plugins
|
||||
flake8_annotations: options.common.flake8_annotations,
|
||||
|
|
@ -1179,6 +1190,7 @@ impl LintConfiguration {
|
|||
pyupgrade: self.pyupgrade.combine(config.pyupgrade),
|
||||
ruff: self.ruff.combine(config.ruff),
|
||||
typing_extensions: self.typing_extensions.or(config.typing_extensions),
|
||||
future_annotations: self.future_annotations.or(config.future_annotations),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -529,6 +529,24 @@ pub struct LintOptions {
|
|||
"#
|
||||
)]
|
||||
pub typing_extensions: Option<bool>,
|
||||
|
||||
/// Whether to allow rules to add `from __future__ import annotations` in cases where this would
|
||||
/// simplify a fix or enable a new diagnostic.
|
||||
///
|
||||
/// For example, `TC001`, `TC002`, and `TC003` can move more imports into `TYPE_CHECKING` blocks
|
||||
/// if `__future__` annotations are enabled.
|
||||
///
|
||||
/// This setting is currently in [preview](https://docs.astral.sh/ruff/preview/) and requires
|
||||
/// preview mode to be enabled to have any effect.
|
||||
#[option(
|
||||
default = "false",
|
||||
value_type = "bool",
|
||||
example = r#"
|
||||
# Enable `from __future__ import annotations` imports
|
||||
future-annotations = true
|
||||
"#
|
||||
)]
|
||||
pub future_annotations: Option<bool>,
|
||||
}
|
||||
|
||||
/// Newtype wrapper for [`LintCommonOptions`] that allows customizing the JSON schema and omitting the fields from the [`OptionsMetadata`].
|
||||
|
|
@ -3896,6 +3914,7 @@ pub struct LintOptionsWire {
|
|||
ruff: Option<RuffOptions>,
|
||||
preview: Option<bool>,
|
||||
typing_extensions: Option<bool>,
|
||||
future_annotations: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<LintOptionsWire> for LintOptions {
|
||||
|
|
@ -3951,6 +3970,7 @@ impl From<LintOptionsWire> for LintOptions {
|
|||
ruff,
|
||||
preview,
|
||||
typing_extensions,
|
||||
future_annotations,
|
||||
} = value;
|
||||
|
||||
LintOptions {
|
||||
|
|
@ -4007,6 +4027,7 @@ impl From<LintOptionsWire> for LintOptions {
|
|||
ruff,
|
||||
preview,
|
||||
typing_extensions,
|
||||
future_annotations,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
7
ruff.schema.json
generated
7
ruff.schema.json
generated
|
|
@ -2281,6 +2281,13 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"future-annotations": {
|
||||
"description": "Whether to allow rules to add `from __future__ import annotations` in cases where this would simplify a fix or enable a new diagnostic.\n\nFor example, `TC001`, `TC002`, and `TC003` can move more imports into `TYPE_CHECKING` blocks if `__future__` annotations are enabled.\n\nThis setting is currently in [preview](https://docs.astral.sh/ruff/preview/) and requires preview mode to be enabled to have any effect.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"ignore": {
|
||||
"description": "A list of rule codes or prefixes to ignore. Prefixes can specify exact rules (like `F841`), entire categories (like `F`), or anything in between.\n\nWhen breaking ties between enabled and disabled rules (via `select` and `ignore`, respectively), more specific prefixes override less specific prefixes. `ignore` takes precedence over `select` if the same prefix appears in both.",
|
||||
"type": [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue