From ac3f0db809def6666fdb4cc19cf3ad367c6afa48 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 6 Jan 2026 17:31:10 -0500 Subject: [PATCH] use subtyping --- .../src/types/constraints.rs | 10 +++++--- .../ty_python_semantic/src/types/relation.rs | 24 +++++++++++++++---- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs index c2d878b99b..ec9fb7fdb7 100644 --- a/crates/ty_python_semantic/src/types/constraints.rs +++ b/crates/ty_python_semantic/src/types/constraints.rs @@ -747,12 +747,16 @@ impl<'db> ConstrainedTypeVar<'db> { if !self.typevar(db).is_same_typevar_as(db, other.typevar(db)) { return false; } + + // Note that we check that the bounds are _subtypes_ of each other, because we do not want + // dynamic types like `Any` to be pivots that we can use to compute transitive sequent map + // relationships. other .lower(db) - .is_constraint_set_assignable_to(db, self.lower(db)) + .is_constraint_set_subtype_of(db, self.lower(db)) && self .upper(db) - .is_constraint_set_assignable_to(db, other.upper(db)) + .is_constraint_set_subtype_of(db, other.upper(db)) } /// Returns the intersection of two range constraints, or `None` if the intersection is empty. @@ -763,7 +767,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_constraint_set_assignable_to(db, upper) { + if !lower.is_constraint_set_subtype_of(db, upper) { return IntersectionResult::Disjoint; } diff --git a/crates/ty_python_semantic/src/types/relation.rs b/crates/ty_python_semantic/src/types/relation.rs index 57f1945b67..13c966ea40 100644 --- a/crates/ty_python_semantic/src/types/relation.rs +++ b/crates/ty_python_semantic/src/types/relation.rs @@ -260,15 +260,17 @@ impl<'db> Type<'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) } + /// Return true if this type is assignable to type `target` using constraint-set assignability. + pub fn is_constraint_set_subtype_of(self, db: &'db dyn Db, target: Type<'db>) -> bool { + self.when_constraint_set_subtype_of(db, target, InferableTypeVars::None) + .is_always_satisfied(db) + } + pub(super) fn when_assignable_to( self, db: &'db dyn Db, @@ -297,6 +299,20 @@ impl<'db> Type<'db> { ) } + pub(super) fn when_constraint_set_subtype_of( + self, + db: &'db dyn Db, + target: Type<'db>, + inferable: InferableTypeVars<'_, 'db>, + ) -> ConstraintSet<'db> { + self.has_relation_to( + db, + target, + inferable, + TypeRelation::Subtyping(UseConstraintSets::Yes), + ) + } + /// Return `true` if it would be redundant to add `self` to a union that already contains `other`. /// /// See [`TypeRelation::Redundancy`] for more details.