Skip to content

Commit 70ed19d

Browse files
committed
fix disjointness checks on @final classes
1 parent abaa49f commit 70ed19d

File tree

4 files changed

+106
-12
lines changed

4 files changed

+106
-12
lines changed

crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ python-version = "3.12"
9595
```
9696

9797
```py
98-
from typing import final
98+
from typing import Any, final
9999
from ty_extensions import static_assert, is_disjoint_from
100100

101101
@final
@@ -106,10 +106,13 @@ class Foo[T]:
106106
class A: ...
107107
class B: ...
108108

109+
static_assert(not is_disjoint_from(A, B))
109110
static_assert(not is_disjoint_from(Foo[A], Foo[B]))
111+
static_assert(not is_disjoint_from(Foo[A], Foo[Any]))
112+
static_assert(not is_disjoint_from(Foo[Any], Foo[B]))
110113

111-
# TODO: `int` and `str` are disjoint bases, so these should be disjoint.
112-
static_assert(not is_disjoint_from(Foo[int], Foo[str]))
114+
# `int` and `str` are disjoint bases.
115+
static_assert(is_disjoint_from(Foo[int], Foo[str]))
113116
```
114117

115118
## "Disjoint base" builtin types

crates/ty_python_semantic/src/types/class.rs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -716,17 +716,34 @@ impl<'db> ClassType<'db> {
716716
return true;
717717
}
718718

719-
if self.is_final(db) {
720-
return self
721-
.iter_mro(db)
722-
.filter_map(ClassBase::into_class)
723-
.any(|class| class.class_literal(db).0 == other.class_literal(db).0);
724-
}
725-
if other.is_final(db) {
726-
return other
719+
if self.is_final(db) || other.is_final(db) {
720+
let (this, other) = if self.is_final(db) {
721+
(self, other)
722+
} else {
723+
(other, self)
724+
};
725+
726+
return this
727727
.iter_mro(db)
728728
.filter_map(ClassBase::into_class)
729-
.any(|class| class.class_literal(db).0 == self.class_literal(db).0);
729+
.any(|class| match (class, other) {
730+
(ClassType::NonGeneric(this_class), ClassType::NonGeneric(other_class)) => {
731+
this_class == other_class
732+
}
733+
(ClassType::Generic(this_alias), ClassType::Generic(other_alias)) => {
734+
this_alias.origin(db) == other_alias.origin(db)
735+
&& this_alias
736+
.specialization(db)
737+
.is_disjoint_from(
738+
db,
739+
other_alias.specialization(db),
740+
InferableTypeVars::None,
741+
)
742+
.is_never_satisfied(db)
743+
}
744+
(ClassType::NonGeneric(_), ClassType::Generic(_))
745+
| (ClassType::Generic(_), ClassType::NonGeneric(_)) => false,
746+
});
730747
}
731748

732749
// Two disjoint bases can only coexist in an MRO if one is a subclass of the other.

crates/ty_python_semantic/src/types/generics.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,6 +1215,63 @@ impl<'db> Specialization<'db> {
12151215
result
12161216
}
12171217

1218+
pub(crate) fn is_disjoint_from(
1219+
self,
1220+
db: &'db dyn Db,
1221+
other: Self,
1222+
inferable: InferableTypeVars<'_, 'db>,
1223+
) -> ConstraintSet<'db> {
1224+
self.is_disjoint_from_impl(
1225+
db,
1226+
other,
1227+
inferable,
1228+
&IsDisjointVisitor::default(),
1229+
&HasRelationToVisitor::default(),
1230+
)
1231+
}
1232+
1233+
pub(crate) fn is_disjoint_from_impl(
1234+
self,
1235+
db: &'db dyn Db,
1236+
other: Self,
1237+
inferable: InferableTypeVars<'_, 'db>,
1238+
disjointness_visitor: &IsDisjointVisitor<'db>,
1239+
relation_visitor: &HasRelationToVisitor<'db>,
1240+
) -> ConstraintSet<'db> {
1241+
let generic_context = self.generic_context(db);
1242+
if generic_context != other.generic_context(db) {
1243+
return ConstraintSet::from(true);
1244+
}
1245+
1246+
if let (Some(self_tuple), Some(other_tuple)) = (self.tuple_inner(db), other.tuple_inner(db))
1247+
{
1248+
return self_tuple.is_disjoint_from_impl(
1249+
db,
1250+
other_tuple,
1251+
inferable,
1252+
disjointness_visitor,
1253+
relation_visitor,
1254+
);
1255+
}
1256+
1257+
let mut result = ConstraintSet::from(false);
1258+
for (self_type, other_type) in std::iter::zip(self.types(db), other.types(db)) {
1259+
let disjoint = self_type.is_disjoint_from_impl(
1260+
db,
1261+
*other_type,
1262+
inferable,
1263+
disjointness_visitor,
1264+
relation_visitor,
1265+
);
1266+
1267+
if result.union(db, disjoint).is_always_satisfied(db) {
1268+
return result;
1269+
}
1270+
}
1271+
1272+
result
1273+
}
1274+
12181275
pub(crate) fn is_equivalent_to_impl(
12191276
self,
12201277
db: &'db dyn Db,

crates/ty_python_semantic/src/types/tuple.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,23 @@ impl<'db> TupleType<'db> {
287287
)
288288
}
289289

290+
pub(crate) fn is_disjoint_from_impl(
291+
self,
292+
db: &'db dyn Db,
293+
other: Self,
294+
inferable: InferableTypeVars<'_, 'db>,
295+
disjointness_visitor: &IsDisjointVisitor<'db>,
296+
relation_visitor: &HasRelationToVisitor<'db>,
297+
) -> ConstraintSet<'db> {
298+
self.tuple(db).is_disjoint_from_impl(
299+
db,
300+
other.tuple(db),
301+
inferable,
302+
disjointness_visitor,
303+
relation_visitor,
304+
)
305+
}
306+
290307
pub(crate) fn is_equivalent_to_impl(
291308
self,
292309
db: &'db dyn Db,

0 commit comments

Comments
 (0)