[red-knot] Flatten `Type::Callable` into four `Type` variants (#17126)

## Summary

Currently our `Type::Callable` wraps a four-variant `CallableType` enum.
But as time has gone on, I think we've found that the four variants in
`CallableType` are really more different to each other than they are
similar to each other:
- `GeneralCallableType` is a structural type describing all callable
types with a certain signature, but the other three types are "literal
types", more similar to the `FunctionLiteral` variant
- `GeneralCallableType` is not a singleton or a single-valued type, but
the other three are all single-valued types
(`WrapperDescriptorDunderGet` is even a singleton type)
- `GeneralCallableType` has (or should have) ambiguous truthiness, but
all possible inhabitants of the other three types are always truthy.
- As a structural type, `GeneralCallableType` can contain inner unions
and intersections that must be sorted in some contexts in our internal
model, but this is not true for the other three variants.

This PR flattens `Type::Callable` into four distinct `Type::` variants.
In the process, it fixes a number of latent bugs that were concealed by
the current architecture but are laid bare by the refactor. Unit tests
for these bugs are included in the PR.
This commit is contained in:
Alex Waygood 2025-04-01 19:30:06 +01:00 committed by GitHub
parent a43b683d08
commit d6dcc377f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 384 additions and 303 deletions

View File

@ -240,4 +240,15 @@ static_assert(not is_equivalent_to(CallableTypeOf[f12], CallableTypeOf[f13]))
static_assert(not is_equivalent_to(CallableTypeOf[f13], CallableTypeOf[f12])) static_assert(not is_equivalent_to(CallableTypeOf[f13], CallableTypeOf[f12]))
``` ```
### Unions containing `Callable`s containing unions
Differently ordered unions inside `Callable`s inside unions can still be equivalent:
```py
from typing import Callable
from knot_extensions import is_equivalent_to, static_assert
static_assert(is_equivalent_to(int | Callable[[int | str], None], Callable[[str | int], None] | int))
```
[the equivalence relation]: https://typing.readthedocs.io/en/latest/spec/glossary.html#term-equivalent [the equivalence relation]: https://typing.readthedocs.io/en/latest/spec/glossary.html#term-equivalent

View File

@ -3,8 +3,9 @@
A type is single-valued iff it is not empty and all inhabitants of it compare equal. A type is single-valued iff it is not empty and all inhabitants of it compare equal.
```py ```py
import types
from typing_extensions import Any, Literal, LiteralString, Never, Callable from typing_extensions import Any, Literal, LiteralString, Never, Callable
from knot_extensions import is_single_valued, static_assert from knot_extensions import is_single_valued, static_assert, TypeOf
static_assert(is_single_valued(None)) static_assert(is_single_valued(None))
static_assert(is_single_valued(Literal[True])) static_assert(is_single_valued(Literal[True]))
@ -25,4 +26,11 @@ static_assert(not is_single_valued(tuple[None, int]))
static_assert(not is_single_valued(Callable[..., None])) static_assert(not is_single_valued(Callable[..., None]))
static_assert(not is_single_valued(Callable[[int, str], None])) static_assert(not is_single_valued(Callable[[int, str], None]))
class A:
def method(self): ...
static_assert(is_single_valued(TypeOf[A().method]))
static_assert(is_single_valued(TypeOf[types.FunctionType.__get__]))
static_assert(is_single_valued(TypeOf[A.method.__get__]))
``` ```

View File

@ -133,3 +133,29 @@ from knot_extensions import static_assert, is_singleton
reveal_type(types.NotImplementedType) # revealed: Unknown | Literal[_NotImplementedType] reveal_type(types.NotImplementedType) # revealed: Unknown | Literal[_NotImplementedType]
static_assert(not is_singleton(types.NotImplementedType)) static_assert(not is_singleton(types.NotImplementedType))
``` ```
### Callables
We currently treat the type of `types.FunctionType.__get__` as a singleton type that has its own
dedicated variant in the `Type` enum. That variant should be understood as a singleton type, but the
similar variants `Type::BoundMethod` and `Type::MethodWrapperDunderGet` should not be; nor should
`Type::Callable` types.
If we refactor `Type` in the future to get rid of some or all of these `Type` variants, the
assertion that the type of `types.FunctionType.__get__` is a singleton type does not necessarily
have to hold true; it's more of a unit test for our current implementation.
```py
import types
from typing import Callable
from knot_extensions import static_assert, is_singleton, TypeOf
class A:
def method(self): ...
static_assert(is_singleton(TypeOf[types.FunctionType.__get__]))
static_assert(not is_singleton(Callable[[], None]))
static_assert(not is_singleton(TypeOf[A().method]))
static_assert(not is_singleton(TypeOf[A.method.__get__]))
```

View File

@ -120,3 +120,26 @@ static_assert(is_subtype_of(typing.TypeAliasType, AlwaysTruthy))
static_assert(is_subtype_of(types.MethodWrapperType, AlwaysTruthy)) static_assert(is_subtype_of(types.MethodWrapperType, AlwaysTruthy))
static_assert(is_subtype_of(types.WrapperDescriptorType, AlwaysTruthy)) static_assert(is_subtype_of(types.WrapperDescriptorType, AlwaysTruthy))
``` ```
### `Callable` types always have ambiguous truthiness
```py
from typing import Callable
def f(x: Callable, y: Callable[[int], str]):
reveal_type(bool(x)) # revealed: bool
reveal_type(bool(y)) # revealed: bool
```
But certain callable single-valued types are known to be always truthy:
```py
from types import FunctionType
class A:
def method(self): ...
reveal_type(bool(A().method)) # revealed: Literal[True]
reveal_type(bool(f.__get__)) # revealed: Literal[True]
reveal_type(bool(FunctionType.__get__)) # revealed: Literal[True]
```

View File

@ -236,7 +236,35 @@ pub enum Type<'db> {
Never, Never,
/// A specific function object /// A specific function object
FunctionLiteral(FunctionType<'db>), FunctionLiteral(FunctionType<'db>),
/// A callable object /// Represents a callable `instance.method` where `instance` is an instance of a class
/// and `method` is a method (of that class).
///
/// See [`BoundMethodType`] for more information.
///
/// TODO: consider replacing this with `Callable & Instance(MethodType)`?
/// I.e. if we have a method `def f(self, x: int) -> str`, and see it being called as
/// `instance.f`, we could partially apply (and check) the `instance` argument against
/// the `self` parameter, and return a `MethodType & Callable[[int], str]`.
/// One drawback would be that we could not show the bound instance when that type is displayed.
BoundMethod(BoundMethodType<'db>),
/// Represents the callable `f.__get__` where `f` is a function.
///
/// TODO: consider replacing this with `Callable & types.MethodWrapperType` type?
/// Requires `Callable` to be able to represent overloads, e.g. `types.FunctionType.__get__` has
/// this behaviour when a method is accessed on a class vs an instance:
///
/// ```txt
/// * (None, type) -> Literal[function_on_which_it_was_called]
/// * (object, type | None) -> BoundMethod[instance, function_on_which_it_was_called]
/// ```
MethodWrapperDunderGet(FunctionType<'db>),
/// Represents the callable `FunctionType.__get__`.
///
/// TODO: Similar to above, this could eventually be replaced by a generic `Callable`
/// type. We currently add this as a separate variant because `FunctionType.__get__`
/// is an overloaded method and we do not support `@overload` yet.
WrapperDescriptorDunderGet,
/// The type of an arbitrary callable object with a certain specified signature.
Callable(CallableType<'db>), Callable(CallableType<'db>),
/// A specific module object /// A specific module object
ModuleLiteral(ModuleLiteralType<'db>), ModuleLiteral(ModuleLiteralType<'db>),
@ -339,13 +367,11 @@ impl<'db> Type<'db> {
| Self::LiteralString | Self::LiteralString
| Self::SliceLiteral(_) | Self::SliceLiteral(_)
| Self::Dynamic(DynamicType::Unknown | DynamicType::Any) | Self::Dynamic(DynamicType::Unknown | DynamicType::Any)
| Self::Callable( | Self::BoundMethod(_)
CallableType::BoundMethod(_) | Self::WrapperDescriptorDunderGet
| CallableType::WrapperDescriptorDunderGet | Self::MethodWrapperDunderGet(_) => false,
| CallableType::MethodWrapperDunderGet(_),
) => false,
Self::Callable(CallableType::General(callable)) => { Self::Callable(callable) => {
let signature = callable.signature(db); let signature = callable.signature(db);
signature.parameters().iter().any(|param| { signature.parameters().iter().any(|param| {
param param
@ -565,6 +591,9 @@ impl<'db> Type<'db> {
Type::Intersection(intersection.to_sorted_intersection(db)) Type::Intersection(intersection.to_sorted_intersection(db))
} }
Type::Tuple(tuple) => Type::Tuple(tuple.with_sorted_unions_and_intersections(db)), Type::Tuple(tuple) => Type::Tuple(tuple.with_sorted_unions_and_intersections(db)),
Type::Callable(callable) => {
Type::Callable(callable.with_sorted_unions_and_intersections(db))
}
Type::LiteralString Type::LiteralString
| Type::Instance(_) | Type::Instance(_)
| Type::AlwaysFalsy | Type::AlwaysFalsy
@ -576,7 +605,9 @@ impl<'db> Type<'db> {
| Type::Dynamic(_) | Type::Dynamic(_)
| Type::Never | Type::Never
| Type::FunctionLiteral(_) | Type::FunctionLiteral(_)
| Type::Callable(_) | Type::MethodWrapperDunderGet(_)
| Type::BoundMethod(_)
| Type::WrapperDescriptorDunderGet
| Type::ModuleLiteral(_) | Type::ModuleLiteral(_)
| Type::ClassLiteral(_) | Type::ClassLiteral(_)
| Type::KnownInstance(_) | Type::KnownInstance(_)
@ -703,27 +734,22 @@ impl<'db> Type<'db> {
.is_subtype_of(db, target), .is_subtype_of(db, target),
// The same reasoning applies for these special callable types: // The same reasoning applies for these special callable types:
(Type::Callable(CallableType::BoundMethod(_)), _) => KnownClass::MethodType (Type::BoundMethod(_), _) => KnownClass::MethodType
.to_instance(db) .to_instance(db)
.is_subtype_of(db, target), .is_subtype_of(db, target),
(Type::Callable(CallableType::MethodWrapperDunderGet(_)), _) => { (Type::MethodWrapperDunderGet(_), _) => KnownClass::WrapperDescriptorType
KnownClass::WrapperDescriptorType .to_instance(db)
.to_instance(db) .is_subtype_of(db, target),
.is_subtype_of(db, target) (Type::WrapperDescriptorDunderGet, _) => KnownClass::WrapperDescriptorType
} .to_instance(db)
(Type::Callable(CallableType::WrapperDescriptorDunderGet), _) => { .is_subtype_of(db, target),
KnownClass::WrapperDescriptorType
.to_instance(db) (Type::Callable(self_callable), Type::Callable(other_callable)) => {
.is_subtype_of(db, target) self_callable.is_subtype_of(db, other_callable)
} }
( (Type::Callable(_), _) => {
Type::Callable(CallableType::General(self_callable)), // TODO: Implement subtyping between callable types and other types like
Type::Callable(CallableType::General(other_callable)),
) => self_callable.is_subtype_of(db, other_callable),
(Type::Callable(CallableType::General(_)), _) => {
// TODO: Implement subtyping between general callable types and other types like
// function literals, bound methods, class literals, `type[]`, etc.) // function literals, bound methods, class literals, `type[]`, etc.)
false false
} }
@ -961,10 +987,9 @@ impl<'db> Type<'db> {
) )
} }
( (Type::Callable(self_callable), Type::Callable(target_callable)) => {
Type::Callable(CallableType::General(self_callable)), self_callable.is_assignable_to(db, target_callable)
Type::Callable(CallableType::General(target_callable)), }
) => self_callable.is_assignable_to(db, target_callable),
(Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => { (Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => {
self_function_literal self_function_literal
@ -991,10 +1016,7 @@ impl<'db> Type<'db> {
left.is_equivalent_to(db, right) left.is_equivalent_to(db, right)
} }
(Type::Tuple(left), Type::Tuple(right)) => left.is_equivalent_to(db, right), (Type::Tuple(left), Type::Tuple(right)) => left.is_equivalent_to(db, right),
( (Type::Callable(left), Type::Callable(right)) => left.is_equivalent_to(db, right),
Type::Callable(CallableType::General(left)),
Type::Callable(CallableType::General(right)),
) => left.is_equivalent_to(db, right),
_ => self == other && self.is_fully_static(db) && other.is_fully_static(db), _ => self == other && self.is_fully_static(db) && other.is_fully_static(db),
} }
} }
@ -1053,10 +1075,9 @@ impl<'db> Type<'db> {
first.is_gradual_equivalent_to(db, second) first.is_gradual_equivalent_to(db, second)
} }
( (Type::Callable(first), Type::Callable(second)) => {
Type::Callable(CallableType::General(first)), first.is_gradual_equivalent_to(db, second)
Type::Callable(CallableType::General(second)), }
) => first.is_gradual_equivalent_to(db, second),
_ => false, _ => false,
} }
@ -1113,11 +1134,9 @@ impl<'db> Type<'db> {
| Type::BytesLiteral(..) | Type::BytesLiteral(..)
| Type::SliceLiteral(..) | Type::SliceLiteral(..)
| Type::FunctionLiteral(..) | Type::FunctionLiteral(..)
| Type::Callable( | Type::BoundMethod(..)
CallableType::BoundMethod(..) | Type::MethodWrapperDunderGet(..)
| CallableType::MethodWrapperDunderGet(..) | Type::WrapperDescriptorDunderGet
| CallableType::WrapperDescriptorDunderGet,
)
| Type::ModuleLiteral(..) | Type::ModuleLiteral(..)
| Type::ClassLiteral(..) | Type::ClassLiteral(..)
| Type::KnownInstance(..)), | Type::KnownInstance(..)),
@ -1127,11 +1146,9 @@ impl<'db> Type<'db> {
| Type::BytesLiteral(..) | Type::BytesLiteral(..)
| Type::SliceLiteral(..) | Type::SliceLiteral(..)
| Type::FunctionLiteral(..) | Type::FunctionLiteral(..)
| Type::Callable( | Type::BoundMethod(..)
CallableType::BoundMethod(..) | Type::MethodWrapperDunderGet(..)
| CallableType::MethodWrapperDunderGet(..) | Type::WrapperDescriptorDunderGet
| CallableType::WrapperDescriptorDunderGet,
)
| Type::ModuleLiteral(..) | Type::ModuleLiteral(..)
| Type::ClassLiteral(..) | Type::ClassLiteral(..)
| Type::KnownInstance(..)), | Type::KnownInstance(..)),
@ -1146,7 +1163,9 @@ impl<'db> Type<'db> {
| Type::BooleanLiteral(..) | Type::BooleanLiteral(..)
| Type::BytesLiteral(..) | Type::BytesLiteral(..)
| Type::FunctionLiteral(..) | Type::FunctionLiteral(..)
| Type::Callable(..) | Type::BoundMethod(..)
| Type::MethodWrapperDunderGet(..)
| Type::WrapperDescriptorDunderGet
| Type::IntLiteral(..) | Type::IntLiteral(..)
| Type::SliceLiteral(..) | Type::SliceLiteral(..)
| Type::StringLiteral(..) | Type::StringLiteral(..)
@ -1158,7 +1177,9 @@ impl<'db> Type<'db> {
| Type::BooleanLiteral(..) | Type::BooleanLiteral(..)
| Type::BytesLiteral(..) | Type::BytesLiteral(..)
| Type::FunctionLiteral(..) | Type::FunctionLiteral(..)
| Type::Callable(..) | Type::BoundMethod(..)
| Type::MethodWrapperDunderGet(..)
| Type::WrapperDescriptorDunderGet
| Type::IntLiteral(..) | Type::IntLiteral(..)
| Type::SliceLiteral(..) | Type::SliceLiteral(..)
| Type::StringLiteral(..) | Type::StringLiteral(..)
@ -1187,7 +1208,9 @@ impl<'db> Type<'db> {
| Type::BytesLiteral(..) | Type::BytesLiteral(..)
| Type::SliceLiteral(..) | Type::SliceLiteral(..)
| Type::FunctionLiteral(..) | Type::FunctionLiteral(..)
| Type::Callable(..) | Type::BoundMethod(..)
| Type::MethodWrapperDunderGet(..)
| Type::WrapperDescriptorDunderGet
| Type::ModuleLiteral(..), | Type::ModuleLiteral(..),
) )
| ( | (
@ -1198,7 +1221,9 @@ impl<'db> Type<'db> {
| Type::BytesLiteral(..) | Type::BytesLiteral(..)
| Type::SliceLiteral(..) | Type::SliceLiteral(..)
| Type::FunctionLiteral(..) | Type::FunctionLiteral(..)
| Type::Callable(..) | Type::BoundMethod(..)
| Type::MethodWrapperDunderGet(..)
| Type::WrapperDescriptorDunderGet
| Type::ModuleLiteral(..), | Type::ModuleLiteral(..),
Type::SubclassOf(_), Type::SubclassOf(_),
) => true, ) => true,
@ -1303,32 +1328,20 @@ impl<'db> Type<'db> {
!KnownClass::FunctionType.is_subclass_of(db, class) !KnownClass::FunctionType.is_subclass_of(db, class)
} }
( (Type::BoundMethod(_), other) | (other, Type::BoundMethod(_)) => KnownClass::MethodType
Type::Callable(CallableType::BoundMethod(_)), .to_instance(db)
Type::Instance(InstanceType { class }), .is_disjoint_from(db, other),
)
| (
Type::Instance(InstanceType { class }),
Type::Callable(CallableType::BoundMethod(_)),
) => !KnownClass::MethodType.is_subclass_of(db, class),
( (Type::MethodWrapperDunderGet(_), other) | (other, Type::MethodWrapperDunderGet(_)) => {
Type::Callable(CallableType::MethodWrapperDunderGet(_)), KnownClass::MethodWrapperType
Type::Instance(InstanceType { class }), .to_instance(db)
) .is_disjoint_from(db, other)
| ( }
Type::Instance(InstanceType { class }),
Type::Callable(CallableType::MethodWrapperDunderGet(_)),
) => !KnownClass::MethodWrapperType.is_subclass_of(db, class),
( (Type::WrapperDescriptorDunderGet, other)
Type::Callable(CallableType::WrapperDescriptorDunderGet), | (other, Type::WrapperDescriptorDunderGet) => KnownClass::WrapperDescriptorType
Type::Instance(InstanceType { class }), .to_instance(db)
) .is_disjoint_from(db, other),
| (
Type::Instance(InstanceType { class }),
Type::Callable(CallableType::WrapperDescriptorDunderGet),
) => !KnownClass::WrapperDescriptorType.is_subclass_of(db, class),
(Type::ModuleLiteral(..), other @ Type::Instance(..)) (Type::ModuleLiteral(..), other @ Type::Instance(..))
| (other @ Type::Instance(..), Type::ModuleLiteral(..)) => { | (other @ Type::Instance(..), Type::ModuleLiteral(..)) => {
@ -1367,9 +1380,8 @@ impl<'db> Type<'db> {
instance.is_disjoint_from(db, KnownClass::Tuple.to_instance(db)) instance.is_disjoint_from(db, KnownClass::Tuple.to_instance(db))
} }
(Type::Callable(CallableType::General(_)), _) (Type::Callable(_), _) | (_, Type::Callable(_)) => {
| (_, Type::Callable(CallableType::General(_))) => { // TODO: Implement disjointedness for callable types
// TODO: Implement disjointedness for general callable types
false false
} }
} }
@ -1381,11 +1393,9 @@ impl<'db> Type<'db> {
Type::Dynamic(_) => false, Type::Dynamic(_) => false,
Type::Never Type::Never
| Type::FunctionLiteral(..) | Type::FunctionLiteral(..)
| Type::Callable( | Type::BoundMethod(_)
CallableType::BoundMethod(_) | Type::WrapperDescriptorDunderGet
| CallableType::MethodWrapperDunderGet(_) | Type::MethodWrapperDunderGet(_)
| CallableType::WrapperDescriptorDunderGet,
)
| Type::ModuleLiteral(..) | Type::ModuleLiteral(..)
| Type::IntLiteral(_) | Type::IntLiteral(_)
| Type::BooleanLiteral(_) | Type::BooleanLiteral(_)
@ -1425,7 +1435,7 @@ impl<'db> Type<'db> {
.elements(db) .elements(db)
.iter() .iter()
.all(|elem| elem.is_fully_static(db)), .all(|elem| elem.is_fully_static(db)),
Type::Callable(CallableType::General(callable)) => callable.is_fully_static(db), Type::Callable(callable) => callable.is_fully_static(db),
} }
} }
@ -1451,20 +1461,32 @@ impl<'db> Type<'db> {
Type::SubclassOf(..) => false, Type::SubclassOf(..) => false,
Type::BooleanLiteral(_) Type::BooleanLiteral(_)
| Type::FunctionLiteral(..) | Type::FunctionLiteral(..)
| Type::Callable( | Type::WrapperDescriptorDunderGet
CallableType::BoundMethod(_)
| CallableType::MethodWrapperDunderGet(_)
| CallableType::WrapperDescriptorDunderGet,
)
| Type::ClassLiteral(..) | Type::ClassLiteral(..)
| Type::ModuleLiteral(..) | Type::ModuleLiteral(..)
| Type::KnownInstance(..) => true, | Type::KnownInstance(..) => true,
Type::Callable(CallableType::General(_)) => { Type::Callable(_) => {
// A general callable type is never a singleton because for any given signature, // A callable type is never a singleton because for any given signature,
// there could be any number of distinct objects that are all callable with that // there could be any number of distinct objects that are all callable with that
// signature. // signature.
false false
} }
Type::BoundMethod(..) => {
// `BoundMethod` types are single-valued types, but not singleton types:
// ```pycon
// >>> class Foo:
// ... def bar(self): pass
// >>> f = Foo()
// >>> f.bar is f.bar
// False
// ```
false
}
Type::MethodWrapperDunderGet(_) => {
// Just a special case of `BoundMethod` really
// (this variant represents `f.__get__`, where `f` is any function)
false
}
Type::Instance(InstanceType { class }) => { Type::Instance(InstanceType { class }) => {
class.known(db).is_some_and(KnownClass::is_singleton) class.known(db).is_some_and(KnownClass::is_singleton)
} }
@ -1500,11 +1522,9 @@ impl<'db> Type<'db> {
pub(crate) fn is_single_valued(self, db: &'db dyn Db) -> bool { pub(crate) fn is_single_valued(self, db: &'db dyn Db) -> bool {
match self { match self {
Type::FunctionLiteral(..) Type::FunctionLiteral(..)
| Type::Callable( | Type::BoundMethod(_)
CallableType::BoundMethod(..) | Type::WrapperDescriptorDunderGet
| CallableType::MethodWrapperDunderGet(..) | Type::MethodWrapperDunderGet(_)
| CallableType::WrapperDescriptorDunderGet,
)
| Type::ModuleLiteral(..) | Type::ModuleLiteral(..)
| Type::ClassLiteral(..) | Type::ClassLiteral(..)
| Type::IntLiteral(..) | Type::IntLiteral(..)
@ -1535,7 +1555,7 @@ impl<'db> Type<'db> {
| Type::LiteralString | Type::LiteralString
| Type::AlwaysTruthy | Type::AlwaysTruthy
| Type::AlwaysFalsy | Type::AlwaysFalsy
| Type::Callable(CallableType::General(_)) => false, | Type::Callable(_) => false,
} }
} }
@ -1568,10 +1588,9 @@ impl<'db> Type<'db> {
Type::ClassLiteral(class_literal @ ClassLiteralType { class }) => { Type::ClassLiteral(class_literal @ ClassLiteralType { class }) => {
match (class.known(db), name) { match (class.known(db), name) {
(Some(KnownClass::FunctionType), "__get__") => Some( (Some(KnownClass::FunctionType), "__get__") => {
Symbol::bound(Type::Callable(CallableType::WrapperDescriptorDunderGet)) Some(Symbol::bound(Type::WrapperDescriptorDunderGet).into())
.into(), }
),
(Some(KnownClass::FunctionType), "__set__" | "__delete__") => { (Some(KnownClass::FunctionType), "__set__" | "__delete__") => {
// Hard code this knowledge, as we look up `__set__` and `__delete__` on `FunctionType` often. // Hard code this knowledge, as we look up `__set__` and `__delete__` on `FunctionType` often.
Some(Symbol::Unbound.into()) Some(Symbol::Unbound.into())
@ -1632,6 +1651,9 @@ impl<'db> Type<'db> {
Type::FunctionLiteral(_) Type::FunctionLiteral(_)
| Type::Callable(_) | Type::Callable(_)
| Type::BoundMethod(_)
| Type::WrapperDescriptorDunderGet
| Type::MethodWrapperDunderGet(_)
| Type::ModuleLiteral(_) | Type::ModuleLiteral(_)
| Type::KnownInstance(_) | Type::KnownInstance(_)
| Type::AlwaysTruthy | Type::AlwaysTruthy
@ -1702,22 +1724,16 @@ impl<'db> Type<'db> {
.to_instance(db) .to_instance(db)
.instance_member(db, name), .instance_member(db, name),
Type::Callable(CallableType::BoundMethod(_)) => KnownClass::MethodType Type::BoundMethod(_) => KnownClass::MethodType
.to_instance(db) .to_instance(db)
.instance_member(db, name), .instance_member(db, name),
Type::Callable(CallableType::MethodWrapperDunderGet(_)) => { Type::MethodWrapperDunderGet(_) => KnownClass::MethodWrapperType
KnownClass::MethodWrapperType .to_instance(db)
.to_instance(db) .instance_member(db, name),
.instance_member(db, name) Type::WrapperDescriptorDunderGet => KnownClass::WrapperDescriptorType
} .to_instance(db)
Type::Callable(CallableType::WrapperDescriptorDunderGet) => { .instance_member(db, name),
KnownClass::WrapperDescriptorType Type::Callable(_) => KnownClass::Object.to_instance(db).instance_member(db, name),
.to_instance(db)
.instance_member(db, name)
}
Type::Callable(CallableType::General(_)) => {
KnownClass::Object.to_instance(db).instance_member(db, name)
}
Type::IntLiteral(_) => KnownClass::Int.to_instance(db).instance_member(db, name), Type::IntLiteral(_) => KnownClass::Int.to_instance(db).instance_member(db, name),
Type::BooleanLiteral(_) => KnownClass::Bool.to_instance(db).instance_member(db, name), Type::BooleanLiteral(_) => KnownClass::Bool.to_instance(db).instance_member(db, name),
@ -2038,18 +2054,17 @@ impl<'db> Type<'db> {
Type::Dynamic(..) | Type::Never => Symbol::bound(self).into(), Type::Dynamic(..) | Type::Never => Symbol::bound(self).into(),
Type::FunctionLiteral(function) if name == "__get__" => Symbol::bound(Type::Callable( Type::FunctionLiteral(function) if name == "__get__" => {
CallableType::MethodWrapperDunderGet(function), Symbol::bound(Type::MethodWrapperDunderGet(function)).into()
)) }
.into(),
Type::ClassLiteral(ClassLiteralType { class }) Type::ClassLiteral(ClassLiteralType { class })
if name == "__get__" && class.is_known(db, KnownClass::FunctionType) => if name == "__get__" && class.is_known(db, KnownClass::FunctionType) =>
{ {
Symbol::bound(Type::Callable(CallableType::WrapperDescriptorDunderGet)).into() Symbol::bound(Type::WrapperDescriptorDunderGet).into()
} }
Type::Callable(CallableType::BoundMethod(bound_method)) => match name_str { Type::BoundMethod(bound_method) => match name_str {
"__self__" => Symbol::bound(bound_method.self_instance(db)).into(), "__self__" => Symbol::bound(bound_method.self_instance(db)).into(),
"__func__" => { "__func__" => {
Symbol::bound(Type::FunctionLiteral(bound_method.function(db))).into() Symbol::bound(Type::FunctionLiteral(bound_method.function(db))).into()
@ -2065,19 +2080,13 @@ impl<'db> Type<'db> {
}) })
} }
}, },
Type::Callable(CallableType::MethodWrapperDunderGet(_)) => { Type::MethodWrapperDunderGet(_) => KnownClass::MethodWrapperType
KnownClass::MethodWrapperType .to_instance(db)
.to_instance(db) .member(db, &name),
.member(db, &name) Type::WrapperDescriptorDunderGet => KnownClass::WrapperDescriptorType
} .to_instance(db)
Type::Callable(CallableType::WrapperDescriptorDunderGet) => { .member(db, &name),
KnownClass::WrapperDescriptorType Type::Callable(_) => KnownClass::Object.to_instance(db).member(db, &name),
.to_instance(db)
.member(db, &name)
}
Type::Callable(CallableType::General(_)) => {
KnownClass::Object.to_instance(db).member(db, &name)
}
Type::Instance(InstanceType { class }) Type::Instance(InstanceType { class })
if matches!(name.as_str(), "major" | "minor") if matches!(name.as_str(), "major" | "minor")
@ -2243,21 +2252,31 @@ impl<'db> Type<'db> {
allow_short_circuit: bool, allow_short_circuit: bool,
) -> Result<Truthiness, BoolError<'db>> { ) -> Result<Truthiness, BoolError<'db>> {
let truthiness = match self { let truthiness = match self {
Type::Dynamic(_) | Type::Never => Truthiness::Ambiguous, Type::Dynamic(_) | Type::Never | Type::Callable(_) | Type::LiteralString => {
Type::FunctionLiteral(_) => Truthiness::AlwaysTrue, Truthiness::Ambiguous
Type::Callable(_) => Truthiness::AlwaysTrue, }
Type::ModuleLiteral(_) => Truthiness::AlwaysTrue,
Type::FunctionLiteral(_)
| Type::BoundMethod(_)
| Type::WrapperDescriptorDunderGet
| Type::MethodWrapperDunderGet(_)
| Type::ModuleLiteral(_)
| Type::SliceLiteral(_)
| Type::AlwaysTruthy => Truthiness::AlwaysTrue,
Type::AlwaysFalsy => Truthiness::AlwaysFalse,
Type::ClassLiteral(ClassLiteralType { class }) => class Type::ClassLiteral(ClassLiteralType { class }) => class
.metaclass_instance_type(db) .metaclass_instance_type(db)
.try_bool_impl(db, allow_short_circuit)?, .try_bool_impl(db, allow_short_circuit)?,
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() { Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
ClassBase::Dynamic(_) => Truthiness::Ambiguous, ClassBase::Dynamic(_) => Truthiness::Ambiguous,
ClassBase::Class(class) => { ClassBase::Class(class) => {
Type::class_literal(class).try_bool_impl(db, allow_short_circuit)? Type::class_literal(class).try_bool_impl(db, allow_short_circuit)?
} }
}, },
Type::AlwaysTruthy => Truthiness::AlwaysTrue,
Type::AlwaysFalsy => Truthiness::AlwaysFalse,
instance_ty @ Type::Instance(InstanceType { class }) => match class.known(db) { instance_ty @ Type::Instance(InstanceType { class }) => match class.known(db) {
Some(known_class) => known_class.bool(), Some(known_class) => known_class.bool(),
None => { None => {
@ -2322,7 +2341,9 @@ impl<'db> Type<'db> {
} }
} }
}, },
Type::KnownInstance(known_instance) => known_instance.bool(), Type::KnownInstance(known_instance) => known_instance.bool(),
Type::Union(union) => { Type::Union(union) => {
let mut truthiness = None; let mut truthiness = None;
let mut all_not_callable = true; let mut all_not_callable = true;
@ -2362,16 +2383,16 @@ impl<'db> Type<'db> {
} }
truthiness.unwrap_or(Truthiness::Ambiguous) truthiness.unwrap_or(Truthiness::Ambiguous)
} }
Type::Intersection(_) => { Type::Intersection(_) => {
// TODO // TODO
Truthiness::Ambiguous Truthiness::Ambiguous
} }
Type::IntLiteral(num) => Truthiness::from(*num != 0), Type::IntLiteral(num) => Truthiness::from(*num != 0),
Type::BooleanLiteral(bool) => Truthiness::from(*bool), Type::BooleanLiteral(bool) => Truthiness::from(*bool),
Type::StringLiteral(str) => Truthiness::from(!str.value(db).is_empty()), Type::StringLiteral(str) => Truthiness::from(!str.value(db).is_empty()),
Type::LiteralString => Truthiness::Ambiguous,
Type::BytesLiteral(bytes) => Truthiness::from(!bytes.value(db).is_empty()), Type::BytesLiteral(bytes) => Truthiness::from(!bytes.value(db).is_empty()),
Type::SliceLiteral(_) => Truthiness::AlwaysTrue,
Type::Tuple(items) => Truthiness::from(!items.elements(db).is_empty()), Type::Tuple(items) => Truthiness::from(!items.elements(db).is_empty()),
}; };
@ -2434,18 +2455,19 @@ impl<'db> Type<'db> {
/// [`CallErrorKind::NotCallable`]. /// [`CallErrorKind::NotCallable`].
fn signatures(self, db: &'db dyn Db) -> Signatures<'db> { fn signatures(self, db: &'db dyn Db) -> Signatures<'db> {
match self { match self {
Type::Callable(CallableType::General(callable)) => Signatures::single( Type::Callable(callable) => Signatures::single(CallableSignature::single(
CallableSignature::single(self, callable.signature(db).clone()), self,
), callable.signature(db).clone(),
)),
Type::Callable(CallableType::BoundMethod(bound_method)) => { Type::BoundMethod(bound_method) => {
let signature = bound_method.function(db).signature(db); let signature = bound_method.function(db).signature(db);
let signature = CallableSignature::single(self, signature.clone()) let signature = CallableSignature::single(self, signature.clone())
.with_bound_type(bound_method.self_instance(db)); .with_bound_type(bound_method.self_instance(db));
Signatures::single(signature) Signatures::single(signature)
} }
Type::Callable(CallableType::MethodWrapperDunderGet(_)) => { Type::MethodWrapperDunderGet(_) => {
// Here, we dynamically model the overloaded function signature of `types.FunctionType.__get__`. // Here, we dynamically model the overloaded function signature of `types.FunctionType.__get__`.
// This is required because we need to return more precise types than what the signature in // This is required because we need to return more precise types than what the signature in
// typeshed provides: // typeshed provides:
@ -2490,7 +2512,7 @@ impl<'db> Type<'db> {
Signatures::single(signature) Signatures::single(signature)
} }
Type::Callable(CallableType::WrapperDescriptorDunderGet) => { Type::WrapperDescriptorDunderGet => {
// Here, we also model `types.FunctionType.__get__`, but now we consider a call to // Here, we also model `types.FunctionType.__get__`, but now we consider a call to
// this as a function, i.e. we also expect the `self` argument to be passed in. // this as a function, i.e. we also expect the `self` argument to be passed in.
@ -2974,6 +2996,9 @@ impl<'db> Type<'db> {
| Type::BytesLiteral(_) | Type::BytesLiteral(_)
| Type::FunctionLiteral(_) | Type::FunctionLiteral(_)
| Type::Callable(..) | Type::Callable(..)
| Type::MethodWrapperDunderGet(_)
| Type::BoundMethod(_)
| Type::WrapperDescriptorDunderGet
| Type::Instance(_) | Type::Instance(_)
| Type::KnownInstance(_) | Type::KnownInstance(_)
| Type::ModuleLiteral(_) | Type::ModuleLiteral(_)
@ -3035,6 +3060,9 @@ impl<'db> Type<'db> {
| Type::StringLiteral(_) | Type::StringLiteral(_)
| Type::Tuple(_) | Type::Tuple(_)
| Type::Callable(_) | Type::Callable(_)
| Type::BoundMethod(_)
| Type::WrapperDescriptorDunderGet
| Type::MethodWrapperDunderGet(_)
| Type::Never | Type::Never
| Type::FunctionLiteral(_) => Err(InvalidTypeExpressionError { | Type::FunctionLiteral(_) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType(*self)], invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType(*self)],
@ -3069,9 +3097,7 @@ impl<'db> Type<'db> {
KnownInstanceType::TypeVar(_) => Ok(*self), KnownInstanceType::TypeVar(_) => Ok(*self),
// TODO: Use an opt-in rule for a bare `Callable` // TODO: Use an opt-in rule for a bare `Callable`
KnownInstanceType::Callable => Ok(Type::Callable(CallableType::General( KnownInstanceType::Callable => Ok(Type::Callable(CallableType::unknown(db))),
GeneralCallableType::unknown(db),
))),
KnownInstanceType::TypingSelf => Ok(todo_type!("Support for `typing.Self`")), KnownInstanceType::TypingSelf => Ok(todo_type!("Support for `typing.Self`")),
KnownInstanceType::TypeAlias => Ok(todo_type!("Support for `typing.TypeAlias`")), KnownInstanceType::TypeAlias => Ok(todo_type!("Support for `typing.TypeAlias`")),
@ -3239,16 +3265,12 @@ impl<'db> Type<'db> {
Type::SliceLiteral(_) => KnownClass::Slice.to_class_literal(db), Type::SliceLiteral(_) => KnownClass::Slice.to_class_literal(db),
Type::IntLiteral(_) => KnownClass::Int.to_class_literal(db), Type::IntLiteral(_) => KnownClass::Int.to_class_literal(db),
Type::FunctionLiteral(_) => KnownClass::FunctionType.to_class_literal(db), Type::FunctionLiteral(_) => KnownClass::FunctionType.to_class_literal(db),
Type::Callable(CallableType::BoundMethod(_)) => { Type::BoundMethod(_) => KnownClass::MethodType.to_class_literal(db),
KnownClass::MethodType.to_class_literal(db) Type::MethodWrapperDunderGet(_) => KnownClass::MethodWrapperType.to_class_literal(db),
} Type::WrapperDescriptorDunderGet => {
Type::Callable(CallableType::MethodWrapperDunderGet(_)) => {
KnownClass::MethodWrapperType.to_class_literal(db)
}
Type::Callable(CallableType::WrapperDescriptorDunderGet) => {
KnownClass::WrapperDescriptorType.to_class_literal(db) KnownClass::WrapperDescriptorType.to_class_literal(db)
} }
Type::Callable(CallableType::General(_)) => KnownClass::Type.to_instance(db), Type::Callable(_) => KnownClass::Type.to_instance(db),
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db), Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db),
Type::Tuple(_) => KnownClass::Tuple.to_class_literal(db), Type::Tuple(_) => KnownClass::Tuple.to_class_literal(db),
Type::ClassLiteral(ClassLiteralType { class }) => class.metaclass(db), Type::ClassLiteral(ClassLiteralType { class }) => class.metaclass(db),
@ -4204,10 +4226,7 @@ impl<'db> FunctionType<'db> {
/// ///
/// This powers the `CallableTypeOf` special form from the `knot_extensions` module. /// This powers the `CallableTypeOf` special form from the `knot_extensions` module.
pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> { pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> {
Type::Callable(CallableType::General(GeneralCallableType::new( Type::Callable(CallableType::new(db, self.signature(db).clone()))
db,
self.signature(db).clone(),
)))
} }
/// Typed externally-visible signature for this function. /// Typed externally-visible signature for this function.
@ -4387,23 +4406,38 @@ pub struct BoundMethodType<'db> {
self_instance: Type<'db>, self_instance: Type<'db>,
} }
/// This type represents a general callable type that are used to represent `typing.Callable` /// This type represents the set of all callable objects with a certain signature.
/// and `lambda` expressions. /// It can be written in type expressions using `typing.Callable`.
/// `lambda` expressions are inferred directly as `CallableType`s; all function-literal types
/// are subtypes of a `CallableType`.
#[salsa::interned(debug)] #[salsa::interned(debug)]
pub struct GeneralCallableType<'db> { pub struct CallableType<'db> {
#[return_ref] #[return_ref]
signature: Signature<'db>, signature: Signature<'db>,
} }
impl<'db> GeneralCallableType<'db> { impl<'db> CallableType<'db> {
/// Create a general callable type which accepts any parameters and returns an `Unknown` type. /// Create a callable type which accepts any parameters and returns an `Unknown` type.
pub(crate) fn unknown(db: &'db dyn Db) -> Self { pub(crate) fn unknown(db: &'db dyn Db) -> Self {
GeneralCallableType::new( CallableType::new(
db, db,
Signature::new(Parameters::unknown(), Some(Type::unknown())), Signature::new(Parameters::unknown(), Some(Type::unknown())),
) )
} }
fn with_sorted_unions_and_intersections(self, db: &'db dyn Db) -> Self {
let signature = self.signature(db);
let parameters = signature
.parameters()
.iter()
.map(|param| param.clone().with_sorted_unions_and_intersections(db))
.collect();
let return_ty = signature
.return_ty
.map(|return_ty| return_ty.with_sorted_unions_and_intersections(db));
CallableType::new(db, Signature::new(parameters, return_ty))
}
/// Returns `true` if this is a fully static callable type. /// Returns `true` if this is a fully static callable type.
/// ///
/// A callable type is fully static if all of its parameters and return type are fully static /// A callable type is fully static if all of its parameters and return type are fully static
@ -4928,45 +4962,6 @@ impl<'db> GeneralCallableType<'db> {
} }
} }
/// A type that represents callable objects.
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, salsa::Update)]
pub enum CallableType<'db> {
/// Represents a general callable type.
General(GeneralCallableType<'db>),
/// Represents a callable `instance.method` where `instance` is an instance of a class
/// and `method` is a method (of that class).
///
/// See [`BoundMethodType`] for more information.
///
/// TODO: This could eventually be replaced by a more general `Callable` type, if we
/// decide to bind the first argument of method calls early, i.e. if we have a method
/// `def f(self, x: int) -> str`, and see it being called as `instance.f`, we could
/// partially apply (and check) the `instance` argument against the `self` parameter,
/// and return a `Callable[[int], str]`. One drawback would be that we could not show
/// the bound instance when that type is displayed.
BoundMethod(BoundMethodType<'db>),
/// Represents the callable `f.__get__` where `f` is a function.
///
/// TODO: This could eventually be replaced by a more general `Callable` type that is
/// also able to represent overloads. It would need to represent the two overloads of
/// `types.FunctionType.__get__`:
///
/// ```txt
/// * (None, type) -> Literal[function_on_which_it_was_called]
/// * (object, type | None) -> BoundMethod[instance, function_on_which_it_was_called]
/// ```
MethodWrapperDunderGet(FunctionType<'db>),
/// Represents the callable `FunctionType.__get__`.
///
/// TODO: Similar to above, this could eventually be replaced by a generic `Callable`
/// type. We currently add this as a separate variant because `FunctionType.__get__`
/// is an overloaded method and we do not support `@overload` yet.
WrapperDescriptorDunderGet,
}
#[salsa::interned(debug)] #[salsa::interned(debug)]
pub struct ModuleLiteralType<'db> { pub struct ModuleLiteralType<'db> {
/// The file in which this module was imported. /// The file in which this module was imported.

View File

@ -18,8 +18,8 @@ use crate::types::diagnostic::{
}; };
use crate::types::signatures::{Parameter, ParameterForm}; use crate::types::signatures::{Parameter, ParameterForm};
use crate::types::{ use crate::types::{
todo_type, BoundMethodType, CallableType, ClassLiteralType, KnownClass, KnownFunction, todo_type, BoundMethodType, ClassLiteralType, KnownClass, KnownFunction, KnownInstanceType,
KnownInstanceType, UnionType, UnionType,
}; };
use ruff_db::diagnostic::{OldSecondaryDiagnosticMessage, Span}; use ruff_db::diagnostic::{OldSecondaryDiagnosticMessage, Span};
use ruff_python_ast as ast; use ruff_python_ast as ast;
@ -210,26 +210,22 @@ impl<'db> Bindings<'db> {
}; };
match binding_type { match binding_type {
Type::Callable(CallableType::MethodWrapperDunderGet(function)) => { Type::MethodWrapperDunderGet(function) => {
if function.has_known_class_decorator(db, KnownClass::Classmethod) if function.has_known_class_decorator(db, KnownClass::Classmethod)
&& function.decorators(db).len() == 1 && function.decorators(db).len() == 1
{ {
match overload.parameter_types() { match overload.parameter_types() {
[_, Some(owner)] => { [_, Some(owner)] => {
overload.set_return_type(Type::Callable( overload.set_return_type(Type::BoundMethod(BoundMethodType::new(
CallableType::BoundMethod(BoundMethodType::new( db, function, *owner,
db, function, *owner, )));
)),
));
} }
[Some(instance), None] => { [Some(instance), None] => {
overload.set_return_type(Type::Callable( overload.set_return_type(Type::BoundMethod(BoundMethodType::new(
CallableType::BoundMethod(BoundMethodType::new( db,
db, function,
function, instance.to_meta_type(db),
instance.to_meta_type(db), )));
)),
));
} }
_ => {} _ => {}
} }
@ -237,14 +233,14 @@ impl<'db> Bindings<'db> {
if first.is_none(db) { if first.is_none(db) {
overload.set_return_type(Type::FunctionLiteral(function)); overload.set_return_type(Type::FunctionLiteral(function));
} else { } else {
overload.set_return_type(Type::Callable(CallableType::BoundMethod( overload.set_return_type(Type::BoundMethod(BoundMethodType::new(
BoundMethodType::new(db, function, *first), db, function, *first,
))); )));
} }
} }
} }
Type::Callable(CallableType::WrapperDescriptorDunderGet) => { Type::WrapperDescriptorDunderGet => {
if let [Some(function_ty @ Type::FunctionLiteral(function)), ..] = if let [Some(function_ty @ Type::FunctionLiteral(function)), ..] =
overload.parameter_types() overload.parameter_types()
{ {
@ -253,20 +249,18 @@ impl<'db> Bindings<'db> {
{ {
match overload.parameter_types() { match overload.parameter_types() {
[_, _, Some(owner)] => { [_, _, Some(owner)] => {
overload.set_return_type(Type::Callable( overload.set_return_type(Type::BoundMethod(
CallableType::BoundMethod(BoundMethodType::new( BoundMethodType::new(db, *function, *owner),
db, *function, *owner,
)),
)); ));
} }
[_, Some(instance), None] => { [_, Some(instance), None] => {
overload.set_return_type(Type::Callable( overload.set_return_type(Type::BoundMethod(
CallableType::BoundMethod(BoundMethodType::new( BoundMethodType::new(
db, db,
*function, *function,
instance.to_meta_type(db), instance.to_meta_type(db),
)), ),
)); ));
} }
@ -308,10 +302,8 @@ impl<'db> Bindings<'db> {
} }
[_, Some(instance), _] => { [_, Some(instance), _] => {
overload.set_return_type(Type::Callable( overload.set_return_type(Type::BoundMethod(
CallableType::BoundMethod(BoundMethodType::new( BoundMethodType::new(db, *function, *instance),
db, *function, *instance,
)),
)); ));
} }
@ -935,17 +927,15 @@ impl<'db> CallableDescription<'db> {
kind: "class", kind: "class",
name: class_type.class().name(db), name: class_type.class().name(db),
}), }),
Type::Callable(CallableType::BoundMethod(bound_method)) => Some(CallableDescription { Type::BoundMethod(bound_method) => Some(CallableDescription {
kind: "bound method", kind: "bound method",
name: bound_method.function(db).name(db), name: bound_method.function(db).name(db),
}), }),
Type::Callable(CallableType::MethodWrapperDunderGet(function)) => { Type::MethodWrapperDunderGet(function) => Some(CallableDescription {
Some(CallableDescription { kind: "method wrapper `__get__` of function",
kind: "method wrapper `__get__` of function", name: function.name(db),
name: function.name(db), }),
}) Type::WrapperDescriptorDunderGet => Some(CallableDescription {
}
Type::Callable(CallableType::WrapperDescriptorDunderGet) => Some(CallableDescription {
kind: "wrapper descriptor", kind: "wrapper descriptor",
name: "FunctionType.__get__", name: "FunctionType.__get__",
}), }),
@ -1061,13 +1051,11 @@ impl<'db> BindingError<'db> {
None None
} }
} }
Type::Callable(CallableType::BoundMethod(bound_method)) => { Type::BoundMethod(bound_method) => Self::parameter_span_from_index(
Self::parameter_span_from_index( db,
db, Type::FunctionLiteral(bound_method.function(db)),
Type::FunctionLiteral(bound_method.function(db)), parameter_index,
parameter_index, ),
)
}
_ => None, _ => None,
} }
} }

View File

@ -73,6 +73,9 @@ impl<'db> ClassBase<'db> {
| Type::BooleanLiteral(_) | Type::BooleanLiteral(_)
| Type::FunctionLiteral(_) | Type::FunctionLiteral(_)
| Type::Callable(..) | Type::Callable(..)
| Type::BoundMethod(_)
| Type::MethodWrapperDunderGet(_)
| Type::WrapperDescriptorDunderGet
| Type::BytesLiteral(_) | Type::BytesLiteral(_)
| Type::IntLiteral(_) | Type::IntLiteral(_)
| Type::StringLiteral(_) | Type::StringLiteral(_)

View File

@ -9,8 +9,8 @@ use ruff_python_literal::escape::AsciiEscape;
use crate::types::class_base::ClassBase; use crate::types::class_base::ClassBase;
use crate::types::signatures::{Parameter, Parameters, Signature}; use crate::types::signatures::{Parameter, Parameters, Signature};
use crate::types::{ use crate::types::{
CallableType, ClassLiteralType, InstanceType, IntersectionType, KnownClass, StringLiteralType, ClassLiteralType, InstanceType, IntersectionType, KnownClass, StringLiteralType, Type,
Type, UnionType, UnionType,
}; };
use crate::Db; use crate::Db;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
@ -90,10 +90,8 @@ impl Display for DisplayRepresentation<'_> {
}, },
Type::KnownInstance(known_instance) => f.write_str(known_instance.repr(self.db)), Type::KnownInstance(known_instance) => f.write_str(known_instance.repr(self.db)),
Type::FunctionLiteral(function) => f.write_str(function.name(self.db)), Type::FunctionLiteral(function) => f.write_str(function.name(self.db)),
Type::Callable(CallableType::General(callable)) => { Type::Callable(callable) => callable.signature(self.db).display(self.db).fmt(f),
callable.signature(self.db).display(self.db).fmt(f) Type::BoundMethod(bound_method) => {
}
Type::Callable(CallableType::BoundMethod(bound_method)) => {
write!( write!(
f, f,
"<bound method `{method}` of `{instance}`>", "<bound method `{method}` of `{instance}`>",
@ -101,14 +99,14 @@ impl Display for DisplayRepresentation<'_> {
instance = bound_method.self_instance(self.db).display(self.db) instance = bound_method.self_instance(self.db).display(self.db)
) )
} }
Type::Callable(CallableType::MethodWrapperDunderGet(function)) => { Type::MethodWrapperDunderGet(function) => {
write!( write!(
f, f,
"<method-wrapper `__get__` of `{function}`>", "<method-wrapper `__get__` of `{function}`>",
function = function.name(self.db) function = function.name(self.db)
) )
} }
Type::Callable(CallableType::WrapperDescriptorDunderGet) => { Type::WrapperDescriptorDunderGet => {
f.write_str("<wrapper-descriptor `__get__` of `function` objects>") f.write_str("<wrapper-descriptor `__get__` of `function` objects>")
} }
Type::Union(union) => union.display(self.db).fmt(f), Type::Union(union) => union.display(self.db).fmt(f),
@ -423,9 +421,7 @@ struct DisplayMaybeParenthesizedType<'db> {
impl Display for DisplayMaybeParenthesizedType<'_> { impl Display for DisplayMaybeParenthesizedType<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if let Type::Callable(CallableType::General(_) | CallableType::MethodWrapperDunderGet(_)) = if let Type::Callable(_) | Type::MethodWrapperDunderGet(_) = self.ty {
self.ty
{
write!(f, "({})", self.ty.display(self.db)) write!(f, "({})", self.ty.display(self.db))
} else { } else {
self.ty.display(self.db).fmt(f) self.ty.display(self.db).fmt(f)

View File

@ -82,7 +82,7 @@ use crate::types::{
Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers, TypeArrayDisplay,
TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType,
}; };
use crate::types::{CallableType, GeneralCallableType, Signature}; use crate::types::{CallableType, Signature};
use crate::unpack::{Unpack, UnpackPosition}; use crate::unpack::{Unpack, UnpackPosition};
use crate::util::subscript::{PyIndex, PySlice}; use crate::util::subscript::{PyIndex, PySlice};
use crate::Db; use crate::Db;
@ -2313,6 +2313,9 @@ impl<'db> TypeInferenceBuilder<'db> {
| Type::KnownInstance(..) | Type::KnownInstance(..)
| Type::FunctionLiteral(..) | Type::FunctionLiteral(..)
| Type::Callable(..) | Type::Callable(..)
| Type::BoundMethod(_)
| Type::MethodWrapperDunderGet(_)
| Type::WrapperDescriptorDunderGet
| Type::AlwaysTruthy | Type::AlwaysTruthy
| Type::AlwaysFalsy => match object_ty.class_member(db, attribute.into()) { | Type::AlwaysFalsy => match object_ty.class_member(db, attribute.into()) {
meta_attr @ SymbolAndQualifiers { .. } if meta_attr.is_class_var() => { meta_attr @ SymbolAndQualifiers { .. } if meta_attr.is_class_var() => {
@ -3887,10 +3890,10 @@ impl<'db> TypeInferenceBuilder<'db> {
// TODO: Useful inference of a lambda's return type will require a different approach, // TODO: Useful inference of a lambda's return type will require a different approach,
// which does the inference of the body expression based on arguments at each call site, // which does the inference of the body expression based on arguments at each call site,
// rather than eagerly computing a return type without knowing the argument types. // rather than eagerly computing a return type without knowing the argument types.
Type::Callable(CallableType::General(GeneralCallableType::new( Type::Callable(CallableType::new(
self.db(), self.db(),
Signature::new(parameters, Some(Type::unknown())), Signature::new(parameters, Some(Type::unknown())),
))) ))
} }
fn infer_call_expression(&mut self, call_expression: &ast::ExprCall) -> Type<'db> { fn infer_call_expression(&mut self, call_expression: &ast::ExprCall) -> Type<'db> {
@ -4410,6 +4413,9 @@ impl<'db> TypeInferenceBuilder<'db> {
op @ (ast::UnaryOp::UAdd | ast::UnaryOp::USub | ast::UnaryOp::Invert), op @ (ast::UnaryOp::UAdd | ast::UnaryOp::USub | ast::UnaryOp::Invert),
Type::FunctionLiteral(_) Type::FunctionLiteral(_)
| Type::Callable(..) | Type::Callable(..)
| Type::WrapperDescriptorDunderGet
| Type::MethodWrapperDunderGet(_)
| Type::BoundMethod(_)
| Type::ModuleLiteral(_) | Type::ModuleLiteral(_)
| Type::ClassLiteral(_) | Type::ClassLiteral(_)
| Type::SubclassOf(_) | Type::SubclassOf(_)
@ -4658,6 +4664,9 @@ impl<'db> TypeInferenceBuilder<'db> {
( (
Type::FunctionLiteral(_) Type::FunctionLiteral(_)
| Type::Callable(..) | Type::Callable(..)
| Type::BoundMethod(_)
| Type::WrapperDescriptorDunderGet
| Type::MethodWrapperDunderGet(_)
| Type::ModuleLiteral(_) | Type::ModuleLiteral(_)
| Type::ClassLiteral(_) | Type::ClassLiteral(_)
| Type::SubclassOf(_) | Type::SubclassOf(_)
@ -4674,6 +4683,9 @@ impl<'db> TypeInferenceBuilder<'db> {
| Type::Tuple(_), | Type::Tuple(_),
Type::FunctionLiteral(_) Type::FunctionLiteral(_)
| Type::Callable(..) | Type::Callable(..)
| Type::BoundMethod(_)
| Type::WrapperDescriptorDunderGet
| Type::MethodWrapperDunderGet(_)
| Type::ModuleLiteral(_) | Type::ModuleLiteral(_)
| Type::ClassLiteral(_) | Type::ClassLiteral(_)
| Type::SubclassOf(_) | Type::SubclassOf(_)
@ -6744,12 +6756,12 @@ impl<'db> TypeInferenceBuilder<'db> {
let callable_type = if let (Some(parameters), Some(return_type), true) = let callable_type = if let (Some(parameters), Some(return_type), true) =
(parameters, return_type, correct_argument_number) (parameters, return_type, correct_argument_number)
{ {
GeneralCallableType::new(db, Signature::new(parameters, Some(return_type))) CallableType::new(db, Signature::new(parameters, Some(return_type)))
} else { } else {
GeneralCallableType::unknown(db) CallableType::unknown(db)
}; };
let callable_type = Type::Callable(CallableType::General(callable_type)); let callable_type = Type::Callable(callable_type);
// `Signature` / `Parameters` are not a `Type` variant, so we're storing // `Signature` / `Parameters` are not a `Type` variant, so we're storing
// the outer callable type on the these expressions instead. // the outer callable type on the these expressions instead.
@ -6839,10 +6851,7 @@ impl<'db> TypeInferenceBuilder<'db> {
return Type::unknown(); return Type::unknown();
}; };
Type::Callable(CallableType::General(GeneralCallableType::new( Type::Callable(CallableType::new(db, signature.clone()))
db,
signature.clone(),
)))
} }
}, },

View File

@ -1,8 +1,8 @@
use crate::db::tests::TestDb; use crate::db::tests::TestDb;
use crate::symbol::{builtins_symbol, known_module_symbol}; use crate::symbol::{builtins_symbol, known_module_symbol};
use crate::types::{ use crate::types::{
BoundMethodType, CallableType, IntersectionBuilder, KnownClass, KnownInstanceType, BoundMethodType, IntersectionBuilder, KnownClass, KnownInstanceType, SubclassOfType, TupleType,
SubclassOfType, TupleType, Type, UnionType, Type, UnionType,
}; };
use crate::{Db, KnownModule}; use crate::{Db, KnownModule};
use quickcheck::{Arbitrary, Gen}; use quickcheck::{Arbitrary, Gen};
@ -53,11 +53,11 @@ fn create_bound_method<'db>(
function: Type<'db>, function: Type<'db>,
builtins_class: Type<'db>, builtins_class: Type<'db>,
) -> Type<'db> { ) -> Type<'db> {
Type::Callable(CallableType::BoundMethod(BoundMethodType::new( Type::BoundMethod(BoundMethodType::new(
db, db,
function.expect_function_literal(), function.expect_function_literal(),
builtins_class.to_instance(db).unwrap(), builtins_class.to_instance(db).unwrap(),
))) ))
} }
impl Ty { impl Ty {

View File

@ -504,6 +504,12 @@ impl<'db, 'a> IntoIterator for &'a Parameters<'db> {
} }
} }
impl<'db> FromIterator<Parameter<'db>> for Parameters<'db> {
fn from_iter<T: IntoIterator<Item = Parameter<'db>>>(iter: T) -> Self {
Self::new(iter)
}
}
impl<'db> std::ops::Index<usize> for Parameters<'db> { impl<'db> std::ops::Index<usize> for Parameters<'db> {
type Output = Parameter<'db>; type Output = Parameter<'db>;
@ -593,6 +599,33 @@ impl<'db> Parameter<'db> {
self self
} }
pub(crate) fn with_sorted_unions_and_intersections(mut self, db: &'db dyn Db) -> Self {
self.annotated_type = self
.annotated_type
.map(|ty| ty.with_sorted_unions_and_intersections(db));
self.kind = match self.kind {
ParameterKind::PositionalOnly { name, default_type } => ParameterKind::PositionalOnly {
name,
default_type: default_type.map(|ty| ty.with_sorted_unions_and_intersections(db)),
},
ParameterKind::PositionalOrKeyword { name, default_type } => {
ParameterKind::PositionalOrKeyword {
name,
default_type: default_type
.map(|ty| ty.with_sorted_unions_and_intersections(db)),
}
}
ParameterKind::KeywordOnly { name, default_type } => ParameterKind::KeywordOnly {
name,
default_type: default_type.map(|ty| ty.with_sorted_unions_and_intersections(db)),
},
ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => self.kind,
};
self
}
fn from_node_and_kind( fn from_node_and_kind(
db: &'db dyn Db, db: &'db dyn Db,
definition: Definition<'db>, definition: Definition<'db>,

View File

@ -1,7 +1,6 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use crate::db::Db; use crate::db::Db;
use crate::types::CallableType;
use super::{ use super::{
class_base::ClassBase, ClassLiteralType, DynamicType, InstanceType, KnownInstanceType, class_base::ClassBase, ClassLiteralType, DynamicType, InstanceType, KnownInstanceType,
@ -62,32 +61,22 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(Type::FunctionLiteral(_), _) => Ordering::Less, (Type::FunctionLiteral(_), _) => Ordering::Less,
(_, Type::FunctionLiteral(_)) => Ordering::Greater, (_, Type::FunctionLiteral(_)) => Ordering::Greater,
( (Type::BoundMethod(left), Type::BoundMethod(right)) => left.cmp(right),
Type::Callable(CallableType::BoundMethod(left)), (Type::BoundMethod(_), _) => Ordering::Less,
Type::Callable(CallableType::BoundMethod(right)), (_, Type::BoundMethod(_)) => Ordering::Greater,
) => left.cmp(right),
(Type::Callable(CallableType::BoundMethod(_)), _) => Ordering::Less,
(_, Type::Callable(CallableType::BoundMethod(_))) => Ordering::Greater,
( (Type::MethodWrapperDunderGet(left), Type::MethodWrapperDunderGet(right)) => {
Type::Callable(CallableType::MethodWrapperDunderGet(left)), left.cmp(right)
Type::Callable(CallableType::MethodWrapperDunderGet(right)),
) => left.cmp(right),
(Type::Callable(CallableType::MethodWrapperDunderGet(_)), _) => Ordering::Less,
(_, Type::Callable(CallableType::MethodWrapperDunderGet(_))) => Ordering::Greater,
(
Type::Callable(CallableType::WrapperDescriptorDunderGet),
Type::Callable(CallableType::WrapperDescriptorDunderGet),
) => Ordering::Equal,
(Type::Callable(CallableType::WrapperDescriptorDunderGet), _) => Ordering::Less,
(_, Type::Callable(CallableType::WrapperDescriptorDunderGet)) => Ordering::Greater,
(Type::Callable(CallableType::General(_)), Type::Callable(CallableType::General(_))) => {
Ordering::Equal
} }
(Type::Callable(CallableType::General(_)), _) => Ordering::Less, (Type::MethodWrapperDunderGet(_), _) => Ordering::Less,
(_, Type::Callable(CallableType::General(_))) => Ordering::Greater, (_, Type::MethodWrapperDunderGet(_)) => Ordering::Greater,
(Type::WrapperDescriptorDunderGet, _) => Ordering::Less,
(_, Type::WrapperDescriptorDunderGet) => Ordering::Greater,
(Type::Callable(left), Type::Callable(right)) => left.cmp(right),
(Type::Callable(_), _) => Ordering::Less,
(_, Type::Callable(_)) => Ordering::Greater,
(Type::Tuple(left), Type::Tuple(right)) => { (Type::Tuple(left), Type::Tuple(right)) => {
debug_assert_eq!(*left, left.with_sorted_unions_and_intersections(db)); debug_assert_eq!(*left, left.with_sorted_unions_and_intersections(db));