Skip to content

Commit 3be83d3

Browse files
MatthewMckee4AlexWaygoodcarljm
authored
[ty] Add into_callable method for Type (#19130)
## 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 <[email protected]> Co-authored-by: Carl Meyer <[email protected]>
1 parent 333191b commit 3be83d3

File tree

1 file changed

+74
-50
lines changed

1 file changed

+74
-50
lines changed

crates/ty_python_semantic/src/types.rs

Lines changed: 74 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,6 +1095,74 @@ impl<'db> Type<'db> {
10951095
}
10961096
}
10971097

1098+
pub(crate) fn into_callable(self, db: &'db dyn Db) -> Option<Type<'db>> {
1099+
match self {
1100+
Type::Callable(_) => Some(self),
1101+
1102+
Type::Dynamic(_) => Some(CallableType::single(db, Signature::dynamic(self))),
1103+
1104+
Type::FunctionLiteral(function_literal) => {
1105+
Some(function_literal.into_callable_type(db))
1106+
}
1107+
Type::BoundMethod(bound_method) => Some(bound_method.into_callable_type(db)),
1108+
1109+
Type::NominalInstance(_) | Type::ProtocolInstance(_) => {
1110+
let call_symbol = self
1111+
.member_lookup_with_policy(
1112+
db,
1113+
Name::new_static("__call__"),
1114+
MemberLookupPolicy::NO_INSTANCE_FALLBACK,
1115+
)
1116+
.place;
1117+
1118+
if let Place::Type(ty, Boundness::Bound) = call_symbol {
1119+
ty.into_callable(db)
1120+
} else {
1121+
None
1122+
}
1123+
}
1124+
Type::ClassLiteral(class_literal) => {
1125+
Some(ClassType::NonGeneric(class_literal).into_callable(db))
1126+
}
1127+
1128+
Type::GenericAlias(alias) => Some(ClassType::Generic(alias).into_callable(db)),
1129+
1130+
// TODO: This is unsound so in future we can consider an opt-in option to disable it.
1131+
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
1132+
SubclassOfInner::Class(class) => Some(class.into_callable(db)),
1133+
SubclassOfInner::Dynamic(dynamic) => Some(CallableType::single(
1134+
db,
1135+
Signature::new(Parameters::unknown(), Some(Type::Dynamic(dynamic))),
1136+
)),
1137+
},
1138+
1139+
Type::Union(union) => union.try_map(db, |element| element.into_callable(db)),
1140+
1141+
Type::Never
1142+
| Type::DataclassTransformer(_)
1143+
| Type::AlwaysTruthy
1144+
| Type::AlwaysFalsy
1145+
| Type::IntLiteral(_)
1146+
| Type::BooleanLiteral(_)
1147+
| Type::StringLiteral(_)
1148+
| Type::LiteralString
1149+
| Type::BytesLiteral(_)
1150+
| Type::Tuple(_)
1151+
| Type::TypeIs(_) => None,
1152+
1153+
// TODO
1154+
Type::MethodWrapper(_)
1155+
| Type::WrapperDescriptor(_)
1156+
| Type::DataclassDecorator(_)
1157+
| Type::ModuleLiteral(_)
1158+
| Type::SpecialForm(_)
1159+
| Type::KnownInstance(_)
1160+
| Type::PropertyInstance(_)
1161+
| Type::Intersection(_)
1162+
| Type::TypeVar(_)
1163+
| Type::BoundSuper(_) => None,
1164+
}
1165+
}
10981166
/// Return true if this type is a [subtype of] type `target`.
10991167
///
11001168
/// For fully static types, this means that the set of objects represented by `self` is a
@@ -1305,24 +1373,14 @@ impl<'db> Type<'db> {
13051373
| Type::ModuleLiteral(_),
13061374
) => false,
13071375

1308-
(Type::NominalInstance(_) | Type::ProtocolInstance(_), Type::Callable(_)) => {
1309-
let call_symbol = self
1310-
.member_lookup_with_policy(
1311-
db,
1312-
Name::new_static("__call__"),
1313-
MemberLookupPolicy::NO_INSTANCE_FALLBACK,
1314-
)
1315-
.place;
1316-
// If the type of __call__ is a subtype of a callable type, this instance is.
1317-
// Don't add other special cases here; our subtyping of a callable type
1318-
// shouldn't get out of sync with the calls we will actually allow.
1319-
if let Place::Type(t, Boundness::Bound) = call_symbol {
1320-
t.has_relation_to(db, target, relation)
1321-
} else {
1322-
false
1323-
}
1376+
(Type::Callable(self_callable), Type::Callable(other_callable)) => {
1377+
self_callable.has_relation_to(db, other_callable, relation)
13241378
}
13251379

1380+
(_, Type::Callable(_)) => self
1381+
.into_callable(db)
1382+
.is_some_and(|callable| callable.has_relation_to(db, target, relation)),
1383+
13261384
(Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => {
13271385
left.has_relation_to(db, right, relation)
13281386
}
@@ -1349,16 +1407,6 @@ impl<'db> Type<'db> {
13491407
) => (self.literal_fallback_instance(db))
13501408
.is_some_and(|instance| instance.has_relation_to(db, target, relation)),
13511409

1352-
(Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => {
1353-
self_function_literal
1354-
.into_callable_type(db)
1355-
.has_relation_to(db, target, relation)
1356-
}
1357-
1358-
(Type::BoundMethod(self_bound_method), Type::Callable(_)) => self_bound_method
1359-
.into_callable_type(db)
1360-
.has_relation_to(db, target, relation),
1361-
13621410
// A `FunctionLiteral` type is a single-valued type like the other literals handled above,
13631411
// so it also, for now, just delegates to its instance fallback.
13641412
(Type::FunctionLiteral(_), _) => KnownClass::FunctionType
@@ -1376,10 +1424,6 @@ impl<'db> Type<'db> {
13761424
.to_instance(db)
13771425
.has_relation_to(db, target, relation),
13781426

1379-
(Type::Callable(self_callable), Type::Callable(other_callable)) => {
1380-
self_callable.has_relation_to(db, other_callable, relation)
1381-
}
1382-
13831427
(Type::DataclassDecorator(_) | Type::DataclassTransformer(_), _) => {
13841428
// TODO: Implement subtyping using an equivalent `Callable` type.
13851429
false
@@ -1456,26 +1500,6 @@ impl<'db> Type<'db> {
14561500
self_subclass_ty.has_relation_to(db, target_subclass_ty, relation)
14571501
}
14581502

1459-
(Type::ClassLiteral(class_literal), Type::Callable(_)) => {
1460-
ClassType::NonGeneric(class_literal)
1461-
.into_callable(db)
1462-
.has_relation_to(db, target, relation)
1463-
}
1464-
1465-
(Type::GenericAlias(alias), Type::Callable(_)) => ClassType::Generic(alias)
1466-
.into_callable(db)
1467-
.has_relation_to(db, target, relation),
1468-
1469-
// TODO: This is unsound so in future we can consider an opt-in option to disable it.
1470-
(Type::SubclassOf(subclass_of_ty), Type::Callable(_))
1471-
if subclass_of_ty.subclass_of().into_class().is_some() =>
1472-
{
1473-
let class = subclass_of_ty.subclass_of().into_class().unwrap();
1474-
class
1475-
.into_callable(db)
1476-
.has_relation_to(db, target, relation)
1477-
}
1478-
14791503
// `Literal[str]` is a subtype of `type` because the `str` class object is an instance of its metaclass `type`.
14801504
// `Literal[abc.ABC]` is a subtype of `abc.ABCMeta` because the `abc.ABC` class object
14811505
// is an instance of its metaclass `abc.ABCMeta`.

0 commit comments

Comments
 (0)