[flake8-future-annotations] Add autofix (FA100) (#18903)

Summary
--

This PR resolves the easiest part of
https://github.com/astral-sh/ruff/issues/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.
This commit is contained in:
Brent Westbrook 2025-06-25 08:37:18 -04:00 committed by GitHub
parent c1fed55d51
commit 7783cea14f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 81 additions and 17 deletions

View file

@ -1,9 +1,11 @@
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::Violation;
use crate::AlwaysFixableViolation;
use crate::checkers::ast::Checker;
/// ## What it does
@ -61,6 +63,10 @@ use crate::checkers::ast::Checker;
/// def func(obj: dict[str, int | None]) -> None: ...
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as adding `from __future__ import annotations`
/// may change the semantics of the program.
///
/// ## Options
/// - `target-version`
#[derive(ViolationMetadata)]
@ -68,12 +74,16 @@ pub(crate) struct FutureRewritableTypeAnnotation {
name: String,
}
impl Violation for FutureRewritableTypeAnnotation {
impl AlwaysFixableViolation for FutureRewritableTypeAnnotation {
#[derive_message_formats]
fn message(&self) -> String {
let FutureRewritableTypeAnnotation { name } = self;
format!("Add `from __future__ import annotations` to simplify `{name}`")
}
fn fix_title(&self) -> String {
"Add `from __future__ import annotations`".to_string()
}
}
/// FA100
@ -83,7 +93,17 @@ pub(crate) fn future_rewritable_type_annotation(checker: &Checker, expr: &Expr)
.resolve_qualified_name(expr)
.map(|binding| binding.to_string());
if let Some(name) = name {
checker.report_diagnostic(FutureRewritableTypeAnnotation { name }, expr.range());
}
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()),
));
}

View file

@ -1,19 +1,32 @@
---
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
snapshot_kind: text
---
edge_case.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
edge_case.py:5:13: FA100 [*] Add `from __future__ import annotations` to simplify `typing.List`
|
5 | def main(_: List[int]) -> None:
| ^^^^ FA100
6 | a_list: t.List[str] = []
7 | a_list.append("hello")
|
= help: Add `from __future__ import annotations`
edge_case.py:6:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
Unsafe fix
1 |+from __future__ import annotations
1 2 | from typing import List
2 3 | import typing as t
3 4 |
edge_case.py:6:13: FA100 [*] Add `from __future__ import annotations` to simplify `typing.List`
|
5 | def main(_: List[int]) -> None:
6 | a_list: t.List[str] = []
| ^^^^^^ FA100
7 | a_list.append("hello")
|
= help: Add `from __future__ import annotations`
Unsafe fix
1 |+from __future__ import annotations
1 2 | from typing import List
2 3 | import typing as t
3 4 |

View file

@ -1,11 +1,17 @@
---
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
snapshot_kind: text
---
from_typing_import.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
from_typing_import.py:5:13: FA100 [*] Add `from __future__ import annotations` to simplify `typing.List`
|
4 | def main() -> None:
5 | a_list: List[str] = []
| ^^^^ FA100
6 | a_list.append("hello")
|
= help: Add `from __future__ import annotations`
Unsafe fix
1 |+from __future__ import annotations
1 2 | from typing import List
2 3 |
3 4 |

View file

@ -1,8 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
snapshot_kind: text
---
from_typing_import_many.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
from_typing_import_many.py:5:13: FA100 [*] Add `from __future__ import annotations` to simplify `typing.List`
|
4 | def main() -> None:
5 | a_list: List[Optional[str]] = []
@ -10,8 +9,15 @@ from_typing_import_many.py:5:13: FA100 Add `from __future__ import annotations`
6 | a_list.append("hello")
7 | a_dict = cast(Dict[int | None, Union[int, Set[bool]]], {})
|
= help: Add `from __future__ import annotations`
from_typing_import_many.py:5:18: FA100 Add `from __future__ import annotations` to simplify `typing.Optional`
Unsafe fix
1 |+from __future__ import annotations
1 2 | from typing import Dict, List, Optional, Set, Union, cast
2 3 |
3 4 |
from_typing_import_many.py:5:18: FA100 [*] Add `from __future__ import annotations` to simplify `typing.Optional`
|
4 | def main() -> None:
5 | a_list: List[Optional[str]] = []
@ -19,3 +25,10 @@ from_typing_import_many.py:5:18: FA100 Add `from __future__ import annotations`
6 | a_list.append("hello")
7 | a_dict = cast(Dict[int | None, Union[int, Set[bool]]], {})
|
= help: Add `from __future__ import annotations`
Unsafe fix
1 |+from __future__ import annotations
1 2 | from typing import Dict, List, Optional, Set, Union, cast
2 3 |
3 4 |

View file

@ -1,11 +1,17 @@
---
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
snapshot_kind: text
---
import_typing.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
import_typing.py:5:13: FA100 [*] Add `from __future__ import annotations` to simplify `typing.List`
|
4 | def main() -> None:
5 | a_list: typing.List[str] = []
| ^^^^^^^^^^^ FA100
6 | a_list.append("hello")
|
= help: Add `from __future__ import annotations`
Unsafe fix
1 |+from __future__ import annotations
1 2 | import typing
2 3 |
3 4 |

View file

@ -1,11 +1,17 @@
---
source: crates/ruff_linter/src/rules/flake8_future_annotations/mod.rs
snapshot_kind: text
---
import_typing_as.py:5:13: FA100 Add `from __future__ import annotations` to simplify `typing.List`
import_typing_as.py:5:13: FA100 [*] Add `from __future__ import annotations` to simplify `typing.List`
|
4 | def main() -> None:
5 | a_list: t.List[str] = []
| ^^^^^^ FA100
6 | a_list.append("hello")
|
= help: Add `from __future__ import annotations`
Unsafe fix
1 |+from __future__ import annotations
1 2 | import typing as t
2 3 |
3 4 |