Skip to content

Commit 7783cea

Browse files
authored
[flake8-future-annotations] Add autofix (FA100) (#18903)
Summary -- This PR resolves the easiest part of #18502 by adding an autofix that just adds `from __future__ import annotations` at the top of the file, in the same way as FA102, which already has an identical unsafe fix. Test Plan -- Existing snapshots, updated to add the fixes.
1 parent c1fed55 commit 7783cea

6 files changed

+81
-17
lines changed

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

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
use ruff_diagnostics::Fix;
12
use ruff_python_ast::Expr;
23

34
use ruff_macros::{ViolationMetadata, derive_message_formats};
5+
use ruff_python_semantic::{MemberNameImport, NameImport};
46
use ruff_text_size::Ranged;
57

6-
use crate::Violation;
8+
use crate::AlwaysFixableViolation;
79
use crate::checkers::ast::Checker;
810

911
/// ## What it does
@@ -61,19 +63,27 @@ use crate::checkers::ast::Checker;
6163
/// def func(obj: dict[str, int | None]) -> None: ...
6264
/// ```
6365
///
66+
/// ## Fix safety
67+
/// This rule's fix is marked as unsafe, as adding `from __future__ import annotations`
68+
/// may change the semantics of the program.
69+
///
6470
/// ## Options
6571
/// - `target-version`
6672
#[derive(ViolationMetadata)]
6773
pub(crate) struct FutureRewritableTypeAnnotation {
6874
name: String,
6975
}
7076

71-
impl Violation for FutureRewritableTypeAnnotation {
77+
impl AlwaysFixableViolation for FutureRewritableTypeAnnotation {
7278
#[derive_message_formats]
7379
fn message(&self) -> String {
7480
let FutureRewritableTypeAnnotation { name } = self;
7581
format!("Add `from __future__ import annotations` to simplify `{name}`")
7682
}
83+
84+
fn fix_title(&self) -> String {
85+
"Add `from __future__ import annotations`".to_string()
86+
}
7787
}
7888

7989
/// FA100
@@ -83,7 +93,17 @@ pub(crate) fn future_rewritable_type_annotation(checker: &Checker, expr: &Expr)
8393
.resolve_qualified_name(expr)
8494
.map(|binding| binding.to_string());
8595

86-
if let Some(name) = name {
87-
checker.report_diagnostic(FutureRewritableTypeAnnotation { name }, expr.range());
88-
}
96+
let Some(name) = name else { return };
97+
98+
let import = &NameImport::ImportFrom(MemberNameImport::member(
99+
"__future__".to_string(),
100+
"annotations".to_string(),
101+
));
102+
checker
103+
.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+
));
89109
}
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,32 @@
11
---
22
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
3-
snapshot_kind: text
43
---
5-
edge_case.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
4+
edge_case.py:5:13: FA100 [*] Add `from __future__ import annotations` to simplify `typing.List`
65
|
76
5 | def main(_: List[int]) -> None:
87
| ^^^^ FA100
98
6 | a_list: t.List[str] = []
109
7 | a_list.append("hello")
1110
|
11+
= help: Add `from __future__ import annotations`
1212

13-
edge_case.py:6:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
13+
Unsafe fix
14+
1 |+from __future__ import annotations
15+
1 2 | from typing import List
16+
2 3 | import typing as t
17+
3 4 |
18+
19+
edge_case.py:6:13: FA100 [*] Add `from __future__ import annotations` to simplify `typing.List`
1420
|
1521
5 | def main(_: List[int]) -> None:
1622
6 | a_list: t.List[str] = []
1723
| ^^^^^^ FA100
1824
7 | a_list.append("hello")
1925
|
26+
= help: Add `from __future__ import annotations`
27+
28+
Unsafe fix
29+
1 |+from __future__ import annotations
30+
1 2 | from typing import List
31+
2 3 | import typing as t
32+
3 4 |
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
---
22
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
3-
snapshot_kind: text
43
---
5-
from_typing_import.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
4+
from_typing_import.py:5:13: FA100 [*] Add `from __future__ import annotations` to simplify `typing.List`
65
|
76
4 | def main() -> None:
87
5 | a_list: List[str] = []
98
| ^^^^ FA100
109
6 | a_list.append("hello")
1110
|
11+
= help: Add `from __future__ import annotations`
12+
13+
Unsafe fix
14+
1 |+from __future__ import annotations
15+
1 2 | from typing import List
16+
2 3 |
17+
3 4 |
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,34 @@
11
---
22
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
3-
snapshot_kind: text
43
---
5-
from_typing_import_many.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
4+
from_typing_import_many.py:5:13: FA100 [*] Add `from __future__ import annotations` to simplify `typing.List`
65
|
76
4 | def main() -> None:
87
5 | a_list: List[Optional[str]] = []
98
| ^^^^ FA100
109
6 | a_list.append("hello")
1110
7 | a_dict = cast(Dict[int | None, Union[int, Set[bool]]], {})
1211
|
12+
= help: Add `from __future__ import annotations`
1313

14-
from_typing_import_many.py:5:18: FA100 Add `from __future__ import annotations` to simplify `typing.Optional`
14+
Unsafe fix
15+
1 |+from __future__ import annotations
16+
1 2 | from typing import Dict, List, Optional, Set, Union, cast
17+
2 3 |
18+
3 4 |
19+
20+
from_typing_import_many.py:5:18: FA100 [*] Add `from __future__ import annotations` to simplify `typing.Optional`
1521
|
1622
4 | def main() -> None:
1723
5 | a_list: List[Optional[str]] = []
1824
| ^^^^^^^^ FA100
1925
6 | a_list.append("hello")
2026
7 | a_dict = cast(Dict[int | None, Union[int, Set[bool]]], {})
2127
|
28+
= help: Add `from __future__ import annotations`
29+
30+
Unsafe fix
31+
1 |+from __future__ import annotations
32+
1 2 | from typing import Dict, List, Optional, Set, Union, cast
33+
2 3 |
34+
3 4 |
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
---
22
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
3-
snapshot_kind: text
43
---
5-
import_typing.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
4+
import_typing.py:5:13: FA100 [*] Add `from __future__ import annotations` to simplify `typing.List`
65
|
76
4 | def main() -> None:
87
5 | a_list: typing.List[str] = []
98
| ^^^^^^^^^^^ FA100
109
6 | a_list.append("hello")
1110
|
11+
= help: Add `from __future__ import annotations`
12+
13+
Unsafe fix
14+
1 |+from __future__ import annotations
15+
1 2 | import typing
16+
2 3 |
17+
3 4 |
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
---
22
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
3-
snapshot_kind: text
43
---
5-
import_typing_as.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
4+
import_typing_as.py:5:13: FA100 [*] Add `from __future__ import annotations` to simplify `typing.List`
65
|
76
4 | def main() -> None:
87
5 | a_list: t.List[str] = []
98
| ^^^^^^ FA100
109
6 | a_list.append("hello")
1110
|
11+
= help: Add `from __future__ import annotations`
12+
13+
Unsafe fix
14+
1 |+from __future__ import annotations
15+
1 2 | import typing as t
16+
2 3 |
17+
3 4 |

0 commit comments

Comments
 (0)