From db3501bac101f6887fb98cf962dee80b272b2f16 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Thu, 4 Dec 2025 08:40:28 -0500 Subject: [PATCH] use ConstraintSetAssignability for constraint bounds --- crates/ty_python_semantic/src/types.rs | 14 ++++++++++++++ .../ty_python_semantic/src/types/constraints.rs | 16 ++++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 2ffb97a65e..a811cab142 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1911,6 +1911,20 @@ impl<'db> Type<'db> { .is_always_satisfied(db) } + /// Return true if this type is assignable to type `target` using constraint-set assignability. + /// + /// This uses `TypeRelation::ConstraintSetAssignability`, which encodes typevar relations into + /// a constraint set and lets `satisfied_by_all_typevars` perform existential vs universal + /// reasoning depending on inferable typevars. + pub fn is_constraint_set_assignable_to( + self, + db: &'db dyn Db, + target: Type<'db>, + ) -> bool { + self.when_constraint_set_assignable_to(db, target, InferableTypeVars::None) + .is_always_satisfied(db) + } + fn when_assignable_to( self, db: &'db dyn Db, diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs index d876e99857..4d6c302901 100644 --- a/crates/ty_python_semantic/src/types/constraints.rs +++ b/crates/ty_python_semantic/src/types/constraints.rs @@ -606,7 +606,7 @@ impl<'db> ConstrainedTypeVar<'db> { // If `lower ≰ upper`, then the constraint cannot be satisfied, since there is no type that // is both greater than `lower`, and less than `upper`. - if !lower.is_subtype_of(db, upper) { + if !lower.is_constraint_set_assignable_to(db, upper) { return Node::AlwaysFalse; } @@ -724,8 +724,12 @@ impl<'db> ConstrainedTypeVar<'db> { if !self.typevar(db).is_same_typevar_as(db, other.typevar(db)) { return false; } - other.lower(db).is_subtype_of(db, self.lower(db)) - && self.upper(db).is_subtype_of(db, other.upper(db)) + other + .lower(db) + .is_constraint_set_assignable_to(db, self.lower(db)) + && self + .upper(db) + .is_constraint_set_assignable_to(db, other.upper(db)) } /// Returns the intersection of two range constraints, or `None` if the intersection is empty. @@ -736,7 +740,7 @@ impl<'db> ConstrainedTypeVar<'db> { // If `lower ≰ upper`, then the intersection is empty, since there is no type that is both // greater than `lower`, and less than `upper`. - if !lower.is_subtype_of(db, upper) { + if !lower.is_constraint_set_assignable_to(db, upper) { return IntersectionResult::Disjoint; } @@ -1280,7 +1284,7 @@ impl<'db> Node<'db> { // process. debug_assert!(current_bounds.is_none_or( |(greatest_lower_bound, least_upper_bound)| { - greatest_lower_bound.is_subtype_of(db, least_upper_bound) + greatest_lower_bound.is_constraint_set_assignable_to(db, least_upper_bound) } )); @@ -3485,7 +3489,7 @@ impl<'db> GenericContext<'db> { // If `lower ≰ upper`, then there is no type that satisfies all of the paths in the // BDD. That's an ambiguous specialization, as described above. - if !greatest_lower_bound.is_subtype_of(db, least_upper_bound) { + if !greatest_lower_bound.is_constraint_set_assignable_to(db, least_upper_bound) { tracing::debug!( target: "ty_python_semantic::types::constraints::specialize_constrained", bound_typevar = %identity.display(db),