mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 14:21:24 +00:00
Respect runtime-required decorators for function signatures (#10091)
## Summary The original implementation of this applied the runtime-required context to definitions _within_ the function, but not the signature itself. (We had test coverage; the snapshot was just correctly showing the wrong outcome.) Closes https://github.com/astral-sh/ruff/issues/10089.
This commit is contained in:
parent
6fe15e7289
commit
946028e358
3 changed files with 68 additions and 46 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
use ruff_python_ast::StmtFunctionDef;
|
||||||
use ruff_python_semantic::{ScopeKind, SemanticModel};
|
use ruff_python_semantic::{ScopeKind, SemanticModel};
|
||||||
|
|
||||||
use crate::rules::flake8_type_checking;
|
use crate::rules::flake8_type_checking;
|
||||||
|
@ -26,6 +27,8 @@ pub(super) enum AnnotationContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
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
|
// 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
|
// class field) or a function scope, and that class or function is marked as
|
||||||
|
@ -71,4 +74,23 @@ impl AnnotationContext {
|
||||||
|
|
||||||
Self::TypingOnly
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -584,7 +584,8 @@ where
|
||||||
|
|
||||||
// Function annotations are always evaluated at runtime, unless future annotations
|
// Function annotations are always evaluated at runtime, unless future annotations
|
||||||
// are enabled.
|
// 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.
|
// The first parameter may be a single dispatch.
|
||||||
let mut singledispatch =
|
let mut singledispatch =
|
||||||
|
@ -608,10 +609,18 @@ where
|
||||||
if let Some(expr) = ¶meter_with_default.parameter.annotation {
|
if let Some(expr) = ¶meter_with_default.parameter.annotation {
|
||||||
if singledispatch {
|
if singledispatch {
|
||||||
self.visit_runtime_required_annotation(expr);
|
self.visit_runtime_required_annotation(expr);
|
||||||
} else if runtime_annotation {
|
|
||||||
self.visit_runtime_evaluated_annotation(expr);
|
|
||||||
} else {
|
} else {
|
||||||
|
match annotation {
|
||||||
|
AnnotationContext::RuntimeRequired => {
|
||||||
|
self.visit_runtime_required_annotation(expr);
|
||||||
|
}
|
||||||
|
AnnotationContext::RuntimeEvaluated => {
|
||||||
|
self.visit_runtime_evaluated_annotation(expr);
|
||||||
|
}
|
||||||
|
AnnotationContext::TypingOnly => {
|
||||||
self.visit_annotation(expr);
|
self.visit_annotation(expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if let Some(expr) = ¶meter_with_default.default {
|
if let Some(expr) = ¶meter_with_default.default {
|
||||||
|
@ -621,28 +630,46 @@ where
|
||||||
}
|
}
|
||||||
if let Some(arg) = ¶meters.vararg {
|
if let Some(arg) = ¶meters.vararg {
|
||||||
if let Some(expr) = &arg.annotation {
|
if let Some(expr) = &arg.annotation {
|
||||||
if runtime_annotation {
|
match annotation {
|
||||||
|
AnnotationContext::RuntimeRequired => {
|
||||||
|
self.visit_runtime_required_annotation(expr);
|
||||||
|
}
|
||||||
|
AnnotationContext::RuntimeEvaluated => {
|
||||||
self.visit_runtime_evaluated_annotation(expr);
|
self.visit_runtime_evaluated_annotation(expr);
|
||||||
} else {
|
}
|
||||||
|
AnnotationContext::TypingOnly => {
|
||||||
self.visit_annotation(expr);
|
self.visit_annotation(expr);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(arg) = ¶meters.kwarg {
|
if let Some(arg) = ¶meters.kwarg {
|
||||||
if let Some(expr) = &arg.annotation {
|
if let Some(expr) = &arg.annotation {
|
||||||
if runtime_annotation {
|
match annotation {
|
||||||
|
AnnotationContext::RuntimeRequired => {
|
||||||
|
self.visit_runtime_required_annotation(expr);
|
||||||
|
}
|
||||||
|
AnnotationContext::RuntimeEvaluated => {
|
||||||
self.visit_runtime_evaluated_annotation(expr);
|
self.visit_runtime_evaluated_annotation(expr);
|
||||||
} else {
|
}
|
||||||
|
AnnotationContext::TypingOnly => {
|
||||||
self.visit_annotation(expr);
|
self.visit_annotation(expr);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for expr in returns {
|
for expr in returns {
|
||||||
if runtime_annotation {
|
match annotation {
|
||||||
|
AnnotationContext::RuntimeRequired => {
|
||||||
|
self.visit_runtime_required_annotation(expr);
|
||||||
|
}
|
||||||
|
AnnotationContext::RuntimeEvaluated => {
|
||||||
self.visit_runtime_evaluated_annotation(expr);
|
self.visit_runtime_evaluated_annotation(expr);
|
||||||
} else {
|
}
|
||||||
|
AnnotationContext::TypingOnly => {
|
||||||
self.visit_annotation(expr);
|
self.visit_annotation(expr);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let definition = docstrings::extraction::extract_definition(
|
let definition = docstrings::extraction::extract_definition(
|
||||||
|
|
|
@ -30,31 +30,4 @@ runtime_evaluated_decorators_3.py:6:18: TCH003 [*] Move standard library import
|
||||||
13 16 |
|
13 16 |
|
||||||
14 17 | @attrs.define(auto_attribs=True)
|
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)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue