From c920cbc7cb460101259f032dc71a39a46aef64af Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 22 Feb 2024 22:22:55 -0500 Subject: [PATCH] Respect runtime-required decorators for function signatures --- .../src/checkers/ast/annotation.rs | 22 +++++++ crates/ruff_linter/src/checkers/ast/mod.rs | 65 +++++++++++++------ ...ort_runtime_evaluated_decorators_3.py.snap | 27 -------- 3 files changed, 68 insertions(+), 46 deletions(-) diff --git a/crates/ruff_linter/src/checkers/ast/annotation.rs b/crates/ruff_linter/src/checkers/ast/annotation.rs index 753fd3f30e93f..1421361a92a9c 100644 --- a/crates/ruff_linter/src/checkers/ast/annotation.rs +++ b/crates/ruff_linter/src/checkers/ast/annotation.rs @@ -1,3 +1,4 @@ +use ruff_python_ast::StmtFunctionDef; use ruff_python_semantic::{ScopeKind, SemanticModel}; use crate::rules::flake8_type_checking; @@ -26,6 +27,8 @@ pub(super) enum AnnotationContext { } impl AnnotationContext { + /// Determine the [`AnnotationContext`] for an annotation based on the current scope of the + /// semantic model. pub(super) fn from_model(semantic: &SemanticModel, settings: &LinterSettings) -> Self { // If the annotation is in a class scope (e.g., an annotated assignment for a // class field) or a function scope, and that class or function is marked as @@ -71,4 +74,23 @@ impl AnnotationContext { Self::TypingOnly } + + /// Determine the [`AnnotationContext`] to use for annotations in a function signature. + pub(super) fn from_function( + function_def: &StmtFunctionDef, + semantic: &SemanticModel, + settings: &LinterSettings, + ) -> Self { + if flake8_type_checking::helpers::runtime_required_function( + function_def, + &settings.flake8_type_checking.runtime_required_decorators, + semantic, + ) { + Self::RuntimeRequired + } else if semantic.future_annotations() { + Self::TypingOnly + } else { + Self::RuntimeEvaluated + } + } } diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index df79cf9f087ed..c8753bd961a6a 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -584,7 +584,8 @@ where // Function annotations are always evaluated at runtime, unless future annotations // are enabled. - let runtime_annotation = !self.semantic.future_annotations(); + let annotation = + AnnotationContext::from_function(function_def, &self.semantic, self.settings); // The first parameter may be a single dispatch. let mut singledispatch = @@ -608,10 +609,18 @@ where if let Some(expr) = ¶meter_with_default.parameter.annotation { if singledispatch { self.visit_runtime_required_annotation(expr); - } else if runtime_annotation { - self.visit_runtime_evaluated_annotation(expr); } else { - self.visit_annotation(expr); + match annotation { + AnnotationContext::RuntimeRequired => { + self.visit_runtime_required_annotation(expr); + } + AnnotationContext::RuntimeEvaluated => { + self.visit_runtime_evaluated_annotation(expr); + } + AnnotationContext::TypingOnly => { + self.visit_annotation(expr); + } + } }; } if let Some(expr) = ¶meter_with_default.default { @@ -621,28 +630,46 @@ where } if let Some(arg) = ¶meters.vararg { if let Some(expr) = &arg.annotation { - if runtime_annotation { - self.visit_runtime_evaluated_annotation(expr); - } else { - self.visit_annotation(expr); - }; + match annotation { + AnnotationContext::RuntimeRequired => { + self.visit_runtime_required_annotation(expr); + } + AnnotationContext::RuntimeEvaluated => { + self.visit_runtime_evaluated_annotation(expr); + } + AnnotationContext::TypingOnly => { + self.visit_annotation(expr); + } + } } } if let Some(arg) = ¶meters.kwarg { if let Some(expr) = &arg.annotation { - if runtime_annotation { - self.visit_runtime_evaluated_annotation(expr); - } else { - self.visit_annotation(expr); - }; + match annotation { + AnnotationContext::RuntimeRequired => { + self.visit_runtime_required_annotation(expr); + } + AnnotationContext::RuntimeEvaluated => { + self.visit_runtime_evaluated_annotation(expr); + } + AnnotationContext::TypingOnly => { + self.visit_annotation(expr); + } + } } } for expr in returns { - if runtime_annotation { - self.visit_runtime_evaluated_annotation(expr); - } else { - self.visit_annotation(expr); - }; + match annotation { + AnnotationContext::RuntimeRequired => { + self.visit_runtime_required_annotation(expr); + } + AnnotationContext::RuntimeEvaluated => { + self.visit_runtime_evaluated_annotation(expr); + } + AnnotationContext::TypingOnly => { + self.visit_annotation(expr); + } + } } let definition = docstrings::extraction::extract_definition( diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__typing-only-standard-library-import_runtime_evaluated_decorators_3.py.snap b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__typing-only-standard-library-import_runtime_evaluated_decorators_3.py.snap index fc68794213d2e..618928099681a 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__typing-only-standard-library-import_runtime_evaluated_decorators_3.py.snap +++ b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__typing-only-standard-library-import_runtime_evaluated_decorators_3.py.snap @@ -30,31 +30,4 @@ runtime_evaluated_decorators_3.py:6:18: TCH003 [*] Move standard library import 13 16 | 14 17 | @attrs.define(auto_attribs=True) -runtime_evaluated_decorators_3.py:7:29: TCH003 [*] Move standard library import `collections.abc.Sequence` into a type-checking block - | -5 | from dataclasses import dataclass -6 | from uuid import UUID # TCH003 -7 | from collections.abc import Sequence - | ^^^^^^^^ TCH003 -8 | from pydantic import validate_call - | - = help: Move into type-checking block - -ℹ Unsafe fix -4 4 | from array import array -5 5 | from dataclasses import dataclass -6 6 | from uuid import UUID # TCH003 -7 |-from collections.abc import Sequence -8 7 | from pydantic import validate_call -9 8 | -10 9 | import attrs -11 10 | from attrs import frozen - 11 |+from typing import TYPE_CHECKING - 12 |+ - 13 |+if TYPE_CHECKING: - 14 |+ from collections.abc import Sequence -12 15 | -13 16 | -14 17 | @attrs.define(auto_attribs=True) -