diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index c6b6c540f5..76acbad34f 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1963,6 +1963,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() => { @@ -2043,7 +2070,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), @@ -2053,7 +2080,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) => { @@ -2250,14 +2277,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, @@ -11066,6 +11098,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<'_> { @@ -11073,6 +11110,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) } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 033f326c39..484da6aeee 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -622,7 +622,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. diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs index 5733b7f600..7ddc9f65bb 100644 --- a/crates/ty_python_semantic/src/types/constraints.rs +++ b/crates/ty_python_semantic/src/types/constraints.rs @@ -194,7 +194,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), ), diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 480c1a7741..8ae3f0f434 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -876,7 +876,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, @@ -886,7 +890,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,