mirror of https://github.com/astral-sh/ruff
[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
|
|
@ -1099,5 +1099,54 @@ static_assert(is_subtype_of(TypeOf[C.foo], object))
|
||||||
static_assert(not is_subtype_of(object, TypeOf[C.foo]))
|
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
|
[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
|
[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)
|
.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,
|
// 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.
|
// so it also, for now, just delegates to its instance fallback.
|
||||||
(Type::FunctionLiteral(_), _) => KnownClass::FunctionType
|
(Type::FunctionLiteral(_), _) => KnownClass::FunctionType
|
||||||
|
|
@ -833,6 +837,16 @@ impl<'db> Type<'db> {
|
||||||
self_instance.is_subtype_of(db, target_instance)
|
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,
|
// Other than the special cases enumerated above,
|
||||||
// `Instance` types are never subtypes of any other variants
|
// `Instance` types are never subtypes of any other variants
|
||||||
(Type::Instance(_), _) => false,
|
(Type::Instance(_), _) => false,
|
||||||
|
|
@ -4414,6 +4428,15 @@ pub struct BoundMethodType<'db> {
|
||||||
self_instance: Type<'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.
|
/// This type represents the set of all callable objects with a certain signature.
|
||||||
/// It can be written in type expressions using `typing.Callable`.
|
/// It can be written in type expressions using `typing.Callable`.
|
||||||
/// `lambda` expressions are inferred directly as `CallableType`s; all function-literal types
|
/// `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> {
|
pub(crate) fn parameters(&self) -> &Parameters<'db> {
|
||||||
&self.parameters
|
&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)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue