[ty] Add into_callable method for Type (#19130)
Some checks are pending
[ty Playground] Release / publish (push) Waiting to run
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / mkdocs (push) Waiting to run
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions

## Summary

Was just playing around with this, there's definitely more to do with
this function, but it seems like maybe a better option than having so
many arms in has_relation_to for (_, Callable).

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
Matthew Mckee 2025-07-04 03:04:03 +01:00 committed by GitHub
parent 333191b7f7
commit 3be83d36a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1095,6 +1095,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
@ -1305,24 +1373,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)
}
@ -1349,16 +1407,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
@ -1376,10 +1424,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
@ -1456,26 +1500,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`.