Skip to content
124 changes: 74 additions & 50 deletions crates/ty_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1186,6 +1186,74 @@ impl<'db> Type<'db> {
}
}

pub(crate) fn into_callable(self, db: &'db dyn Db) -> Option<Type<'db>> {
match self {
Type::Callable(_) => Some(self),

Type::Dynamic(_) => Some(CallableType::single(db, Signature::dynamic(self))),

Type::FunctionLiteral(function_literal) => {
Some(function_literal.into_callable_type(db))
}
Type::BoundMethod(bound_method) => Some(bound_method.into_callable_type(db)),

Type::NominalInstance(_) | Type::ProtocolInstance(_) => {
let call_symbol = self
.member_lookup_with_policy(
db,
Name::new_static("__call__"),
MemberLookupPolicy::NO_INSTANCE_FALLBACK,
)
.place;

if let Place::Type(ty, Boundness::Bound) = call_symbol {
ty.into_callable(db)
} else {
None
}
}
Type::ClassLiteral(class_literal) => {
Some(ClassType::NonGeneric(class_literal).into_callable(db))
}

Type::GenericAlias(alias) => Some(ClassType::Generic(alias).into_callable(db)),

// TODO: This is unsound so in future we can consider an opt-in option to disable it.
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
SubclassOfInner::Class(class) => Some(class.into_callable(db)),
SubclassOfInner::Dynamic(dynamic) => Some(CallableType::single(
db,
Signature::new(Parameters::unknown(), Some(Type::Dynamic(dynamic))),
)),
},

Type::Union(union) => union.try_map(db, |element| element.into_callable(db)),

Type::Never
| Type::DataclassTransformer(_)
| Type::AlwaysTruthy
| Type::AlwaysFalsy
| Type::IntLiteral(_)
| Type::BooleanLiteral(_)
| Type::StringLiteral(_)
| Type::LiteralString
| Type::BytesLiteral(_)
| Type::Tuple(_)
| Type::TypeIs(_) => None,

// TODO
Type::MethodWrapper(_)
| Type::WrapperDescriptor(_)
| Type::DataclassDecorator(_)
| Type::ModuleLiteral(_)
| Type::SpecialForm(_)
| Type::KnownInstance(_)
| Type::PropertyInstance(_)
| Type::Intersection(_)
| Type::TypeVar(_)
| Type::BoundSuper(_) => None,
}
}
/// Return true if this type is a [subtype of] type `target`.
///
/// For fully static types, this means that the set of objects represented by `self` is a
Expand Down Expand Up @@ -1396,24 +1464,14 @@ impl<'db> Type<'db> {
| Type::ModuleLiteral(_),
) => false,

(Type::NominalInstance(_) | Type::ProtocolInstance(_), Type::Callable(_)) => {
let call_symbol = self
.member_lookup_with_policy(
db,
Name::new_static("__call__"),
MemberLookupPolicy::NO_INSTANCE_FALLBACK,
)
.place;
// If the type of __call__ is a subtype of a callable type, this instance is.
// Don't add other special cases here; our subtyping of a callable type
// shouldn't get out of sync with the calls we will actually allow.
if let Place::Type(t, Boundness::Bound) = call_symbol {
t.has_relation_to(db, target, relation)
} else {
false
}
(Type::Callable(self_callable), Type::Callable(other_callable)) => {
self_callable.has_relation_to(db, other_callable, relation)
}

(_, Type::Callable(_)) => self
.into_callable(db)
.is_some_and(|callable| callable.has_relation_to(db, target, relation)),

(Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => {
left.has_relation_to(db, right, relation)
}
Expand All @@ -1440,16 +1498,6 @@ impl<'db> Type<'db> {
) => (self.literal_fallback_instance(db))
.is_some_and(|instance| instance.has_relation_to(db, target, relation)),

(Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => {
self_function_literal
.into_callable_type(db)
.has_relation_to(db, target, relation)
}

(Type::BoundMethod(self_bound_method), Type::Callable(_)) => self_bound_method
.into_callable_type(db)
.has_relation_to(db, target, relation),

// 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
Expand All @@ -1467,10 +1515,6 @@ impl<'db> Type<'db> {
.to_instance(db)
.has_relation_to(db, target, relation),

(Type::Callable(self_callable), Type::Callable(other_callable)) => {
self_callable.has_relation_to(db, other_callable, relation)
}

(Type::DataclassDecorator(_) | Type::DataclassTransformer(_), _) => {
// TODO: Implement subtyping using an equivalent `Callable` type.
false
Expand Down Expand Up @@ -1547,26 +1591,6 @@ impl<'db> Type<'db> {
self_subclass_ty.has_relation_to(db, target_subclass_ty, relation)
}

(Type::ClassLiteral(class_literal), Type::Callable(_)) => {
ClassType::NonGeneric(class_literal)
.into_callable(db)
.has_relation_to(db, target, relation)
}

(Type::GenericAlias(alias), Type::Callable(_)) => ClassType::Generic(alias)
.into_callable(db)
.has_relation_to(db, target, relation),

// TODO: This is unsound so in future we can consider an opt-in option to disable it.
(Type::SubclassOf(subclass_of_ty), Type::Callable(_))
if subclass_of_ty.subclass_of().into_class().is_some() =>
{
let class = subclass_of_ty.subclass_of().into_class().unwrap();
class
.into_callable(db)
.has_relation_to(db, target, relation)
}

// `Literal[str]` is a subtype of `type` because the `str` class object is an instance of its metaclass `type`.
// `Literal[abc.ABC]` is a subtype of `abc.ABCMeta` because the `abc.ABC` class object
// is an instance of its metaclass `abc.ABCMeta`.
Expand Down
Loading