From b6e75e58c97003af923366abf8b822ebbc74fe8c Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 15 Oct 2023 20:09:37 -0400 Subject: [PATCH] Treat type aliases as typing-only expressions (#7968) ## Summary Given `type RecordOrThings = Record | int | str`, the right-hand side won't be evaluated at runtime. Same goes for `Record` in `type RecordCallback[R: Record] = Callable[[R], None]`. This PR modifies the visitation logic to treat them as typing-only. Closes https://github.com/astral-sh/ruff/issues/7966. --- .../flake8_type_checking/TCH004_15.py | 19 +++++++++++++++++++ crates/ruff_linter/src/checkers/ast/mod.rs | 6 ++++-- .../src/rules/flake8_type_checking/mod.rs | 1 + ...t-in-type-checking-block_TCH004_15.py.snap | 4 ++++ 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_15.py create mode 100644 crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_TCH004_15.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_15.py b/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_15.py new file mode 100644 index 0000000000..6cf57e2f83 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_15.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from collections.abc import Callable +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from .foo import Record + +type RecordOrThings = Record | int | str +type RecordCallback[R: Record] = Callable[[R], None] + + +def process_record[R: Record](record: R) -> None: + ... + + +class RecordContainer[R: Record]: + def add_record(self, record: R) -> None: + ... diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 26944ce0f0..a111c5cc49 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -580,7 +580,9 @@ where if let Some(type_params) = type_params { self.visit_type_params(type_params); } - self.visit_expr(value); + // The value in a `type` alias has annotation semantics, in that it's never + // evaluated at runtime. + self.visit_annotation(value); self.semantic.pop_scope(); self.visit_expr(name); } @@ -1766,7 +1768,7 @@ impl<'a> Checker<'a> { bound: Some(bound), .. }) = type_param { - self.visit_expr(bound); + self.visit_annotation(bound); } } } diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/mod.rs b/crates/ruff_linter/src/rules/flake8_type_checking/mod.rs index 314e1a6785..8bda8ec66d 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/mod.rs @@ -22,6 +22,7 @@ mod tests { #[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_15.py"))] #[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"))] diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_TCH004_15.py.snap b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_TCH004_15.py.snap new file mode 100644 index 0000000000..6c5ead2742 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__runtime-import-in-type-checking-block_TCH004_15.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs +--- +