-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement flake8-future-annotations
FA100
#3979
Merged
Merged
Changes from 4 commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
0456611
Implement flake8-future-annotations
TylerYep d5c81de
Remove commented code, remove autofix for now
TylerYep cc9e9ce
Remove .python-version
TylerYep 9acaee8
Merge branch 'main' into pr
charliermarsh 73b4653
Defer context checks if future is enabled
charliermarsh 69abeb0
Move list into typing
charliermarsh 4373751
Remove Deque and DefaultDict
TylerYep 822cdb7
Merge branch 'main' into pr
charliermarsh fd41668
Merge branch 'main' into pr
TylerYep 8bdd0c1
Fix fmt
TylerYep 4569ac6
Revert "Remove Deque and DefaultDict"
TylerYep e6775ea
Fix mkdocs
TylerYep d872a25
Fix mkdocs
TylerYep b82b04f
Merge branch 'main' into pr
TylerYep 92cc016
Rebase onto main
TylerYep 64e2012
Fix lint error
TylerYep b52241f
Merge branch 'main' into pr
charliermarsh af9be69
Fix rebased future_annotations() check
charliermarsh e16416b
Merge branch 'main' into pr
TylerYep 33a73b9
Fix bad rebase
TylerYep d47ef9a
Merge branch 'main' into pr
charliermarsh 41f65c4
Include PEP 604
charliermarsh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
crates/ruff/resources/test/fixtures/flake8_future_annotations/edge_case.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from typing import List | ||
import typing as t | ||
|
||
|
||
def main(_: List[int]) -> None: | ||
a_list: t.List[str] = [] | ||
a_list.append("hello") |
6 changes: 6 additions & 0 deletions
6
crates/ruff/resources/test/fixtures/flake8_future_annotations/from_typing_import.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from typing import List | ||
|
||
|
||
def main() -> None: | ||
a_list: List[str] = [] | ||
a_list.append("hello") |
8 changes: 8 additions & 0 deletions
8
crates/ruff/resources/test/fixtures/flake8_future_annotations/from_typing_import_many.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from typing import Dict, List, Optional, Set, Union, cast | ||
|
||
|
||
def main() -> None: | ||
a_list: List[Optional[str]] = [] | ||
a_list.append("hello") | ||
a_dict = cast(Dict[int | None, Union[int, Set[bool]]], {}) | ||
a_dict[1] = {True, False} |
6 changes: 6 additions & 0 deletions
6
crates/ruff/resources/test/fixtures/flake8_future_annotations/import_typing.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import typing | ||
|
||
|
||
def main() -> None: | ||
a_list: typing.List[str] = [] | ||
a_list.append("hello") |
6 changes: 6 additions & 0 deletions
6
crates/ruff/resources/test/fixtures/flake8_future_annotations/import_typing_as.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import typing as t | ||
|
||
|
||
def main() -> None: | ||
a_list: t.List[str] = [] | ||
a_list.append("hello") |
7 changes: 7 additions & 0 deletions
7
...ruff/resources/test/fixtures/flake8_future_annotations/no_future_import_uses_lowercase.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
def main() -> None: | ||
a_list: list[str] = [] | ||
a_list.append("hello") | ||
|
||
|
||
def hello(y: dict[str, int]) -> None: | ||
del y |
7 changes: 7 additions & 0 deletions
7
crates/ruff/resources/test/fixtures/flake8_future_annotations/no_future_import_uses_union.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
def main() -> None: | ||
a_list: list[str] | None = [] | ||
a_list.append("hello") | ||
|
||
|
||
def hello(y: dict[str, int] | None) -> None: | ||
del y |
8 changes: 8 additions & 0 deletions
8
...ff/resources/test/fixtures/flake8_future_annotations/no_future_import_uses_union_inner.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
def main() -> None: | ||
a_list: list[str | None] = [] | ||
a_list.append("hello") | ||
|
||
|
||
def hello(y: dict[str | None, int]) -> None: | ||
z: tuple[str, str | None, str] = tuple(y) | ||
del z |
3 changes: 3 additions & 0 deletions
3
crates/ruff/resources/test/fixtures/flake8_future_annotations/ok_no_types.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
def main() -> str: | ||
a_str = "hello" | ||
return a_str |
10 changes: 10 additions & 0 deletions
10
crates/ruff/resources/test/fixtures/flake8_future_annotations/ok_non_simplifiable_types.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from typing import NamedTuple | ||
|
||
|
||
class Stuff(NamedTuple): | ||
x: int | ||
|
||
|
||
def main() -> None: | ||
a_list = Stuff(5) | ||
print(a_list) |
6 changes: 6 additions & 0 deletions
6
crates/ruff/resources/test/fixtures/flake8_future_annotations/ok_uses_future.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from __future__ import annotations | ||
|
||
|
||
def main() -> None: | ||
a_list: list[str] = [] | ||
a_list.append("hello") |
8 changes: 8 additions & 0 deletions
8
crates/ruff/resources/test/fixtures/flake8_future_annotations/ok_variable_name.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import typing | ||
|
||
IRRELEVANT = typing.TypeVar | ||
|
||
|
||
def main() -> None: | ||
List: list[str] = [] | ||
List.append("hello") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
//! Rules from [flake8-future-annotations](https://pypi.org/project/flake8-future-annotations/). | ||
pub(crate) mod rules; | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use std::path::Path; | ||
|
||
use anyhow::Result; | ||
use test_case::test_case; | ||
|
||
use crate::registry::Rule; | ||
use crate::test::test_path; | ||
use crate::{assert_messages, settings}; | ||
|
||
#[test_case(Path::new("edge_case.py"); "edge_case")] | ||
#[test_case(Path::new("from_typing_import.py"); "from_typing_import")] | ||
#[test_case(Path::new("from_typing_import_many.py"); "from_typing_import_many")] | ||
#[test_case(Path::new("import_typing.py"); "import_typing")] | ||
#[test_case(Path::new("import_typing_as.py"); "import_typing_as")] | ||
#[test_case(Path::new("no_future_import_uses_lowercase.py"); "no_future_import_uses_lowercase")] | ||
#[test_case(Path::new("no_future_import_uses_union.py"); "no_future_import_uses_union")] | ||
#[test_case(Path::new("no_future_import_uses_union_inner.py"); "no_future_import_uses_union_inner")] | ||
#[test_case(Path::new("ok_no_types.py"); "ok_no_types")] | ||
#[test_case(Path::new("ok_non_simplifiable_types.py"); "ok_non_simplifiable_types")] | ||
#[test_case(Path::new("ok_uses_future.py"); "ok_uses_future")] | ||
#[test_case(Path::new("ok_variable_name.py"); "ok_variable_name")] | ||
fn rules(path: &Path) -> Result<()> { | ||
let snapshot = path.to_string_lossy().into_owned(); | ||
let diagnostics = test_path( | ||
Path::new("flake8_future_annotations").join(path).as_path(), | ||
&settings::Settings::for_rules(vec![Rule::MissingFutureAnnotationsWithImports]), | ||
)?; | ||
assert_messages!(snapshot, diagnostics); | ||
Ok(()) | ||
} | ||
} |
113 changes: 113 additions & 0 deletions
113
crates/ruff/src/rules/flake8_future_annotations/rules.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
use crate::checkers::ast::Checker; | ||
use itertools::Itertools; | ||
use ruff_diagnostics::{Diagnostic, Violation}; | ||
use ruff_macros::{derive_message_formats, violation}; | ||
use ruff_python_ast::types::Range; | ||
use rustpython_parser::ast::{AliasData, Expr, Located, Stmt}; | ||
|
||
/// ## What it does | ||
/// Checks for missing `from __future__ import annotations` import if a type used in the | ||
/// module can be rewritten using PEP 563. | ||
/// | ||
/// ## Why is this bad? | ||
/// | ||
/// Pairs well with pyupgrade with the --py37-plus flag or higher, since pyupgrade only | ||
/// replaces type annotations with the PEP 563 rules if `from __future__ import annotations` | ||
/// is present. | ||
/// | ||
/// ## Example | ||
/// ```python | ||
/// import typing as t | ||
/// from typing import List | ||
/// | ||
/// def function(a_dict: t.Dict[str, t.Optional[int]]) -> None: | ||
/// a_list: List[str] = [] | ||
/// a_list.append("hello") | ||
/// ``` | ||
/// | ||
/// To fix the lint error: | ||
/// ```python | ||
/// from __future__ import annotations | ||
/// | ||
/// import typing as t | ||
/// from typing import List | ||
/// | ||
/// def function(a_dict: t.Dict[str, t.Optional[int]]) -> None: | ||
/// a_list: List[str] = [] | ||
/// a_list.append("hello") | ||
/// ``` | ||
/// | ||
/// After running additional pyupgrade autofixes: | ||
/// ```python | ||
/// from __future__ import annotations | ||
/// | ||
/// def function(a_dict: dict[str, int | None]) -> None: | ||
/// a_list: list[str] = [] | ||
/// a_list.append("hello") | ||
/// ``` | ||
#[violation] | ||
pub struct MissingFutureAnnotationsWithImports { | ||
pub names: Vec<String>, | ||
} | ||
|
||
impl Violation for MissingFutureAnnotationsWithImports { | ||
#[derive_message_formats] | ||
fn message(&self) -> String { | ||
let MissingFutureAnnotationsWithImports { names } = self; | ||
let names = names.iter().map(|name| format!("`{name}`")).join(", "); | ||
format!("Missing from __future__ import annotations but imports: {names}") | ||
} | ||
} | ||
|
||
// PEP_593_SUBSCRIPTS | ||
pub const FUTURE_ANNOTATIONS_REWRITE_ELIGIBLE: &[&[&str]] = &[ | ||
&["typing", "DefaultDict"], | ||
&["typing", "Deque"], | ||
&["typing", "Dict"], | ||
&["typing", "FrozenSet"], | ||
&["typing", "List"], | ||
&["typing", "Optional"], | ||
&["typing", "Set"], | ||
&["typing", "Tuple"], | ||
&["typing", "Type"], | ||
&["typing", "Union"], | ||
&["typing_extensions", "Type"], | ||
]; | ||
|
||
/// FA100 | ||
pub fn check_missing_future_annotations_from_typing_import( | ||
checker: &mut Checker, | ||
stmt: &Stmt, | ||
module: &str, | ||
names: &[Located<AliasData>], | ||
) { | ||
let result: Vec<String> = names | ||
.iter() | ||
.map(|name| name.node.name.as_str()) | ||
.filter(|alias| FUTURE_ANNOTATIONS_REWRITE_ELIGIBLE.contains(&[module, alias].as_slice())) | ||
.map(std::string::ToString::to_string) | ||
.sorted() | ||
.collect(); | ||
|
||
if !checker.ctx.annotations_future_enabled && !result.is_empty() { | ||
checker.diagnostics.push(Diagnostic::new( | ||
MissingFutureAnnotationsWithImports { names: result }, | ||
Range::from(stmt), | ||
)); | ||
} | ||
} | ||
|
||
pub fn check_missing_future_annotations_import(checker: &mut Checker, expr: &Expr) { | ||
if let Some(binding) = checker.ctx.resolve_call_path(expr) { | ||
if !checker.ctx.annotations_future_enabled | ||
&& FUTURE_ANNOTATIONS_REWRITE_ELIGIBLE.contains(&binding.as_slice()) | ||
{ | ||
checker.diagnostics.push(Diagnostic::new( | ||
MissingFutureAnnotationsWithImports { | ||
names: vec![binding.iter().join(".")], | ||
}, | ||
Range::from(expr), | ||
)); | ||
} | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
...re_annotations/snapshots/ruff__rules__flake8_future_annotations__tests__edge_case.py.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
--- | ||
source: crates/ruff/src/rules/flake8_future_annotations/mod.rs | ||
--- | ||
edge_case.py:1:1: FA100 Missing from __future__ import annotations but imports: `List` | ||
| | ||
1 | from typing import List | ||
| ^^^^^^^^^^^^^^^^^^^^^^^ FA100 | ||
2 | import typing as t | ||
| | ||
|
||
edge_case.py:6:13: FA100 Missing from __future__ import annotations but imports: `typing.List` | ||
| | ||
6 | def main(_: List[int]) -> None: | ||
7 | a_list: t.List[str] = [] | ||
| ^^^^^^ FA100 | ||
8 | a_list.append("hello") | ||
| | ||
|
||
|
10 changes: 10 additions & 0 deletions
10
...tions/snapshots/ruff__rules__flake8_future_annotations__tests__from_typing_import.py.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
--- | ||
source: crates/ruff/src/rules/flake8_future_annotations/mod.rs | ||
--- | ||
from_typing_import.py:1:1: FA100 Missing from __future__ import annotations but imports: `List` | ||
| | ||
1 | from typing import List | ||
| ^^^^^^^^^^^^^^^^^^^^^^^ FA100 | ||
| | ||
|
||
|
10 changes: 10 additions & 0 deletions
10
.../snapshots/ruff__rules__flake8_future_annotations__tests__from_typing_import_many.py.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
--- | ||
source: crates/ruff/src/rules/flake8_future_annotations/mod.rs | ||
--- | ||
from_typing_import_many.py:1:1: FA100 Missing from __future__ import annotations but imports: `Dict`, `List`, `Optional`, `Set`, `Union` | ||
| | ||
1 | from typing import Dict, List, Optional, Set, Union, cast | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FA100 | ||
| | ||
|
||
|
12 changes: 12 additions & 0 deletions
12
...nnotations/snapshots/ruff__rules__flake8_future_annotations__tests__import_typing.py.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
--- | ||
source: crates/ruff/src/rules/flake8_future_annotations/mod.rs | ||
--- | ||
import_typing.py:5:13: FA100 Missing from __future__ import annotations but imports: `typing.List` | ||
| | ||
5 | def main() -> None: | ||
6 | a_list: typing.List[str] = [] | ||
| ^^^^^^^^^^^ FA100 | ||
7 | a_list.append("hello") | ||
| | ||
|
||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should only be activated for minimum-version < Python 3.9, right? Since PEP 585 was part of Python 3.9 (so future annotations aren't required to use the standard-library variants in 3.9 and later).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In practice, this works on Python 3.7+.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if your minimum-supported Python version is Python 3.9, you don't need
__future__
annotations to use the standard-library generics. So these errors would already be detected and fixed by the existingpyupgrade
rules, right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, you're asking about the upper version.
Unions like
str | None
didn't start working until Python 3.10, which are also covered by this plugin.After 3.10, the future annotations import is not as useful anymore.