mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 18:58:26 +00:00
[pyflakes
] Visit forward annotations in TypeAliasType
as types (F401
) (#15829)
## Summary Fixes https://github.com/astral-sh/ruff/issues/15812 by visiting the second argument as a type definition. ## Test Plan New F401 tests based on the report. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
4f2aea8d50
commit
fe516e24f5
5 changed files with 136 additions and 3 deletions
67
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_34.py
vendored
Normal file
67
crates/ruff_linter/resources/test/fixtures/pyflakes/F401_34.py
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
"""Regression tests for https://github.com/astral-sh/ruff/issues/15812"""
|
||||
|
||||
|
||||
def f():
|
||||
from typing import Union
|
||||
|
||||
from typing_extensions import TypeAliasType
|
||||
|
||||
Json = TypeAliasType(
|
||||
"Json",
|
||||
"Union[dict[str, Json], list[Json], str, int, float, bool, None]",
|
||||
)
|
||||
|
||||
|
||||
def f():
|
||||
from typing import Union
|
||||
|
||||
from typing_extensions import TypeAliasType, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
V = TypeVar("V")
|
||||
Json = TypeAliasType(
|
||||
"Json",
|
||||
"Union[dict[str, Json], list[Json], str, int, float, bool, T, V, None]",
|
||||
type_params=(T, V),
|
||||
)
|
||||
|
||||
|
||||
def f():
|
||||
from typing import Union
|
||||
|
||||
from typing_extensions import TypeAliasType
|
||||
|
||||
Json = TypeAliasType(
|
||||
value="Union[dict[str, Json], list[Json], str, int, float, bool, None]",
|
||||
name="Json",
|
||||
)
|
||||
|
||||
|
||||
# strictly speaking it's a false positive to emit F401 for both of these, but
|
||||
# we can't really be expected to understand that the strings here are type
|
||||
# expressions (and type checkers probably wouldn't understand them as type
|
||||
# expressions either!)
|
||||
def f():
|
||||
from typing import Union
|
||||
|
||||
from typing_extensions import TypeAliasType
|
||||
|
||||
args = [
|
||||
"Json",
|
||||
"Union[dict[str, Json], list[Json], str, int, float, bool, None]",
|
||||
]
|
||||
|
||||
Json = TypeAliasType(*args)
|
||||
|
||||
|
||||
def f():
|
||||
from typing import Union
|
||||
|
||||
from typing_extensions import TypeAliasType
|
||||
|
||||
kwargs = {
|
||||
"name": "Json",
|
||||
"value": "Union[dict[str, Json], list[Json], str, int, float, bool, None]",
|
||||
}
|
||||
|
||||
Json = TypeAliasType(**kwargs)
|
|
@ -41,9 +41,9 @@ use ruff_python_ast::name::QualifiedName;
|
|||
use ruff_python_ast::str::Quote;
|
||||
use ruff_python_ast::visitor::{walk_except_handler, walk_pattern, Visitor};
|
||||
use ruff_python_ast::{
|
||||
self as ast, AnyParameterRef, Comprehension, ElifElseClause, ExceptHandler, Expr, ExprContext,
|
||||
FStringElement, Keyword, MatchCase, ModModule, Parameter, Parameters, Pattern, Stmt, Suite,
|
||||
UnaryOp,
|
||||
self as ast, AnyParameterRef, ArgOrKeyword, Comprehension, ElifElseClause, ExceptHandler, Expr,
|
||||
ExprContext, FStringElement, Keyword, MatchCase, ModModule, Parameter, Parameters, Pattern,
|
||||
Stmt, Suite, UnaryOp,
|
||||
};
|
||||
use ruff_python_ast::{helpers, str, visitor, PySourceType};
|
||||
use ruff_python_codegen::{Generator, Stylist};
|
||||
|
@ -1269,6 +1269,11 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
|||
.match_typing_qualified_name(&qualified_name, "TypeVar")
|
||||
{
|
||||
Some(typing::Callable::TypeVar)
|
||||
} else if self
|
||||
.semantic
|
||||
.match_typing_qualified_name(&qualified_name, "TypeAliasType")
|
||||
{
|
||||
Some(typing::Callable::TypeAliasType)
|
||||
} else if self
|
||||
.semantic
|
||||
.match_typing_qualified_name(&qualified_name, "NamedTuple")
|
||||
|
@ -1354,6 +1359,24 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
Some(typing::Callable::TypeAliasType) => {
|
||||
// Ex) TypeAliasType("Json", "Union[dict[str, Json]]", type_params=())
|
||||
for (i, arg) in arguments.arguments_source_order().enumerate() {
|
||||
match (i, arg) {
|
||||
(1, ArgOrKeyword::Arg(arg)) => self.visit_type_definition(arg),
|
||||
(_, ArgOrKeyword::Arg(arg)) => self.visit_non_type_definition(arg),
|
||||
(_, ArgOrKeyword::Keyword(Keyword { arg, value, .. })) => {
|
||||
if let Some(id) = arg {
|
||||
if matches!(&**id, "value" | "type_params") {
|
||||
self.visit_type_definition(value);
|
||||
} else {
|
||||
self.visit_non_type_definition(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(typing::Callable::NamedTuple) => {
|
||||
// Ex) NamedTuple("a", [("a", int)])
|
||||
let mut args = arguments.args.iter();
|
||||
|
|
|
@ -56,6 +56,7 @@ mod tests {
|
|||
#[test_case(Rule::UnusedImport, Path::new("F401_22.py"))]
|
||||
#[test_case(Rule::UnusedImport, Path::new("F401_23.py"))]
|
||||
#[test_case(Rule::UnusedImport, Path::new("F401_32.py"))]
|
||||
#[test_case(Rule::UnusedImport, Path::new("F401_34.py"))]
|
||||
#[test_case(Rule::ImportShadowedByLoopVar, Path::new("F402.py"))]
|
||||
#[test_case(Rule::ImportShadowedByLoopVar, Path::new("F402.ipynb"))]
|
||||
#[test_case(Rule::UndefinedLocalWithImportStar, Path::new("F403.py"))]
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F401_34.py:45:24: F401 [*] `typing.Union` imported but unused
|
||||
|
|
||||
43 | # expressions either!)
|
||||
44 | def f():
|
||||
45 | from typing import Union
|
||||
| ^^^^^ F401
|
||||
46 |
|
||||
47 | from typing_extensions import TypeAliasType
|
||||
|
|
||||
= help: Remove unused import: `typing.Union`
|
||||
|
||||
ℹ Safe fix
|
||||
42 42 | # expressions (and type checkers probably wouldn't understand them as type
|
||||
43 43 | # expressions either!)
|
||||
44 44 | def f():
|
||||
45 |- from typing import Union
|
||||
46 45 |
|
||||
47 46 | from typing_extensions import TypeAliasType
|
||||
48 47 |
|
||||
|
||||
F401_34.py:58:24: F401 [*] `typing.Union` imported but unused
|
||||
|
|
||||
57 | def f():
|
||||
58 | from typing import Union
|
||||
| ^^^^^ F401
|
||||
59 |
|
||||
60 | from typing_extensions import TypeAliasType
|
||||
|
|
||||
= help: Remove unused import: `typing.Union`
|
||||
|
||||
ℹ Safe fix
|
||||
55 55 |
|
||||
56 56 |
|
||||
57 57 | def f():
|
||||
58 |- from typing import Union
|
||||
59 58 |
|
||||
60 59 | from typing_extensions import TypeAliasType
|
||||
61 60 |
|
|
@ -28,6 +28,7 @@ pub enum Callable {
|
|||
NamedTuple,
|
||||
TypedDict,
|
||||
MypyExtension,
|
||||
TypeAliasType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue