[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:
Charlie Marsh authored and GitHub committed 2026-10-25 16:50:01 -04:00
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)]
+7 -6
View File
@@ -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,
}
}