Skip to content

Commit 08145db

Browse files
committed
option for no callable
1 parent 1575381 commit 08145db

File tree

7 files changed

+89
-84
lines changed

7 files changed

+89
-84
lines changed

crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -871,12 +871,14 @@ impl ReachabilityConstraints {
871871
return Truthiness::AlwaysFalse.negate_if(!predicate.is_positive);
872872
}
873873

874-
let overloads_iterator =
875-
if let Some(callable) = ty.try_upcast_to_callable(db).exactly_one() {
876-
callable.signatures(db).overloads.iter()
877-
} else {
878-
return Truthiness::AlwaysFalse.negate_if(!predicate.is_positive);
879-
};
874+
let overloads_iterator = if let Some(callable) = ty
875+
.try_upcast_to_callable(db)
876+
.and_then(|callables| callables.exactly_one())
877+
{
878+
callable.signatures(db).overloads.iter()
879+
} else {
880+
return Truthiness::AlwaysFalse.negate_if(!predicate.is_positive);
881+
};
880882

881883
let (no_overloads_return_never, all_overloads_return_never) = overloads_iterator
882884
.fold((true, true), |(none, all), overload| {

crates/ty_python_semantic/src/types.rs

Lines changed: 44 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,20 +1525,20 @@ impl<'db> Type<'db> {
15251525
}
15261526
}
15271527

1528-
pub(crate) fn try_upcast_to_callable(self, db: &'db dyn Db) -> CallableTypes<'db> {
1528+
pub(crate) fn try_upcast_to_callable(self, db: &'db dyn Db) -> Option<CallableTypes<'db>> {
15291529
match self {
1530-
Type::Callable(callable) => CallableTypes::one(callable),
1530+
Type::Callable(callable) => Some(CallableTypes::one(callable)),
15311531

1532-
Type::Dynamic(_) => CallableTypes::one(CallableType::function_like_callable(
1532+
Type::Dynamic(_) => Some(CallableTypes::one(CallableType::function_like_callable(
15331533
db,
15341534
Signature::dynamic(self),
1535-
)),
1535+
))),
15361536

15371537
Type::FunctionLiteral(function_literal) => {
1538-
CallableTypes::one(function_literal.into_callable_type(db))
1538+
Some(CallableTypes::one(function_literal.into_callable_type(db)))
15391539
}
15401540
Type::BoundMethod(bound_method) => {
1541-
CallableTypes::one(bound_method.into_callable_type(db))
1541+
Some(CallableTypes::one(bound_method.into_callable_type(db)))
15421542
}
15431543

15441544
Type::NominalInstance(_) | Type::ProtocolInstance(_) => {
@@ -1553,7 +1553,7 @@ impl<'db> Type<'db> {
15531553
if let Place::Defined(ty, _, Definedness::AlwaysDefined) = call_symbol {
15541554
ty.try_upcast_to_callable(db)
15551555
} else {
1556-
CallableTypes::none()
1556+
None
15571557
}
15581558
}
15591559
Type::ClassLiteral(class_literal) => {
@@ -1570,23 +1570,20 @@ impl<'db> Type<'db> {
15701570
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
15711571
SubclassOfInner::Class(class) => class.into_callable(db),
15721572
SubclassOfInner::Dynamic(dynamic) => {
1573-
CallableTypes::one(CallableType::single_callable(
1573+
Some(CallableTypes::one(CallableType::single_callable(
15741574
db,
15751575
Signature::new(Parameters::unknown(), Some(Type::Dynamic(dynamic))),
1576-
))
1576+
)))
15771577
}
15781578
},
15791579

15801580
Type::Union(union) => {
15811581
let mut callables = SmallVec::new();
15821582
for element in union.elements(db) {
1583-
let element_callable = element.try_upcast_to_callable(db);
1584-
if element_callable.is_empty() {
1585-
return CallableTypes::none();
1586-
}
1583+
let element_callable = element.try_upcast_to_callable(db)?;
15871584
callables.extend(element_callable.into_inner());
15881585
}
1589-
CallableTypes(callables)
1586+
Some(CallableTypes(callables))
15901587
}
15911588

15921589
Type::EnumLiteral(enum_literal) => enum_literal
@@ -1595,27 +1592,29 @@ impl<'db> Type<'db> {
15951592

15961593
Type::TypeAlias(alias) => alias.value_type(db).try_upcast_to_callable(db),
15971594

1598-
Type::KnownBoundMethod(method) => CallableTypes::one(CallableType::new(
1595+
Type::KnownBoundMethod(method) => Some(CallableTypes::one(CallableType::new(
15991596
db,
16001597
CallableSignature::from_overloads(method.signatures(db)),
16011598
false,
1602-
)),
1599+
))),
16031600

1604-
Type::WrapperDescriptor(wrapper_descriptor) => CallableTypes::one(CallableType::new(
1605-
db,
1606-
CallableSignature::from_overloads(wrapper_descriptor.signatures(db)),
1607-
false,
1608-
)),
1601+
Type::WrapperDescriptor(wrapper_descriptor) => {
1602+
Some(CallableTypes::one(CallableType::new(
1603+
db,
1604+
CallableSignature::from_overloads(wrapper_descriptor.signatures(db)),
1605+
false,
1606+
)))
1607+
}
16091608

16101609
Type::KnownInstance(KnownInstanceType::NewType(newtype)) => {
1611-
CallableTypes::one(CallableType::single_callable(
1610+
Some(CallableTypes::one(CallableType::single_callable(
16121611
db,
16131612
Signature::new(
16141613
Parameters::new([Parameter::positional_only(None)
16151614
.with_annotated_type(newtype.base(db).instance_type(db))]),
16161615
Some(Type::NewTypeInstance(newtype)),
16171616
),
1618-
))
1617+
)))
16191618
}
16201619

16211620
Type::Never
@@ -1628,7 +1627,7 @@ impl<'db> Type<'db> {
16281627
| Type::LiteralString
16291628
| Type::BytesLiteral(_)
16301629
| Type::TypeIs(_)
1631-
| Type::TypedDict(_) => CallableTypes::none(),
1630+
| Type::TypedDict(_) => None,
16321631

16331632
// TODO
16341633
Type::DataclassDecorator(_)
@@ -1638,7 +1637,7 @@ impl<'db> Type<'db> {
16381637
| Type::PropertyInstance(_)
16391638
| Type::Intersection(_)
16401639
| Type::TypeVar(_)
1641-
| Type::BoundSuper(_) => CallableTypes::none(),
1640+
| Type::BoundSuper(_) => None,
16421641
}
16431642
}
16441643

@@ -2194,14 +2193,16 @@ impl<'db> Type<'db> {
21942193

21952194
(_, Type::Callable(other_callable)) => {
21962195
relation_visitor.visit((self, target, relation), || {
2197-
self.try_upcast_to_callable(db).has_relation_to_impl(
2198-
db,
2199-
other_callable,
2200-
inferable,
2201-
relation,
2202-
relation_visitor,
2203-
disjointness_visitor,
2204-
)
2196+
self.try_upcast_to_callable(db).when_some_and(|callables| {
2197+
callables.has_relation_to_impl(
2198+
db,
2199+
other_callable,
2200+
inferable,
2201+
relation,
2202+
relation_visitor,
2203+
disjointness_visitor,
2204+
)
2205+
})
22052206
})
22062207
}
22072208

@@ -11110,20 +11111,21 @@ impl<'db> CallableType<'db> {
1111011111
/// when coercing that result to a single type, you'll get a `UnionType`. But this lets you handle
1111111112
/// that result as a list of `CallableType`s before merging them into a `UnionType` should that be
1111211113
/// helpful.
11114+
///
11115+
/// Note that this type is guaranteed to contain at least one callable. If you need to support "no
11116+
/// callables" as a possibility, use `Option<CallableTypes>`.
1111311117
#[derive(Clone, Debug, Eq, PartialEq, get_size2::GetSize, salsa::Update)]
1111411118
pub(crate) struct CallableTypes<'db>(SmallVec<[CallableType<'db>; 1]>);
1111511119

1111611120
impl<'db> CallableTypes<'db> {
11117-
pub(crate) fn none() -> Self {
11118-
CallableTypes(smallvec![])
11119-
}
11120-
1112111121
pub(crate) fn one(callable: CallableType<'db>) -> Self {
1112211122
CallableTypes(smallvec![callable])
1112311123
}
1112411124

1112511125
pub(crate) fn from_elements(callables: impl IntoIterator<Item = CallableType<'db>>) -> Self {
11126-
CallableTypes(callables.into_iter().collect())
11126+
let callables: SmallVec<_> = callables.into_iter().collect();
11127+
assert!(!callables.is_empty(), "CallableTypes should not be empty");
11128+
CallableTypes(callables)
1112711129
}
1112811130

1112911131
pub(crate) fn exactly_one(self) -> Option<CallableType<'db>> {
@@ -11133,22 +11135,15 @@ impl<'db> CallableTypes<'db> {
1113311135
}
1113411136
}
1113511137

11136-
fn is_empty(&self) -> bool {
11137-
self.0.is_empty()
11138-
}
11139-
1114011138
fn into_inner(self) -> SmallVec<[CallableType<'db>; 1]> {
1114111139
self.0
1114211140
}
1114311141

11144-
pub(crate) fn into_type(self, db: &'db dyn Db) -> Option<Type<'db>> {
11142+
pub(crate) fn into_type(self, db: &'db dyn Db) -> Type<'db> {
1114511143
match self.0.as_slice() {
11146-
[] => None,
11147-
[single] => Some(Type::Callable(*single)),
11148-
slice => Some(UnionType::from_elements(
11149-
db,
11150-
slice.iter().copied().map(Type::Callable),
11151-
)),
11144+
[] => unreachable!("CallableTypes should not be empty"),
11145+
[single] => Type::Callable(*single),
11146+
slice => UnionType::from_elements(db, slice.iter().copied().map(Type::Callable)),
1115211147
}
1115311148
}
1115411149

@@ -11165,9 +11160,6 @@ impl<'db> CallableTypes<'db> {
1116511160
relation_visitor: &HasRelationToVisitor<'db>,
1116611161
disjointness_visitor: &IsDisjointVisitor<'db>,
1116711162
) -> ConstraintSet<'db> {
11168-
if self.is_empty() {
11169-
return ConstraintSet::from(false);
11170-
}
1117111163
self.0.iter().when_all(db, |element| {
1117211164
element.has_relation_to_impl(
1117311165
db,

crates/ty_python_semantic/src/types/class.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,7 +1051,7 @@ impl<'db> ClassType<'db> {
10511051
/// Return a callable type (or union of callable types) that represents the callable
10521052
/// constructor signature of this class.
10531053
#[salsa::tracked(cycle_initial=into_callable_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
1054-
pub(super) fn into_callable(self, db: &'db dyn Db) -> CallableTypes<'db> {
1054+
pub(super) fn into_callable(self, db: &'db dyn Db) -> Option<CallableTypes<'db>> {
10551055
let self_ty = Type::from(self);
10561056
let metaclass_dunder_call_function_symbol = self_ty
10571057
.member_lookup_with_policy(
@@ -1069,7 +1069,9 @@ impl<'db> ClassType<'db> {
10691069
// https://typing.python.org/en/latest/spec/constructors.html#converting-a-constructor-to-callable
10701070
// by always respecting the signature of the metaclass `__call__`, rather than
10711071
// using a heuristic which makes unwarranted assumptions to sometimes ignore it.
1072-
return CallableTypes::one(metaclass_dunder_call_function.into_callable_type(db));
1072+
return Some(CallableTypes::one(
1073+
metaclass_dunder_call_function.into_callable_type(db),
1074+
));
10731075
}
10741076

10751077
let dunder_new_function_symbol = self_ty.lookup_dunder_new(db);
@@ -1104,7 +1106,7 @@ impl<'db> ClassType<'db> {
11041106
);
11051107

11061108
if returns_non_subclass {
1107-
return CallableTypes::one(dunder_new_bound_method);
1109+
return Some(CallableTypes::one(dunder_new_bound_method));
11081110
}
11091111
Some(dunder_new_bound_method)
11101112
} else {
@@ -1161,13 +1163,13 @@ impl<'db> ClassType<'db> {
11611163

11621164
match (dunder_new_function, synthesized_dunder_init_callable) {
11631165
(Some(dunder_new_function), Some(synthesized_dunder_init_callable)) => {
1164-
CallableTypes::from_elements([
1166+
Some(CallableTypes::from_elements([
11651167
dunder_new_function,
11661168
synthesized_dunder_init_callable,
1167-
])
1169+
]))
11681170
}
11691171
(Some(constructor), None) | (None, Some(constructor)) => {
1170-
CallableTypes::one(constructor)
1172+
Some(CallableTypes::one(constructor))
11711173
}
11721174
(None, None) => {
11731175
// If no `__new__` or `__init__` method is found, then we fall back to looking for
@@ -1183,17 +1185,17 @@ impl<'db> ClassType<'db> {
11831185
if let Place::Defined(Type::FunctionLiteral(new_function), _, _) =
11841186
new_function_symbol
11851187
{
1186-
CallableTypes::one(
1188+
Some(CallableTypes::one(
11871189
new_function
11881190
.into_bound_method_type(db, correct_return_type)
11891191
.into_callable_type(db),
1190-
)
1192+
))
11911193
} else {
11921194
// Fallback if no `object.__new__` is found.
1193-
CallableTypes::one(CallableType::single_callable(
1195+
Some(CallableTypes::one(CallableType::single_callable(
11941196
db,
11951197
Signature::new(Parameters::empty(), Some(correct_return_type)),
1196-
))
1198+
)))
11971199
}
11981200
}
11991201
}
@@ -1212,8 +1214,8 @@ fn into_callable_cycle_initial<'db>(
12121214
_db: &'db dyn Db,
12131215
_id: salsa::Id,
12141216
_self: ClassType<'db>,
1215-
) -> CallableTypes<'db> {
1216-
CallableTypes::none()
1217+
) -> Option<CallableTypes<'db>> {
1218+
None
12171219
}
12181220

12191221
impl<'db> From<GenericAlias<'db>> for ClassType<'db> {

crates/ty_python_semantic/src/types/ide_support.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -876,7 +876,10 @@ pub fn definitions_for_keyword_argument<'db>(
876876

877877
let mut resolved_definitions = Vec::new();
878878

879-
if let Some(callable_type) = func_type.try_upcast_to_callable(db).exactly_one() {
879+
if let Some(callable_type) = func_type
880+
.try_upcast_to_callable(db)
881+
.and_then(|callables| callables.exactly_one())
882+
{
880883
let signatures = callable_type.signatures(db);
881884

882885
// For each signature, find the parameter with the matching name
@@ -987,7 +990,10 @@ pub fn call_signature_details<'db>(
987990
let func_type = call_expr.func.inferred_type(model);
988991

989992
// Use into_callable to handle all the complex type conversions
990-
if let Some(callable_type) = func_type.try_upcast_to_callable(db).into_type(db) {
993+
if let Some(callable_type) = func_type
994+
.try_upcast_to_callable(db)
995+
.map(|callables| callables.into_type(db))
996+
{
991997
let call_arguments =
992998
CallArguments::from_arguments(&call_expr.arguments, |_, splatted_value| {
993999
splatted_value.inferred_type(model)
@@ -1042,7 +1048,7 @@ pub fn call_type_simplified_by_overloads<'db>(
10421048
let func_type = call_expr.func.inferred_type(model);
10431049

10441050
// Use into_callable to handle all the complex type conversions
1045-
let callable_type = func_type.try_upcast_to_callable(db).into_type(db)?;
1051+
let callable_type = func_type.try_upcast_to_callable(db)?.into_type(db);
10461052
let bindings = callable_type.bindings(db);
10471053

10481054
// If the callable is trivial this analysis is useless, bail out

crates/ty_python_semantic/src/types/infer/builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2287,7 +2287,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
22872287

22882288
let is_input_function_like = inferred_ty
22892289
.try_upcast_to_callable(self.db())
2290-
.exactly_one()
2290+
.and_then(|callables| callables.exactly_one())
22912291
.is_some_and(|callable| callable.is_function_like(self.db()));
22922292

22932293
if is_input_function_like

crates/ty_python_semantic/src/types/infer/builder/type_expression.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1229,7 +1229,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
12291229

12301230
let Some(callable_type) = argument_type
12311231
.try_upcast_to_callable(db)
1232-
.into_type(self.db())
1232+
.map(|callables| callables.into_type(self.db()))
12331233
else {
12341234
if let Some(builder) = self
12351235
.context

crates/ty_python_semantic/src/types/protocol_class.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -707,15 +707,18 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
707707
let fallback_other = other.literal_fallback_instance(db).unwrap_or(other);
708708
attribute_type
709709
.try_upcast_to_callable(db)
710-
.map(|callable| callable.apply_self(db, fallback_other))
711-
.has_relation_to_impl(
712-
db,
713-
method.bind_self(db, Some(fallback_other)),
714-
inferable,
715-
relation,
716-
relation_visitor,
717-
disjointness_visitor,
718-
)
710+
.when_some_and(|callables| {
711+
callables
712+
.map(|callable| callable.apply_self(db, fallback_other))
713+
.has_relation_to_impl(
714+
db,
715+
method.bind_self(db, Some(fallback_other)),
716+
inferable,
717+
relation,
718+
relation_visitor,
719+
disjointness_visitor,
720+
)
721+
})
719722
}
720723
// TODO: consider the types of the attribute on `other` for property members
721724
ProtocolMemberKind::Property(_) => ConstraintSet::from(matches!(

0 commit comments

Comments
 (0)