diff --git a/crates/ruff/resources/test/fixtures/flake8_type_checking/TCH004_10.py b/crates/ruff/resources/test/fixtures/flake8_type_checking/TCH004_10.py new file mode 100644 index 0000000000..0fc70b83cc --- /dev/null +++ b/crates/ruff/resources/test/fixtures/flake8_type_checking/TCH004_10.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Tuple, List, Dict + +x: Tuple + + +class C: + x: List + + +def f(): + x: Dict diff --git a/crates/ruff/resources/test/fixtures/flake8_type_checking/TCH004_9.py b/crates/ruff/resources/test/fixtures/flake8_type_checking/TCH004_9.py new file mode 100644 index 0000000000..25867433fe --- /dev/null +++ b/crates/ruff/resources/test/fixtures/flake8_type_checking/TCH004_9.py @@ -0,0 +1,14 @@ +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Tuple, List, Dict + +x: Tuple + + +class C: + x: List + + +def f(): + x: Dict diff --git a/crates/ruff/src/checkers/ast.rs b/crates/ruff/src/checkers/ast.rs index 1aa697a387..f046b01d73 100644 --- a/crates/ruff/src/checkers/ast.rs +++ b/crates/ruff/src/checkers/ast.rs @@ -1945,7 +1945,21 @@ where value, .. } => { - self.visit_annotation(annotation); + // If we're in a class or module scope, then the annotation needs to be available + // at runtime. + // See: https://docs.python.org/3/reference/simple_stmts.html#annotated-assignment-statements + if !self.annotations_future_enabled + && matches!( + self.current_scope().kind, + ScopeKind::Class(..) | ScopeKind::Module + ) + { + self.in_type_definition = true; + self.visit_expr(annotation); + self.in_type_definition = false; + } else { + self.visit_annotation(annotation); + } if let Some(expr) = value { if self.match_typing_expr(annotation, "TypeAlias") { self.in_type_definition = true; diff --git a/crates/ruff/src/rules/flake8_type_checking/mod.rs b/crates/ruff/src/rules/flake8_type_checking/mod.rs index 5fa2217443..7d8f0c26d7 100644 --- a/crates/ruff/src/rules/flake8_type_checking/mod.rs +++ b/crates/ruff/src/rules/flake8_type_checking/mod.rs @@ -26,6 +26,8 @@ mod tests { #[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_6.py"); "TCH004_6")] #[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_7.py"); "TCH004_7")] #[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_8.py"); "TCH004_8")] + #[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_9.py"); "TCH004_9")] + #[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_10.py"); "TCH004_10")] #[test_case(Rule::EmptyTypeCheckingBlock, Path::new("TCH005.py"); "TCH005")] #[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("strict.py"); "strict")] fn rules(rule_code: Rule, path: &Path) -> Result<()> { diff --git a/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_TCH004_10.py.snap b/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_TCH004_10.py.snap new file mode 100644 index 0000000000..b308635bbd --- /dev/null +++ b/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_TCH004_10.py.snap @@ -0,0 +1,6 @@ +--- +source: crates/ruff/src/rules/flake8_type_checking/mod.rs +expression: diagnostics +--- +[] + diff --git a/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_TCH004_4.py.snap b/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_TCH004_4.py.snap index d1924b10c2..38e43aef40 100644 --- a/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_TCH004_4.py.snap +++ b/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_TCH004_4.py.snap @@ -1,6 +1,16 @@ --- -source: src/rules/flake8_type_checking/mod.rs +source: crates/ruff/src/rules/flake8_type_checking/mod.rs expression: diagnostics --- -[] +- kind: + RuntimeImportInTypeCheckingBlock: + full_name: typing.Any + location: + row: 4 + column: 23 + end_location: + row: 4 + column: 26 + fix: ~ + parent: ~ diff --git a/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_TCH004_9.py.snap b/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_TCH004_9.py.snap new file mode 100644 index 0000000000..9e350e0180 --- /dev/null +++ b/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_TCH004_9.py.snap @@ -0,0 +1,27 @@ +--- +source: crates/ruff/src/rules/flake8_type_checking/mod.rs +expression: diagnostics +--- +- kind: + RuntimeImportInTypeCheckingBlock: + full_name: typing.Tuple + location: + row: 4 + column: 23 + end_location: + row: 4 + column: 28 + fix: ~ + parent: ~ +- kind: + RuntimeImportInTypeCheckingBlock: + full_name: typing.List + location: + row: 4 + column: 30 + end_location: + row: 4 + column: 34 + fix: ~ + parent: ~ +