mirror of
https://github.com/astral-sh/ruff.git
synced 2026-10-28 19:00:26 +08:00
[ty] Skip method signature inference for concrete decorator results (#29708)
## Summary Checking [`discord/state.py`](https://github.com/Rapptz/discord.py/blob/65232c38702be5844cf2ce865a4777eb1928b5d0/discord/state.py) opened 100 Python files instead of 112. To decide whether `Interaction` was abstract, we used to follow the return annotation on [`Interaction.command`](https://github.com/Rapptz/discord.py/blob/65232c38702be5844cf2ce865a4777eb1928b5d0/discord/interactions.py#L355) into `discord.ext.commands`. ```python def cached_slot_property[Owner, Value]( name: str, ) -> Callable[[Callable[[Owner], Value]], CachedSlotProperty[Owner, Value]]: ... class Interaction: @cached_slot_property("_cs_command") def command(self) -> Command[Any, ..., Any] | ContextMenu | None: ... ``` The decorator always returns a `CachedSlotProperty`. The return annotation on `command` determines the type of the cached value, but it cannot make the descriptor abstract. We now skip the method’s signature when its sole decorator rules out an abstract method or property. Checking all of discord.py showed no measurable speedup.
This commit is contained in:
1 parent
b07f24cb66
commit
229cb6ebdd
4 files changed
+67
-17
No files matched your search
@@ -542,3 +542,37 @@ class Alias:
|
||||
|
||||
Alias() # error: [call-non-callable]
|
||||
```
|
||||
|
||||
A decorator can also return an abstract method. Here the decorator replaces `Decorated.method` with
|
||||
`Abstract.method`, so `Decorated` is abstract.
|
||||
|
||||
```py
|
||||
from abc import ABC
|
||||
from collections.abc import Callable
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
def replace_with(value: T) -> Callable[[object], T]:
|
||||
return lambda _: value
|
||||
|
||||
class Decorated(ABC):
|
||||
@replace_with(Abstract.method)
|
||||
def method(self) -> None:
|
||||
pass
|
||||
|
||||
Decorated() # error: [call-non-callable]
|
||||
```
|
||||
|
||||
Python applies the inner decorator first. It returns a plain object, which the outer decorator
|
||||
replaces with `Abstract.method`.
|
||||
|
||||
```py
|
||||
class Stacked(ABC):
|
||||
@replace_with(Abstract.method)
|
||||
@replace_with(object())
|
||||
def method(self) -> None:
|
||||
pass
|
||||
|
||||
Stacked() # error: [call-non-callable]
|
||||
```
|
||||
@@ -18,7 +18,7 @@ use crate::{
|
||||
ClassBase, ClassLiteral, ClassType, LintDiagnosticGuard, Parameters, Signature, Type,
|
||||
binding_type,
|
||||
diagnostic::{AbstractMethodAnnotationPolicy, abstract_method_span},
|
||||
function::{AbstractMethodKind, FunctionDecorators},
|
||||
function::AbstractMethodKind,
|
||||
infer::{function_known_decorators, infer_definition_types},
|
||||
},
|
||||
};
|
||||
@@ -389,11 +389,7 @@ fn might_be_explicitly_abstract<'db>(db: &'db dyn Db, definition: Definition<'db
|
||||
if !function.has_decorators() {
|
||||
return false;
|
||||
}
|
||||
let decorators = function_known_decorators(db, definition);
|
||||
decorators
|
||||
.known_decorators()
|
||||
.contains(FunctionDecorators::ABSTRACT_METHOD)
|
||||
|| decorators.has_unknown_decorators()
|
||||
function_known_decorators(db, definition).may_produce_abstract_method()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
|
||||
|
||||
@@ -309,7 +309,7 @@ pub(crate) fn is_discarded_dict_key_assignment<'db>(
|
||||
#[salsa::tracked(
|
||||
returns(ref),
|
||||
cycle_initial=|_, _, _| FunctionDecoratorInference {
|
||||
has_unknown_decorators: true,
|
||||
may_produce_abstract_method: true,
|
||||
..FunctionDecoratorInference::default()
|
||||
},
|
||||
heap_size=ruff_memory_usage::heap_size
|
||||
@@ -356,8 +356,9 @@ pub(crate) struct FunctionDecoratorInference<'db> {
|
||||
called_functions: Box<[FunctionType<'db>]>,
|
||||
legacy_aliases: Box<[Definition<'db>]>,
|
||||
known_decorators: FunctionDecorators,
|
||||
/// Whether any decorator is unrecognized, or decorator inference is incomplete.
|
||||
has_unknown_decorators: bool,
|
||||
/// Whether a decorator could produce an explicitly abstract method, or decorator
|
||||
/// inference is incomplete.
|
||||
may_produce_abstract_method: bool,
|
||||
context: InferContextResult,
|
||||
}
|
||||
|
||||
@@ -387,12 +388,12 @@ impl<'db> FunctionDecoratorInference<'db> {
|
||||
&self.legacy_aliases
|
||||
}
|
||||
|
||||
pub(super) fn known_decorators(&self) -> FunctionDecorators {
|
||||
fn known_decorators(&self) -> FunctionDecorators {
|
||||
self.known_decorators
|
||||
}
|
||||
|
||||
pub(super) fn has_unknown_decorators(&self) -> bool {
|
||||
self.has_unknown_decorators
|
||||
pub(super) fn may_produce_abstract_method(&self) -> bool {
|
||||
self.may_produce_abstract_method
|
||||
}
|
||||
|
||||
fn context_result(&self) -> &InferContextResult {
|
||||
|
||||
@@ -12254,25 +12254,44 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
|
||||
pub(super) fn finish_function_decorator_inference(mut self) -> FunctionDecoratorInference<'db> {
|
||||
// Specializing a nominal return type can change a descriptor's type arguments, but it
|
||||
// cannot turn the descriptor into an abstract function, bound method, or property.
|
||||
fn returns_nominal_instance<'db>(db: &'db dyn Db, decorator: Type<'db>) -> bool {
|
||||
let Type::Callable(callable) = decorator else {
|
||||
return false;
|
||||
};
|
||||
if callable.kind(db) != CallableTypeKind::Regular {
|
||||
return false;
|
||||
}
|
||||
let [signature] = callable.signatures(db).overloads.as_slice() else {
|
||||
return false;
|
||||
};
|
||||
matches!(signature.return_ty, Type::NominalInstance(_))
|
||||
}
|
||||
|
||||
self.infer_region();
|
||||
|
||||
let mut known_decorators = FunctionDecorators::empty();
|
||||
let mut has_unknown_decorators = true;
|
||||
let mut may_produce_abstract_method = true;
|
||||
if let InferenceRegion::FunctionDecorators(definition) = self.region
|
||||
&& let DefinitionKind::Function(function) = definition.kind(self.db())
|
||||
{
|
||||
has_unknown_decorators = false;
|
||||
for decorator in &function.node(self.module()).decorator_list {
|
||||
may_produce_abstract_method = false;
|
||||
let decorators = &function.node(self.module()).decorator_list;
|
||||
for decorator in decorators {
|
||||
let ty = self.expression_type(&decorator.expression);
|
||||
let flags = FunctionDecorators::from_decorator_type(self.db(), ty);
|
||||
known_decorators |= flags;
|
||||
if flags.is_empty()
|
||||
&& !matches!(ty, Type::ClassLiteral(class)
|
||||
if class.known(self.db()) == Some(KnownClass::Property))
|
||||
&& !(decorators.len() == 1 && returns_nominal_instance(self.db(), ty))
|
||||
{
|
||||
has_unknown_decorators = true;
|
||||
may_produce_abstract_method = true;
|
||||
}
|
||||
}
|
||||
may_produce_abstract_method |=
|
||||
known_decorators.contains(FunctionDecorators::ABSTRACT_METHOD);
|
||||
}
|
||||
|
||||
let Self {
|
||||
@@ -12314,7 +12333,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice(),
|
||||
known_decorators,
|
||||
has_unknown_decorators,
|
||||
may_produce_abstract_method,
|
||||
context,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user