intersection of atomic types

This commit is contained in:
Douglas Creager 2025-08-05 14:22:14 -04:00
parent 5e163e2a5e
commit 78e1df037c
2 changed files with 54 additions and 1 deletions

View File

@ -10,7 +10,9 @@ use rustc_hash::FxHashMap;
use crate::Db; use crate::Db;
use crate::types::visitor::TypeVisitor; use crate::types::visitor::TypeVisitor;
use crate::types::{IntersectionBuilder, IntersectionType, Type, TypeVarInstance, UnionType}; use crate::types::{
IntersectionBuilder, IntersectionType, Type, TypeVarInstance, UnionBuilder, UnionType,
};
/// A constraint that the type `s` must be a subtype of the type `t`. Tallying will find all /// A constraint that the type `s` must be a subtype of the type `t`. Tallying will find all
/// substitutions of any type variables in `s` and `t` that ensure that this subtyping holds. /// substitutions of any type variables in `s` and `t` that ensure that this subtyping holds.
@ -182,6 +184,7 @@ impl<'db> ConstraintSetSet<'db> {
Type::Union(union) => { Type::Union(union) => {
// Figure 3, step 6 // Figure 3, step 6
// A union is a subtype of Never only if every element is.
self.visit_union_type(db, union); self.visit_union_type(db, union);
let result = (union.iter(db)) let result = (union.iter(db))
.fold(ConstraintSetSet::always(), |sets, element| { .fold(ConstraintSetSet::always(), |sets, element| {
@ -192,6 +195,9 @@ impl<'db> ConstraintSetSet<'db> {
Type::Intersection(intersection) => { Type::Intersection(intersection) => {
// Figure 3, step 2 // Figure 3, step 2
// If an intersection contains any (positive or negative) top-level type
// variables, extract out and isolate the smallest one (according to our
// arbitrary ordering).
let smallest_positive = let smallest_positive =
find_smallest_typevar(intersection.iter_positive(db)); find_smallest_typevar(intersection.iter_positive(db));
let smallest_negative = let smallest_negative =
@ -226,6 +232,47 @@ impl<'db> ConstraintSetSet<'db> {
return; return;
} }
// Figure 3, step 3
// If all (positive and negative) elements of the intersection are atomic
// types (and therefore cannot contain any typevars), fall back on an
// assignability check: if the intersection of the positive elements is
// assignable to the union of the negative elements, then the overall
// intersection is empty.
let mut all_atomic = true;
let mut positive_atomic = IntersectionBuilder::new(db);
let mut negative_atomic = UnionBuilder::new(db);
for positive in intersection.iter_positive(db) {
if !all_atomic {
break;
}
if !positive.is_atomic() {
all_atomic = false;
break;
}
positive_atomic = positive_atomic.add_positive(positive);
}
for negative in intersection.iter_negative(db) {
if !all_atomic {
break;
}
if !negative.is_atomic() {
all_atomic = false;
break;
}
negative_atomic = negative_atomic.add(negative);
}
if all_atomic {
let positive_atomic = positive_atomic.build();
let negative_atomic = negative_atomic.build();
let result = if positive_atomic.is_assignable_to(db, negative_atomic) {
ConstraintSetSet::always()
} else {
ConstraintSetSet::never()
};
self.results.insert(ty, result);
return;
}
// TODO: other intersection clauses // TODO: other intersection clauses
} }

View File

@ -203,6 +203,12 @@ impl<'db> From<Type<'db>> for TypeKind<'db> {
} }
} }
impl<'db> Type<'db> {
pub(crate) fn is_atomic(self) -> bool {
matches!(TypeKind::from(self), TypeKind::Atomic)
}
}
fn walk_non_atomic_type<'db, V: TypeVisitor<'db> + ?Sized>( fn walk_non_atomic_type<'db, V: TypeVisitor<'db> + ?Sized>(
db: &'db dyn Db, db: &'db dyn Db,
non_atomic_type: NonAtomicType<'db>, non_atomic_type: NonAtomicType<'db>,