From aeae63b7eac1d89c1ded8bc7b04d88ecf2a65cb9 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 13 Feb 2023 10:26:34 -0500 Subject: [PATCH] Avoid false-positives for runtime-types in type checking blocks (#2863) --- .../flake8_type_checking/TCH004_13.py | 10 ++++++++ crates/ruff/src/checkers/ast.rs | 23 +++++++++++-------- .../src/rules/flake8_type_checking/mod.rs | 1 + ...t-in-type-checking-block_TCH004_13.py.snap | 6 +++++ 4 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 crates/ruff/resources/test/fixtures/flake8_type_checking/TCH004_13.py create mode 100644 crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_TCH004_13.py.snap diff --git a/crates/ruff/resources/test/fixtures/flake8_type_checking/TCH004_13.py b/crates/ruff/resources/test/fixtures/flake8_type_checking/TCH004_13.py new file mode 100644 index 0000000000000..36f03b3ea4fdb --- /dev/null +++ b/crates/ruff/resources/test/fixtures/flake8_type_checking/TCH004_13.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Final, Literal, TypeAlias + + RatingKey: TypeAlias = Literal["good", "fair", "poor"] + +RATING_KEYS: Final[tuple[RatingKey, ...]] = ("good", "fair", "poor") diff --git a/crates/ruff/src/checkers/ast.rs b/crates/ruff/src/checkers/ast.rs index af954d3fcace5..9873444318fad 100644 --- a/crates/ruff/src/checkers/ast.rs +++ b/crates/ruff/src/checkers/ast.rs @@ -50,6 +50,7 @@ use crate::{autofix, docstrings, noqa, visibility}; const GLOBAL_SCOPE_INDEX: usize = 0; type DeferralContext<'a> = (Vec, Vec>); +type AnnotationContext = (bool, bool); #[allow(clippy::struct_excessive_bools)] pub struct Checker<'a> { @@ -84,8 +85,8 @@ pub struct Checker<'a> { pub(crate) scopes: Vec>, pub(crate) scope_stack: Vec, pub(crate) dead_scopes: Vec<(usize, Vec)>, - deferred_string_type_definitions: Vec<(Range, &'a str, bool, DeferralContext<'a>)>, - deferred_type_definitions: Vec<(&'a Expr, bool, DeferralContext<'a>)>, + deferred_string_type_definitions: Vec<(Range, &'a str, AnnotationContext, DeferralContext<'a>)>, + deferred_type_definitions: Vec<(&'a Expr, AnnotationContext, DeferralContext<'a>)>, deferred_functions: Vec<(&'a Stmt, DeferralContext<'a>, VisibleScope)>, deferred_lambdas: Vec<(&'a Expr, DeferralContext<'a>)>, deferred_for_loops: Vec<(&'a Stmt, DeferralContext<'a>)>, @@ -2065,13 +2066,13 @@ where self.deferred_string_type_definitions.push(( Range::from_located(expr), value, - self.in_annotation, + (self.in_annotation, self.in_type_checking_block), (self.scope_stack.clone(), self.parents.clone()), )); } else { self.deferred_type_definitions.push(( expr, - self.in_annotation, + (self.in_annotation, self.in_type_checking_block), (self.scope_stack.clone(), self.parents.clone()), )); } @@ -3186,7 +3187,7 @@ where self.deferred_string_type_definitions.push(( Range::from_located(expr), value, - self.in_annotation, + (self.in_annotation, self.in_type_checking_block), (self.scope_stack.clone(), self.parents.clone()), )); } @@ -4491,12 +4492,13 @@ impl<'a> Checker<'a> { fn check_deferred_type_definitions(&mut self) { self.deferred_type_definitions.reverse(); - while let Some((expr, in_annotation, (scopes, parents))) = + while let Some((expr, (in_annotation, in_type_checking_block), (scopes, parents))) = self.deferred_type_definitions.pop() { self.scope_stack = scopes; self.parents = parents; self.in_annotation = in_annotation; + self.in_type_checking_block = in_type_checking_block; self.in_type_definition = true; self.in_deferred_type_definition = true; self.visit_expr(expr); @@ -4511,7 +4513,7 @@ impl<'a> Checker<'a> { { let mut stacks = vec![]; self.deferred_string_type_definitions.reverse(); - while let Some((range, expression, in_annotation, context)) = + while let Some((range, expression, (in_annotation, in_type_checking_block), deferral)) = self.deferred_string_type_definitions.pop() { if let Ok(mut expr) = parser::parse_expression(expression, "") { @@ -4522,7 +4524,7 @@ impl<'a> Checker<'a> { } relocate_expr(&mut expr, range); allocator.push(expr); - stacks.push((in_annotation, context)); + stacks.push(((in_annotation, in_type_checking_block), deferral)); } else { if self .settings @@ -4538,10 +4540,13 @@ impl<'a> Checker<'a> { } } } - for (expr, (in_annotation, (scopes, parents))) in allocator.iter().zip(stacks) { + for (expr, ((in_annotation, in_type_checking_block), (scopes, parents))) in + allocator.iter().zip(stacks) + { self.scope_stack = scopes; self.parents = parents; self.in_annotation = in_annotation; + self.in_type_checking_block = in_type_checking_block; self.in_type_definition = true; self.in_deferred_string_type_definition = true; self.visit_expr(expr); diff --git a/crates/ruff/src/rules/flake8_type_checking/mod.rs b/crates/ruff/src/rules/flake8_type_checking/mod.rs index 008e19298ec6d..cbe62cd523443 100644 --- a/crates/ruff/src/rules/flake8_type_checking/mod.rs +++ b/crates/ruff/src/rules/flake8_type_checking/mod.rs @@ -30,6 +30,7 @@ mod tests { #[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_10.py"); "TCH004_10")] #[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_11.py"); "TCH004_11")] #[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_12.py"); "TCH004_12")] + #[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TCH004_13.py"); "TCH004_13")] #[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_13.py.snap b/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_TCH004_13.py.snap new file mode 100644 index 0000000000000..b308635bbdd2e --- /dev/null +++ b/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_TCH004_13.py.snap @@ -0,0 +1,6 @@ +--- +source: crates/ruff/src/rules/flake8_type_checking/mod.rs +expression: diagnostics +--- +[] +