mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 18:28:56 +00:00
[red-knot] Add callable subtyping for callable instances and bound methods (#17105)
## Summary Trying to improve #17005 Partially fixes #16953 ## Test Plan Update is_subtype_of.md --------- Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
parent
d38f6fcc55
commit
eb3e176309
3 changed files with 79 additions and 0 deletions
|
@ -1099,5 +1099,54 @@ static_assert(is_subtype_of(TypeOf[C.foo], object))
|
|||
static_assert(not is_subtype_of(object, TypeOf[C.foo]))
|
||||
```
|
||||
|
||||
### Classes with `__call__`
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
from knot_extensions import TypeOf, is_subtype_of, static_assert, is_assignable_to
|
||||
|
||||
class A:
|
||||
def __call__(self, a: int) -> int:
|
||||
return a
|
||||
|
||||
a = A()
|
||||
|
||||
static_assert(is_subtype_of(A, Callable[[int], int]))
|
||||
static_assert(not is_subtype_of(A, Callable[[], int]))
|
||||
static_assert(not is_subtype_of(Callable[[int], int], A))
|
||||
|
||||
def f(fn: Callable[[int], int]) -> None: ...
|
||||
|
||||
f(a)
|
||||
```
|
||||
|
||||
### Bound methods
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
from knot_extensions import TypeOf, static_assert, is_subtype_of
|
||||
|
||||
class A:
|
||||
def f(self, a: int) -> int:
|
||||
return a
|
||||
|
||||
@classmethod
|
||||
def g(cls, a: int) -> int:
|
||||
return a
|
||||
|
||||
a = A()
|
||||
|
||||
static_assert(is_subtype_of(TypeOf[a.f], Callable[[int], int]))
|
||||
static_assert(is_subtype_of(TypeOf[a.g], Callable[[int], int]))
|
||||
static_assert(is_subtype_of(TypeOf[A.g], Callable[[int], int]))
|
||||
|
||||
static_assert(not is_subtype_of(TypeOf[a.f], Callable[[float], int]))
|
||||
static_assert(not is_subtype_of(TypeOf[A.g], Callable[[], int]))
|
||||
|
||||
# TODO: This assertion should be true
|
||||
# error: [static-assert-error] "Static assertion error: argument evaluates to `False`"
|
||||
static_assert(is_subtype_of(TypeOf[A.f], Callable[[A, int], int]))
|
||||
```
|
||||
|
||||
[special case for float and complex]: https://typing.readthedocs.io/en/latest/spec/special-types.html#special-cases-for-float-and-complex
|
||||
[typing documentation]: https://typing.readthedocs.io/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence
|
||||
|
|
|
@ -727,6 +727,10 @@ impl<'db> Type<'db> {
|
|||
.is_subtype_of(db, target)
|
||||
}
|
||||
|
||||
(Type::BoundMethod(self_bound_method), Type::Callable(_)) => self_bound_method
|
||||
.into_callable_type(db)
|
||||
.is_subtype_of(db, target),
|
||||
|
||||
// A `FunctionLiteral` type is a single-valued type like the other literals handled above,
|
||||
// so it also, for now, just delegates to its instance fallback.
|
||||
(Type::FunctionLiteral(_), _) => KnownClass::FunctionType
|
||||
|
@ -833,6 +837,16 @@ impl<'db> Type<'db> {
|
|||
self_instance.is_subtype_of(db, target_instance)
|
||||
}
|
||||
|
||||
(Type::Instance(_), Type::Callable(_)) => {
|
||||
let call_symbol = self.member(db, "__call__").symbol;
|
||||
match call_symbol {
|
||||
Symbol::Type(Type::BoundMethod(call_function), _) => call_function
|
||||
.into_callable_type(db)
|
||||
.is_subtype_of(db, target),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
// Other than the special cases enumerated above,
|
||||
// `Instance` types are never subtypes of any other variants
|
||||
(Type::Instance(_), _) => false,
|
||||
|
@ -4414,6 +4428,15 @@ pub struct BoundMethodType<'db> {
|
|||
self_instance: Type<'db>,
|
||||
}
|
||||
|
||||
impl<'db> BoundMethodType<'db> {
|
||||
pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||
Type::Callable(CallableType::new(
|
||||
db,
|
||||
self.function(db).signature(db).bind_self(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// This type represents the set of all callable objects with a certain signature.
|
||||
/// It can be written in type expressions using `typing.Callable`.
|
||||
/// `lambda` expressions are inferred directly as `CallableType`s; all function-literal types
|
||||
|
|
|
@ -265,6 +265,13 @@ impl<'db> Signature<'db> {
|
|||
pub(crate) fn parameters(&self) -> &Parameters<'db> {
|
||||
&self.parameters
|
||||
}
|
||||
|
||||
pub(crate) fn bind_self(&self) -> Self {
|
||||
Self {
|
||||
parameters: Parameters::new(self.parameters().iter().skip(1).cloned()),
|
||||
return_ty: self.return_ty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue