Skip to content

Commit 893f572

Browse files
ntBreAlexWaygood
andauthored
[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 #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](#18502 (comment)), 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 #18919 (comment) 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 | # #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>
1 parent b8dddd5 commit 893f572

File tree

37 files changed

+2488
-170
lines changed

37 files changed

+2488
-170
lines changed

crates/ruff/tests/lint.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,7 @@ fn value_given_to_table_key_is_not_inline_table_2() {
993993
- `lint.exclude`
994994
- `lint.preview`
995995
- `lint.typing-extensions`
996+
- `lint.future-annotations`
996997
997998
For more information, try '--help'.
998999
");
@@ -5744,3 +5745,25 @@ match 42: # invalid-syntax
57445745

57455746
Ok(())
57465747
}
5748+
5749+
#[test]
5750+
fn future_annotations_preview_warning() {
5751+
assert_cmd_snapshot!(
5752+
Command::new(get_cargo_bin(BIN_NAME))
5753+
.args(STDIN_BASE_OPTIONS)
5754+
.args(["--config", "lint.future-annotations = true"])
5755+
.args(["--select", "F"])
5756+
.arg("--no-preview")
5757+
.arg("-")
5758+
.pass_stdin("1"),
5759+
@r"
5760+
success: true
5761+
exit_code: 0
5762+
----- stdout -----
5763+
All checks passed!
5764+
5765+
----- stderr -----
5766+
warning: The `lint.future-annotations` setting will have no effect because `preview` is disabled
5767+
",
5768+
);
5769+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from collections import Counter
2+
3+
from elsewhere import third_party
4+
5+
from . import first_party
6+
7+
8+
def f(x: first_party.foo): ...
9+
def g(x: third_party.bar): ...
10+
def h(x: Counter): ...
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
def f():
2+
from . import first_party
3+
4+
def f(x: first_party.foo): ...
5+
6+
7+
# Type parameter bounds
8+
def g():
9+
from . import foo
10+
11+
class C[T: foo.Ty]: ...
12+
13+
14+
def h():
15+
from . import foo
16+
17+
def f[T: foo.Ty](x: T): ...
18+
19+
20+
def i():
21+
from . import foo
22+
23+
type Alias[T: foo.Ty] = list[T]
24+
25+
26+
# Type parameter defaults
27+
def j():
28+
from . import foo
29+
30+
class C[T = foo.Ty]: ...
31+
32+
33+
def k():
34+
from . import foo
35+
36+
def f[T = foo.Ty](x: T): ...
37+
38+
39+
def l():
40+
from . import foo
41+
42+
type Alias[T = foo.Ty] = list[T]
43+
44+
45+
# non-generic type alias
46+
def m():
47+
from . import foo
48+
49+
type Alias = foo.Ty
50+
51+
52+
# unions
53+
from typing import Union
54+
55+
56+
def n():
57+
from . import foo
58+
59+
def f(x: Union[foo.Ty, int]): ...
60+
def g(x: foo.Ty | int): ...
61+
62+
63+
# runtime and typing usage
64+
def o():
65+
from . import foo
66+
67+
def f(x: foo.Ty):
68+
return foo.Ty()
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from __future__ import annotations
2+
3+
from . import first_party
4+
5+
6+
def f(x: first_party.foo): ...

crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
7171
flake8_type_checking::helpers::is_valid_runtime_import(
7272
binding,
7373
&checker.semantic,
74-
&checker.settings().flake8_type_checking,
74+
checker.settings(),
7575
)
7676
})
7777
.collect()

crates/ruff_linter/src/checkers/ast/mod.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2770,11 +2770,10 @@ impl<'a> Checker<'a> {
27702770

27712771
self.semantic.restore(snapshot);
27722772

2773-
if self.semantic.in_typing_only_annotation() {
2774-
if self.is_rule_enabled(Rule::QuotedAnnotation) {
2775-
pyupgrade::rules::quoted_annotation(self, annotation, range);
2776-
}
2773+
if self.is_rule_enabled(Rule::QuotedAnnotation) {
2774+
pyupgrade::rules::quoted_annotation(self, annotation, range);
27772775
}
2776+
27782777
if self.source_type.is_stub() {
27792778
if self.is_rule_enabled(Rule::QuotedAnnotationInStub) {
27802779
flake8_pyi::rules::quoted_annotation_in_stub(

crates/ruff_linter/src/importer/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,17 @@ impl<'a> Importer<'a> {
527527
None
528528
}
529529
}
530+
531+
/// Add a `from __future__ import annotations` import.
532+
pub(crate) fn add_future_import(&self) -> Edit {
533+
let import = &NameImport::ImportFrom(MemberNameImport::member(
534+
"__future__".to_string(),
535+
"annotations".to_string(),
536+
));
537+
// Note that `TextSize::default` should ensure that the import is added at the very
538+
// beginning of the file via `Insertion::start_of_file`.
539+
self.add_import(import, TextSize::default())
540+
}
530541
}
531542

532543
/// An edit to the top-level of a module, making it available at runtime.

crates/ruff_linter/src/preview.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,8 @@ pub(crate) const fn is_safe_super_call_with_parameters_fix_enabled(
195195
pub(crate) const fn is_assert_raises_exception_call_enabled(settings: &LinterSettings) -> bool {
196196
settings.preview.is_enabled()
197197
}
198+
199+
// https://github.com/astral-sh/ruff/pull/19100
200+
pub(crate) const fn is_add_future_annotations_imports_enabled(settings: &LinterSettings) -> bool {
201+
settings.preview.is_enabled()
202+
}

crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_required_type_annotation.rs

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ use std::fmt;
22

33
use ruff_macros::{ViolationMetadata, derive_message_formats};
44
use ruff_python_ast::Expr;
5-
use ruff_python_semantic::{MemberNameImport, NameImport};
6-
use ruff_text_size::{Ranged, TextSize};
5+
use ruff_text_size::Ranged;
76

87
use crate::checkers::ast::Checker;
98
use crate::{AlwaysFixableViolation, Fix};
@@ -85,15 +84,7 @@ impl AlwaysFixableViolation for FutureRequiredTypeAnnotation {
8584

8685
/// FA102
8786
pub(crate) fn future_required_type_annotation(checker: &Checker, expr: &Expr, reason: Reason) {
88-
let mut diagnostic =
89-
checker.report_diagnostic(FutureRequiredTypeAnnotation { reason }, expr.range());
90-
let required_import = NameImport::ImportFrom(MemberNameImport::member(
91-
"__future__".to_string(),
92-
"annotations".to_string(),
93-
));
94-
diagnostic.set_fix(Fix::unsafe_edit(
95-
checker
96-
.importer()
97-
.add_import(&required_import, TextSize::default()),
98-
));
87+
checker
88+
.report_diagnostic(FutureRequiredTypeAnnotation { reason }, expr.range())
89+
.set_fix(Fix::unsafe_edit(checker.importer().add_future_import()));
9990
}

crates/ruff_linter/src/rules/flake8_future_annotations/rules/future_rewritable_type_annotation.rs

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
use ruff_diagnostics::Fix;
21
use ruff_python_ast::Expr;
32

43
use ruff_macros::{ViolationMetadata, derive_message_formats};
5-
use ruff_python_semantic::{MemberNameImport, NameImport};
64
use ruff_text_size::Ranged;
75

8-
use crate::AlwaysFixableViolation;
96
use crate::checkers::ast::Checker;
7+
use crate::{AlwaysFixableViolation, Fix};
108

119
/// ## What it does
1210
/// Checks for missing `from __future__ import annotations` imports upon
@@ -95,15 +93,7 @@ pub(crate) fn future_rewritable_type_annotation(checker: &Checker, expr: &Expr)
9593

9694
let Some(name) = name else { return };
9795

98-
let import = &NameImport::ImportFrom(MemberNameImport::member(
99-
"__future__".to_string(),
100-
"annotations".to_string(),
101-
));
10296
checker
10397
.report_diagnostic(FutureRewritableTypeAnnotation { name }, expr.range())
104-
.set_fix(Fix::unsafe_edit(
105-
checker
106-
.importer()
107-
.add_import(import, ruff_text_size::TextSize::default()),
108-
));
98+
.set_fix(Fix::unsafe_edit(checker.importer().add_future_import()));
10999
}

0 commit comments

Comments
 (0)