add ConstraintSetAssignability relation

This commit is contained in:
Douglas Creager 2025-11-26 14:52:41 -05:00
parent 8ec6673230
commit b9691b8a07
4 changed files with 59 additions and 8 deletions

View File

@ -1955,6 +1955,33 @@ impl<'db> Type<'db> {
return constraints.implies_subtype_of(db, self, target);
}
// Handle the new constraint-set-based assignability relation next. Comparisons with a
// typevar are translated directly into a constraint set.
if relation.is_constraint_set_assignability() {
// A typevar satisfies a relation when...it satisfies the relation. Yes that's a
// tautology! We're moving the caller's subtyping/assignability requirement into a
// constraint set. If the typevar has an upper bound or constraints, then the relation
// only has to hold when the typevar has a valid specialization (i.e., one that
// satisfies the upper bound/constraints).
if let Type::TypeVar(bound_typevar) = self {
return ConstraintSet::constrain_typevar(
db,
bound_typevar,
Type::Never,
target,
relation,
);
} else if let Type::TypeVar(bound_typevar) = target {
return ConstraintSet::constrain_typevar(
db,
bound_typevar,
self,
Type::object(),
relation,
);
}
}
match (self, target) {
// Everything is a subtype of `object`.
(_, Type::NominalInstance(instance)) if instance.is_object() => {
@ -2035,7 +2062,7 @@ impl<'db> Type<'db> {
);
ConstraintSet::from(match relation {
TypeRelation::Subtyping | TypeRelation::SubtypingAssuming(_) => false,
TypeRelation::Assignability => true,
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability => true,
TypeRelation::Redundancy => match target {
Type::Dynamic(_) => true,
Type::Union(union) => union.elements(db).iter().any(Type::is_dynamic),
@ -2045,7 +2072,7 @@ impl<'db> Type<'db> {
}
(_, Type::Dynamic(_)) => ConstraintSet::from(match relation {
TypeRelation::Subtyping | TypeRelation::SubtypingAssuming(_) => false,
TypeRelation::Assignability => true,
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability => true,
TypeRelation::Redundancy => match self {
Type::Dynamic(_) => true,
Type::Intersection(intersection) => {
@ -2309,14 +2336,19 @@ impl<'db> Type<'db> {
TypeRelation::Subtyping
| TypeRelation::Redundancy
| TypeRelation::SubtypingAssuming(_) => self,
TypeRelation::Assignability => self.bottom_materialization(db),
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability => {
self.bottom_materialization(db)
}
};
intersection.negative(db).iter().when_all(db, |&neg_ty| {
let neg_ty = match relation {
TypeRelation::Subtyping
| TypeRelation::Redundancy
| TypeRelation::SubtypingAssuming(_) => neg_ty,
TypeRelation::Assignability => neg_ty.bottom_materialization(db),
TypeRelation::Assignability
| TypeRelation::ConstraintSetAssignability => {
neg_ty.bottom_materialization(db)
}
};
self_ty.is_disjoint_from_impl(
db,
@ -11860,6 +11892,11 @@ pub(crate) enum TypeRelation<'db> {
/// are not actually subtypes of each other. (That is, `implies_subtype_of(false, int, str)`
/// will return true!)
SubtypingAssuming(ConstraintSet<'db>),
/// A placeholder for the new assignability relation that uses constraint sets to encode
/// relationships with a typevar. This will eventually replace `Assignability`, but allows us
/// to start using the new relation in a controlled manner in some places.
ConstraintSetAssignability,
}
impl TypeRelation<'_> {
@ -11867,6 +11904,10 @@ impl TypeRelation<'_> {
matches!(self, TypeRelation::Assignability)
}
pub(crate) const fn is_constraint_set_assignability(self) -> bool {
matches!(self, TypeRelation::ConstraintSetAssignability)
}
pub(crate) const fn is_subtyping(self) -> bool {
matches!(self, TypeRelation::Subtyping)
}

View File

@ -637,7 +637,9 @@ impl<'db> ClassType<'db> {
| TypeRelation::SubtypingAssuming(_) => {
ConstraintSet::from(other.is_object(db))
}
TypeRelation::Assignability => ConstraintSet::from(!other.is_final(db)),
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability => {
ConstraintSet::from(!other.is_final(db))
}
},
// Protocol, Generic, and TypedDict are not represented by a ClassType.

View File

@ -207,7 +207,7 @@ impl<'db> ConstraintSet<'db> {
lower.top_materialization(db),
upper.bottom_materialization(db),
),
TypeRelation::Assignability => (
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability => (
lower.bottom_materialization(db),
upper.top_materialization(db),
),

View File

@ -903,7 +903,11 @@ fn has_relation_in_invariant_position<'db>(
disjointness_visitor,
),
// And A <~ B (assignability) is Bottom[A] <: Top[B]
(None, Some(base_mat), TypeRelation::Assignability) => is_subtype_in_invariant_position(
(
None,
Some(base_mat),
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability,
) => is_subtype_in_invariant_position(
db,
derived_type,
MaterializationKind::Bottom,
@ -913,7 +917,11 @@ fn has_relation_in_invariant_position<'db>(
relation_visitor,
disjointness_visitor,
),
(Some(derived_mat), None, TypeRelation::Assignability) => is_subtype_in_invariant_position(
(
Some(derived_mat),
None,
TypeRelation::Assignability | TypeRelation::ConstraintSetAssignability,
) => is_subtype_in_invariant_position(
db,
derived_type,
derived_mat,