Add quoted annotation rules

This commit is contained in:
Charlie Marsh 2023-07-14 15:10:03 -04:00
parent 402b3c7f04
commit 603d3e1984
11 changed files with 285 additions and 570 deletions

View File

@ -0,0 +1,21 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import module
from module import Class
def f(var: Class) -> Class:
x: Class
def f(var: module.Class) -> module.Class:
x: module.Class
def f():
print(Class)
def f():
print(module.Class)

View File

@ -0,0 +1,6 @@
from typing import TYPE_CHECKING
Class = ...
if TYPE_CHECKING:
from module import Class

View File

@ -4329,6 +4329,11 @@ impl<'a> Checker<'a> {
ResolvedRead::Resolved(_) | ResolvedRead::ImplicitGlobal => {
// Nothing to do.
}
ResolvedRead::TypingOnly(binding_id) => {
if self.enabled(Rule::UnquotedAnnotation) {
flake8_type_checking::rules::unquoted_annotation(self, binding_id, expr);
}
}
ResolvedRead::WildcardImport => {
// F405
if self.enabled(Rule::UndefinedLocalWithImportStarUsage) {

View File

@ -707,6 +707,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8TypeChecking, "003") => (RuleGroup::Unspecified, rules::flake8_type_checking::rules::TypingOnlyStandardLibraryImport),
(Flake8TypeChecking, "004") => (RuleGroup::Unspecified, rules::flake8_type_checking::rules::RuntimeImportInTypeCheckingBlock),
(Flake8TypeChecking, "005") => (RuleGroup::Unspecified, rules::flake8_type_checking::rules::EmptyTypeCheckingBlock),
(Flake8TypeChecking, "200") => (RuleGroup::Unspecified, rules::flake8_type_checking::rules::UnquotedAnnotation),
// tryceratops
(Tryceratops, "002") => (RuleGroup::Unspecified, rules::tryceratops::rules::RaiseVanillaClass),

View File

@ -15,10 +15,13 @@ mod tests {
use crate::test::{test_path, test_snippet};
use crate::{assert_messages, settings};
#[test_case(Rule::TypingOnlyFirstPartyImport, Path::new("TCH001.py"))]
#[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("TCH002.py"))]
#[test_case(Rule::TypingOnlyStandardLibraryImport, Path::new("TCH003.py"))]
#[test_case(Rule::EmptyTypeCheckingBlock, Path::new("TCH005.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_1.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_10.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_11.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_12.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_13.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_14.pyi"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_2.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_3.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_4.py"))]
@ -27,13 +30,12 @@ mod tests {
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_7.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_8.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_9.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_10.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_11.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_12.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_13.py"))]
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_14.pyi"))]
#[test_case(Rule::EmptyTypeCheckingBlock, Path::new("TCH005.py"))]
#[test_case(Rule::TypingOnlyFirstPartyImport, Path::new("TCH001.py"))]
#[test_case(Rule::TypingOnlyStandardLibraryImport, Path::new("TCH003.py"))]
#[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("TCH002.py"))]
#[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("strict.py"))]
#[test_case(Rule::UnquotedAnnotation, Path::new("TCH200_0.py"))]
#[test_case(Rule::UnquotedAnnotation, Path::new("TCH200_1.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy());
let diagnostics = test_path(

View File

@ -1,7 +1,9 @@
pub(crate) use empty_type_checking_block::*;
pub(crate) use quoted_annotation::*;
pub(crate) use runtime_import_in_type_checking_block::*;
pub(crate) use typing_only_runtime_import::*;
mod empty_type_checking_block;
mod quoted_annotation;
mod runtime_import_in_type_checking_block;
mod typing_only_runtime_import;

View File

@ -0,0 +1,100 @@
use rustpython_parser::ast::{Expr, Ranged};
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::BindingId;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
/// ## What it does
/// Checks for the presence of unnecessary quotes in type annotations.
///
/// ## Why is this bad?
/// In Python, type annotations can be quoted to avoid forward references.
/// However, if `from __future__ import annotations` is present, Python
/// will always evaluate type annotations in a deferred manner, making
/// the quotes unnecessary.
///
/// ## Example
/// ```python
/// from __future__ import annotations
///
///
/// def foo(bar: "Bar") -> "Bar":
/// ...
/// ```
///
/// Use instead:
/// ```python
/// from __future__ import annotations
///
///
/// def foo(bar: Bar) -> Bar:
/// ...
/// ```
///
/// ## References
/// - [PEP 563](https://peps.python.org/pep-0563/)
/// - [Python documentation: `__future__`](https://docs.python.org/3/library/__future__.html#module-__future__)
#[violation]
pub struct UnquotedAnnotation {
name: String,
}
impl Violation for UnquotedAnnotation {
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
let UnquotedAnnotation { name } = self;
format!("Typing-only variable referenced in runtime annotation: `{name}`")
}
fn autofix_title(&self) -> Option<String> {
Some("Add quotes".to_string())
}
}
/// TCH200
pub(crate) fn unquoted_annotation(checker: &mut Checker, binding_id: BindingId, expr: &Expr) {
// If we're already in a quoted annotation, skip.
if checker.semantic().in_deferred_type_definition() {
return;
}
// If we're in a typing-only context, skip.
if checker.semantic().execution_context().is_typing() {
return;
}
// If the reference resolved to a typing-only import, flag.
if checker.semantic().bindings[binding_id].context.is_typing() {
// Expand any attribute chains (e.g., flag `typing.List` in `typing.List[int]`).
let mut expr = expr;
for parent in checker.semantic().expr_ancestors() {
if parent.is_attribute_expr() {
expr = parent;
} else {
break;
}
}
let mut diagnostic = Diagnostic::new(
UnquotedAnnotation {
name: checker.locator.slice(expr.range()).to_string(),
},
expr.range(),
);
if checker.patch(diagnostic.kind.rule()) {
// We can only _fix_ this if we're in a type annotation.
if checker.semantic().in_runtime_annotation() {
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
format!("\"{}\"", checker.locator.slice(expr.range()).to_string()),
expr.range(),
)));
}
}
checker.diagnostics.push(diagnostic);
}
}

View File

@ -0,0 +1,92 @@
---
source: crates/ruff/src/rules/flake8_type_checking/mod.rs
---
TCH200_0.py:8:12: TCH200 [*] Typing-only variable referenced in runtime annotation: `Class`
|
8 | def f(var: Class) -> Class:
| ^^^^^ TCH200
9 | x: Class
|
= help: Add quotes
Fix
5 5 | from module import Class
6 6 |
7 7 |
8 |-def f(var: Class) -> Class:
8 |+def f(var: "Class") -> Class:
9 9 | x: Class
10 10 |
11 11 |
TCH200_0.py:8:22: TCH200 [*] Typing-only variable referenced in runtime annotation: `Class`
|
8 | def f(var: Class) -> Class:
| ^^^^^ TCH200
9 | x: Class
|
= help: Add quotes
Fix
5 5 | from module import Class
6 6 |
7 7 |
8 |-def f(var: Class) -> Class:
8 |+def f(var: Class) -> "Class":
9 9 | x: Class
10 10 |
11 11 |
TCH200_0.py:12:12: TCH200 [*] Typing-only variable referenced in runtime annotation: `module.Class`
|
12 | def f(var: module.Class) -> module.Class:
| ^^^^^^^^^^^^ TCH200
13 | x: module.Class
|
= help: Add quotes
Fix
9 9 | x: Class
10 10 |
11 11 |
12 |-def f(var: module.Class) -> module.Class:
12 |+def f(var: "module.Class") -> module.Class:
13 13 | x: module.Class
14 14 |
15 15 |
TCH200_0.py:12:29: TCH200 [*] Typing-only variable referenced in runtime annotation: `module.Class`
|
12 | def f(var: module.Class) -> module.Class:
| ^^^^^^^^^^^^ TCH200
13 | x: module.Class
|
= help: Add quotes
Fix
9 9 | x: Class
10 10 |
11 11 |
12 |-def f(var: module.Class) -> module.Class:
12 |+def f(var: module.Class) -> "module.Class":
13 13 | x: module.Class
14 14 |
15 15 |
TCH200_0.py:17:11: TCH200 Typing-only variable referenced in runtime annotation: `Class`
|
16 | def f():
17 | print(Class)
| ^^^^^ TCH200
|
= help: Add quotes
TCH200_0.py:21:11: TCH200 Typing-only variable referenced in runtime annotation: `module.Class`
|
20 | def f():
21 | print(module.Class)
| ^^^^^^^^^^^^ TCH200
|
= help: Add quotes

View File

@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/flake8_type_checking/mod.rs
---

View File

@ -1,559 +0,0 @@
---
source: crates/ruff/src/rules/pyupgrade/mod.rs
---
UP037.py:18:14: UP037 [*] Remove quotes from type annotation
|
18 | def foo(var: "MyClass") -> "MyClass":
| ^^^^^^^^^ UP037
19 | x: "MyClass"
|
= help: Remove quotes
Fix
15 15 | from mypy_extensions import Arg, DefaultArg, DefaultNamedArg, NamedArg, VarArg
16 16 |
17 17 |
18 |-def foo(var: "MyClass") -> "MyClass":
18 |+def foo(var: MyClass) -> "MyClass":
19 19 | x: "MyClass"
20 20 |
21 21 |
UP037.py:18:28: UP037 [*] Remove quotes from type annotation
|
18 | def foo(var: "MyClass") -> "MyClass":
| ^^^^^^^^^ UP037
19 | x: "MyClass"
|
= help: Remove quotes
Fix
15 15 | from mypy_extensions import Arg, DefaultArg, DefaultNamedArg, NamedArg, VarArg
16 16 |
17 17 |
18 |-def foo(var: "MyClass") -> "MyClass":
18 |+def foo(var: "MyClass") -> MyClass:
19 19 | x: "MyClass"
20 20 |
21 21 |
UP037.py:19:8: UP037 [*] Remove quotes from type annotation
|
18 | def foo(var: "MyClass") -> "MyClass":
19 | x: "MyClass"
| ^^^^^^^^^ UP037
|
= help: Remove quotes
Fix
16 16 |
17 17 |
18 18 | def foo(var: "MyClass") -> "MyClass":
19 |- x: "MyClass"
19 |+ x: MyClass
20 20 |
21 21 |
22 22 | def foo(*, inplace: "bool"):
UP037.py:22:21: UP037 [*] Remove quotes from type annotation
|
22 | def foo(*, inplace: "bool"):
| ^^^^^^ UP037
23 | pass
|
= help: Remove quotes
Fix
19 19 | x: "MyClass"
20 20 |
21 21 |
22 |-def foo(*, inplace: "bool"):
22 |+def foo(*, inplace: bool):
23 23 | pass
24 24 |
25 25 |
UP037.py:26:16: UP037 [*] Remove quotes from type annotation
|
26 | def foo(*args: "str", **kwargs: "int"):
| ^^^^^ UP037
27 | pass
|
= help: Remove quotes
Fix
23 23 | pass
24 24 |
25 25 |
26 |-def foo(*args: "str", **kwargs: "int"):
26 |+def foo(*args: str, **kwargs: "int"):
27 27 | pass
28 28 |
29 29 |
UP037.py:26:33: UP037 [*] Remove quotes from type annotation
|
26 | def foo(*args: "str", **kwargs: "int"):
| ^^^^^ UP037
27 | pass
|
= help: Remove quotes
Fix
23 23 | pass
24 24 |
25 25 |
26 |-def foo(*args: "str", **kwargs: "int"):
26 |+def foo(*args: "str", **kwargs: int):
27 27 | pass
28 28 |
29 29 |
UP037.py:30:10: UP037 [*] Remove quotes from type annotation
|
30 | x: Tuple["MyClass"]
| ^^^^^^^^^ UP037
31 |
32 | x: Callable[["MyClass"], None]
|
= help: Remove quotes
Fix
27 27 | pass
28 28 |
29 29 |
30 |-x: Tuple["MyClass"]
30 |+x: Tuple[MyClass]
31 31 |
32 32 | x: Callable[["MyClass"], None]
33 33 |
UP037.py:32:14: UP037 [*] Remove quotes from type annotation
|
30 | x: Tuple["MyClass"]
31 |
32 | x: Callable[["MyClass"], None]
| ^^^^^^^^^ UP037
|
= help: Remove quotes
Fix
29 29 |
30 30 | x: Tuple["MyClass"]
31 31 |
32 |-x: Callable[["MyClass"], None]
32 |+x: Callable[[MyClass], None]
33 33 |
34 34 |
35 35 | class Foo(NamedTuple):
UP037.py:36:8: UP037 [*] Remove quotes from type annotation
|
35 | class Foo(NamedTuple):
36 | x: "MyClass"
| ^^^^^^^^^ UP037
|
= help: Remove quotes
Fix
33 33 |
34 34 |
35 35 | class Foo(NamedTuple):
36 |- x: "MyClass"
36 |+ x: MyClass
37 37 |
38 38 |
39 39 | class D(TypedDict):
UP037.py:40:27: UP037 [*] Remove quotes from type annotation
|
39 | class D(TypedDict):
40 | E: TypedDict("E", foo="int", total=False)
| ^^^^^ UP037
|
= help: Remove quotes
Fix
37 37 |
38 38 |
39 39 | class D(TypedDict):
40 |- E: TypedDict("E", foo="int", total=False)
40 |+ E: TypedDict("E", foo=int, total=False)
41 41 |
42 42 |
43 43 | class D(TypedDict):
UP037.py:44:31: UP037 [*] Remove quotes from type annotation
|
43 | class D(TypedDict):
44 | E: TypedDict("E", {"foo": "int"})
| ^^^^^ UP037
|
= help: Remove quotes
Fix
41 41 |
42 42 |
43 43 | class D(TypedDict):
44 |- E: TypedDict("E", {"foo": "int"})
44 |+ E: TypedDict("E", {"foo": int})
45 45 |
46 46 |
47 47 | x: Annotated["str", "metadata"]
UP037.py:47:14: UP037 [*] Remove quotes from type annotation
|
47 | x: Annotated["str", "metadata"]
| ^^^^^ UP037
48 |
49 | x: Arg("str", "name")
|
= help: Remove quotes
Fix
44 44 | E: TypedDict("E", {"foo": "int"})
45 45 |
46 46 |
47 |-x: Annotated["str", "metadata"]
47 |+x: Annotated[str, "metadata"]
48 48 |
49 49 | x: Arg("str", "name")
50 50 |
UP037.py:49:8: UP037 [*] Remove quotes from type annotation
|
47 | x: Annotated["str", "metadata"]
48 |
49 | x: Arg("str", "name")
| ^^^^^ UP037
50 |
51 | x: DefaultArg("str", "name")
|
= help: Remove quotes
Fix
46 46 |
47 47 | x: Annotated["str", "metadata"]
48 48 |
49 |-x: Arg("str", "name")
49 |+x: Arg(str, "name")
50 50 |
51 51 | x: DefaultArg("str", "name")
52 52 |
UP037.py:51:15: UP037 [*] Remove quotes from type annotation
|
49 | x: Arg("str", "name")
50 |
51 | x: DefaultArg("str", "name")
| ^^^^^ UP037
52 |
53 | x: NamedArg("str", "name")
|
= help: Remove quotes
Fix
48 48 |
49 49 | x: Arg("str", "name")
50 50 |
51 |-x: DefaultArg("str", "name")
51 |+x: DefaultArg(str, "name")
52 52 |
53 53 | x: NamedArg("str", "name")
54 54 |
UP037.py:53:13: UP037 [*] Remove quotes from type annotation
|
51 | x: DefaultArg("str", "name")
52 |
53 | x: NamedArg("str", "name")
| ^^^^^ UP037
54 |
55 | x: DefaultNamedArg("str", "name")
|
= help: Remove quotes
Fix
50 50 |
51 51 | x: DefaultArg("str", "name")
52 52 |
53 |-x: NamedArg("str", "name")
53 |+x: NamedArg(str, "name")
54 54 |
55 55 | x: DefaultNamedArg("str", "name")
56 56 |
UP037.py:55:20: UP037 [*] Remove quotes from type annotation
|
53 | x: NamedArg("str", "name")
54 |
55 | x: DefaultNamedArg("str", "name")
| ^^^^^ UP037
56 |
57 | x: DefaultNamedArg("str", name="name")
|
= help: Remove quotes
Fix
52 52 |
53 53 | x: NamedArg("str", "name")
54 54 |
55 |-x: DefaultNamedArg("str", "name")
55 |+x: DefaultNamedArg(str, "name")
56 56 |
57 57 | x: DefaultNamedArg("str", name="name")
58 58 |
UP037.py:57:20: UP037 [*] Remove quotes from type annotation
|
55 | x: DefaultNamedArg("str", "name")
56 |
57 | x: DefaultNamedArg("str", name="name")
| ^^^^^ UP037
58 |
59 | x: VarArg("str")
|
= help: Remove quotes
Fix
54 54 |
55 55 | x: DefaultNamedArg("str", "name")
56 56 |
57 |-x: DefaultNamedArg("str", name="name")
57 |+x: DefaultNamedArg(str, name="name")
58 58 |
59 59 | x: VarArg("str")
60 60 |
UP037.py:59:11: UP037 [*] Remove quotes from type annotation
|
57 | x: DefaultNamedArg("str", name="name")
58 |
59 | x: VarArg("str")
| ^^^^^ UP037
60 |
61 | x: List[List[List["MyClass"]]]
|
= help: Remove quotes
Fix
56 56 |
57 57 | x: DefaultNamedArg("str", name="name")
58 58 |
59 |-x: VarArg("str")
59 |+x: VarArg(str)
60 60 |
61 61 | x: List[List[List["MyClass"]]]
62 62 |
UP037.py:61:19: UP037 [*] Remove quotes from type annotation
|
59 | x: VarArg("str")
60 |
61 | x: List[List[List["MyClass"]]]
| ^^^^^^^^^ UP037
62 |
63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
|
= help: Remove quotes
Fix
58 58 |
59 59 | x: VarArg("str")
60 60 |
61 |-x: List[List[List["MyClass"]]]
61 |+x: List[List[List[MyClass]]]
62 62 |
63 63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
64 64 |
UP037.py:63:29: UP037 [*] Remove quotes from type annotation
|
61 | x: List[List[List["MyClass"]]]
62 |
63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
| ^^^^^ UP037
64 |
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
= help: Remove quotes
Fix
60 60 |
61 61 | x: List[List[List["MyClass"]]]
62 62 |
63 |-x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
63 |+x: NamedTuple("X", [("foo", int), ("bar", "str")])
64 64 |
65 65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
66 66 |
UP037.py:63:45: UP037 [*] Remove quotes from type annotation
|
61 | x: List[List[List["MyClass"]]]
62 |
63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
| ^^^^^ UP037
64 |
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
= help: Remove quotes
Fix
60 60 |
61 61 | x: List[List[List["MyClass"]]]
62 62 |
63 |-x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
63 |+x: NamedTuple("X", [("foo", "int"), ("bar", str)])
64 64 |
65 65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
66 66 |
UP037.py:65:29: UP037 [*] Remove quotes from type annotation
|
63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
64 |
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
| ^^^^^ UP037
66 |
67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
|
= help: Remove quotes
Fix
62 62 |
63 63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
64 64 |
65 |-x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
65 |+x: NamedTuple("X", fields=[(foo, "int"), ("bar", "str")])
66 66 |
67 67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
68 68 |
UP037.py:65:36: UP037 [*] Remove quotes from type annotation
|
63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
64 |
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
| ^^^^^ UP037
66 |
67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
|
= help: Remove quotes
Fix
62 62 |
63 63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
64 64 |
65 |-x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
65 |+x: NamedTuple("X", fields=[("foo", int), ("bar", "str")])
66 66 |
67 67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
68 68 |
UP037.py:65:45: UP037 [*] Remove quotes from type annotation
|
63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
64 |
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
| ^^^^^ UP037
66 |
67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
|
= help: Remove quotes
Fix
62 62 |
63 63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
64 64 |
65 |-x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
65 |+x: NamedTuple("X", fields=[("foo", "int"), (bar, "str")])
66 66 |
67 67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
68 68 |
UP037.py:65:52: UP037 [*] Remove quotes from type annotation
|
63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
64 |
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
| ^^^^^ UP037
66 |
67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
|
= help: Remove quotes
Fix
62 62 |
63 63 | x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
64 64 |
65 |-x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
65 |+x: NamedTuple("X", fields=[("foo", "int"), ("bar", str)])
66 66 |
67 67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
68 68 |
UP037.py:67:24: UP037 [*] Remove quotes from type annotation
|
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
66 |
67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
| ^^^ UP037
68 |
69 | X: MyCallable("X")
|
= help: Remove quotes
Fix
64 64 |
65 65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
66 66 |
67 |-x: NamedTuple(typename="X", fields=[("foo", "int")])
67 |+x: NamedTuple(typename=X, fields=[("foo", "int")])
68 68 |
69 69 | X: MyCallable("X")
70 70 |
UP037.py:67:38: UP037 [*] Remove quotes from type annotation
|
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
66 |
67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
| ^^^^^ UP037
68 |
69 | X: MyCallable("X")
|
= help: Remove quotes
Fix
64 64 |
65 65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
66 66 |
67 |-x: NamedTuple(typename="X", fields=[("foo", "int")])
67 |+x: NamedTuple(typename="X", fields=[(foo, "int")])
68 68 |
69 69 | X: MyCallable("X")
70 70 |
UP037.py:67:45: UP037 [*] Remove quotes from type annotation
|
65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
66 |
67 | x: NamedTuple(typename="X", fields=[("foo", "int")])
| ^^^^^ UP037
68 |
69 | X: MyCallable("X")
|
= help: Remove quotes
Fix
64 64 |
65 65 | x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
66 66 |
67 |-x: NamedTuple(typename="X", fields=[("foo", "int")])
67 |+x: NamedTuple(typename="X", fields=[("foo", int)])
68 68 |
69 69 | X: MyCallable("X")
70 70 |

View File

@ -275,6 +275,7 @@ impl<'a> SemanticModel<'a> {
let mut seen_function = false;
let mut import_starred = false;
let mut annotation_id = None;
for (index, scope_id) in self.scopes.ancestor_ids(self.scope_id).enumerate() {
let scope = &self.scopes[scope_id];
if scope.kind.is_class() {
@ -293,7 +294,7 @@ impl<'a> SemanticModel<'a> {
}
}
if let Some(binding_id) = scope.get(symbol) {
for binding_id in scope.get_all(symbol) {
// Mark the binding as used.
let context = self.execution_context();
let reference_id = self.references.push(self.scope_id, range, context);
@ -305,6 +306,27 @@ impl<'a> SemanticModel<'a> {
self.bindings[binding_id].references.push(reference_id);
}
// If we're in a runtime context, but the binding is typing-only, don't treat
// it as resolved. For example, given:
//
// ```python
// from typing import TYPE_CHECKING
//
// if TYPE_CHECKING:
// from foo import Foo
//
// Foo()
// ```
//
// The `Foo` in `Foo()` should be treated as unresolved at runtime, but the `Foo` in
// `from foo import Foo` should be treated as used.
if self.bindings[binding_id].context.is_typing() {
if self.execution_context().is_runtime() {
annotation_id = Some(binding_id);
continue;
}
}
match self.bindings[binding_id].kind {
// If it's a type annotation, don't treat it as resolved. For example, given:
//
@ -405,7 +427,9 @@ impl<'a> SemanticModel<'a> {
import_starred = import_starred || scope.uses_star_imports();
}
if import_starred {
if let Some(annotation_id) = annotation_id {
ResolvedRead::TypingOnly(annotation_id)
} else if import_starred {
ResolvedRead::WildcardImport
} else {
ResolvedRead::NotFound
@ -1380,6 +1404,23 @@ pub enum ResolvedRead {
/// The `x` in `print(x)` is resolved to the binding of `x` in `x = 1`.
Resolved(BindingId),
/// The read reference is resolved to a specific binding, but the binding is only visible
/// at type-checking time.
///
/// For example, given:
/// ```python
/// from typing import TYPE_CHECKING
///
/// if TYPE_CHECKING:
/// from foo import Foo
///
/// Foo()
/// ```
///
/// The `Foo` in `Foo()` is resolved to the binding of `Foo` in `from foo import Foo`, but
/// the binding is only visible at type-checking time.
TypingOnly(BindingId),
/// The read reference is resolved to a context-specific, implicit global (e.g., `__class__`
/// within a class scope).
///