mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-30 03:27:07 +00:00
[ty] Treat Callables as bound-method descriptors in special cases (#20802)
## Summary Treat `Callable`s as bound-method descriptors if `Callable` is the return type of a decorator that is applied to a function definition. See the [rendered version of the new test file](https://github.com/astral-sh/ruff/blob/david/callables-as-descriptors/crates/ty_python_semantic/resources/mdtest/call/callables_as_descriptors.md) for the full description of this new heuristic. I could imagine that we want to treat `Callable`s as bound-method descriptors in other cases as well, but this seems like a step in the right direction. I am planning to add other "use cases" from https://github.com/astral-sh/ty/issues/491 to this test suite. partially addresses https://github.com/astral-sh/ty/issues/491 closes https://github.com/astral-sh/ty/issues/1333 ## Ecosystem impact All positive * 2961 removed `unsupported-operator` diagnostics on `sympy`, which was one of the main motivations for implementing this change * 37 removed `missing-argument` diagnostics, and no added call-error diagnostics, which is an indicator that this heuristic shouldn't cause many false positives * A few removed `possibly-missing-attribute` diagnostics when accessing attributes like `__name__` on decorated functions. The two added `unused-ignore-comment` diagnostics are also cases of this. * One new `invalid-assignment` diagnostic on `dd-trace-py`, which looks suspicious, but only because our `invalid-assignment` diagnostics are not great. This is actually a "Implicit shadowing of function" diagnostic that hides behind the `invalid-assignment` diagnostic, because a module-global function is being patched through a `module.func` attribute assignment. ## Test Plan New Markdown tests.
This commit is contained in:
parent
d912f13661
commit
4b8e278a88
3 changed files with 217 additions and 1 deletions
|
|
@ -1014,6 +1014,13 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn unwrap_as_callable_type(self) -> Option<CallableType<'db>> {
|
||||
match self {
|
||||
Type::Callable(callable_type) => Some(callable_type),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn expect_dynamic(self) -> DynamicType<'db> {
|
||||
self.into_dynamic()
|
||||
.expect("Expected a Type::Dynamic variant")
|
||||
|
|
|
|||
|
|
@ -2175,7 +2175,26 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
.try_call(self.db(), &CallArguments::positional([inferred_ty]))
|
||||
.map(|bindings| bindings.return_type(self.db()))
|
||||
{
|
||||
Ok(return_ty) => return_ty,
|
||||
Ok(return_ty) => {
|
||||
let is_input_function_like = inferred_ty
|
||||
.into_callable(self.db())
|
||||
.and_then(Type::unwrap_as_callable_type)
|
||||
.is_some_and(|callable| callable.is_function_like(self.db()));
|
||||
if is_input_function_like
|
||||
&& let Some(callable_type) = return_ty.unwrap_as_callable_type()
|
||||
{
|
||||
// When a method on a class is decorated with a function that returns a `Callable`, assume that
|
||||
// the returned callable is also function-like. See "Decorating a method with a `Callable`-typed
|
||||
// decorator" in `callables_as_descriptors.md` for the extended explanation.
|
||||
Type::Callable(CallableType::new(
|
||||
self.db(),
|
||||
callable_type.signatures(self.db()),
|
||||
true,
|
||||
))
|
||||
} else {
|
||||
return_ty
|
||||
}
|
||||
}
|
||||
Err(CallError(_, bindings)) => {
|
||||
bindings.report_diagnostics(&self.context, (*decorator_node).into());
|
||||
bindings.return_type(self.db())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue