diff --git a/crates/ty_python_semantic/resources/mdtest/generics/specialize_constrained.md b/crates/ty_python_semantic/resources/mdtest/generics/specialize_constrained.md index 4e04b91944..0cf656ccc3 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/specialize_constrained.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/specialize_constrained.md @@ -16,7 +16,7 @@ An unbounded typevar can specialize to any type. We will specialize the typevar bound of all of the types that satisfy the constraint set. ```py -from typing import Never +from typing import Any, Never from ty_extensions import ConstraintSet, generic_context # fmt: off @@ -26,6 +26,8 @@ def unbounded[T](): reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.always())) # revealed: ty_extensions.Specialization[T@unbounded = object] reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.range(Never, T, object))) + # revealed: ty_extensions.Specialization[T@unbounded = Any] + reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.range(Never, T, Any))) # revealed: None reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.never())) @@ -68,6 +70,10 @@ class Unrelated: ... def bounded[T: Base](): # revealed: ty_extensions.Specialization[T@bounded = Base] reveal_type(generic_context(bounded).specialize_constrained(ConstraintSet.always())) + # revealed: ty_extensions.Specialization[T@bounded = Base] + reveal_type(generic_context(bounded).specialize_constrained(ConstraintSet.range(Never, T, object))) + # revealed: ty_extensions.Specialization[T@bounded = Base & Any] + reveal_type(generic_context(bounded).specialize_constrained(ConstraintSet.range(Never, T, Any))) # revealed: None reveal_type(generic_context(bounded).specialize_constrained(ConstraintSet.never())) @@ -94,11 +100,17 @@ def bounded_by_gradual[T: Any](): # TODO: revealed: ty_extensions.Specialization[T@bounded_by_gradual = Any] # revealed: ty_extensions.Specialization[T@bounded_by_gradual = object] reveal_type(generic_context(bounded_by_gradual).specialize_constrained(ConstraintSet.always())) + # revealed: ty_extensions.Specialization[T@bounded_by_gradual = object] + reveal_type(generic_context(bounded_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, object))) + # revealed: ty_extensions.Specialization[T@bounded_by_gradual = Any] + reveal_type(generic_context(bounded_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, Any))) # revealed: None reveal_type(generic_context(bounded_by_gradual).specialize_constrained(ConstraintSet.never())) # revealed: ty_extensions.Specialization[T@bounded_by_gradual = Base] reveal_type(generic_context(bounded_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, Base))) + # revealed: ty_extensions.Specialization[T@bounded_by_gradual = object] + reveal_type(generic_context(bounded_by_gradual).specialize_constrained(ConstraintSet.range(Base, T, object))) # revealed: ty_extensions.Specialization[T@bounded_by_gradual = Unrelated] reveal_type(generic_context(bounded_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, Unrelated))) @@ -106,14 +118,24 @@ def bounded_by_gradual[T: Any](): def bounded_by_gradual_list[T: list[Any]](): # revealed: ty_extensions.Specialization[T@bounded_by_gradual_list = Top[list[Any]]] reveal_type(generic_context(bounded_by_gradual_list).specialize_constrained(ConstraintSet.always())) + # revealed: ty_extensions.Specialization[T@bounded_by_gradual_list = list[object]] + reveal_type(generic_context(bounded_by_gradual_list).specialize_constrained(ConstraintSet.range(Never, T, list[object]))) + # revealed: ty_extensions.Specialization[T@bounded_by_gradual_list = list[Any]] + reveal_type(generic_context(bounded_by_gradual_list).specialize_constrained(ConstraintSet.range(Never, T, list[Any]))) # revealed: None reveal_type(generic_context(bounded_by_gradual_list).specialize_constrained(ConstraintSet.never())) # revealed: ty_extensions.Specialization[T@bounded_by_gradual_list = list[Base]] reveal_type(generic_context(bounded_by_gradual_list).specialize_constrained(ConstraintSet.range(Never, T, list[Base]))) + # TODO: revealed: ty_extensions.Specialization[T@bounded_by_gradual_list = list[Base]] + # revealed: ty_extensions.Specialization[T@bounded_by_gradual_list = Top[list[Any]]] + reveal_type(generic_context(bounded_by_gradual_list).specialize_constrained(ConstraintSet.range(list[Base], T, object))) # revealed: ty_extensions.Specialization[T@bounded_by_gradual_list = list[Unrelated]] reveal_type(generic_context(bounded_by_gradual_list).specialize_constrained(ConstraintSet.range(Never, T, list[Unrelated]))) + # TODO: revealed: ty_extensions.Specialization[T@bounded_by_gradual_list = list[Unrelated]] + # revealed: ty_extensions.Specialization[T@bounded_by_gradual_list = Top[list[Any]]] + reveal_type(generic_context(bounded_by_gradual_list).specialize_constrained(ConstraintSet.range(list[Unrelated], T, object))) ``` ## Constrained typevar @@ -142,12 +164,21 @@ def constrained[T: (Base, Unrelated)](): # revealed: None reveal_type(generic_context(constrained).specialize_constrained(ConstraintSet.always())) # revealed: None + reveal_type(generic_context(constrained).specialize_constrained(ConstraintSet.range(Never, T, object))) + # revealed: None + reveal_type(generic_context(constrained).specialize_constrained(ConstraintSet.range(Never, T, Any))) + # revealed: None reveal_type(generic_context(constrained).specialize_constrained(ConstraintSet.never())) # revealed: ty_extensions.Specialization[T@constrained = Base] reveal_type(generic_context(constrained).specialize_constrained(ConstraintSet.range(Never, T, Base))) + # revealed: ty_extensions.Specialization[T@constrained = Base] + reveal_type(generic_context(constrained).specialize_constrained(ConstraintSet.range(Base, T, object))) + # revealed: ty_extensions.Specialization[T@constrained = Unrelated] reveal_type(generic_context(constrained).specialize_constrained(ConstraintSet.range(Never, T, Unrelated))) + # revealed: ty_extensions.Specialization[T@constrained = Unrelated] + reveal_type(generic_context(constrained).specialize_constrained(ConstraintSet.range(Unrelated, T, object))) # revealed: ty_extensions.Specialization[T@constrained = Base] reveal_type(generic_context(constrained).specialize_constrained(ConstraintSet.range(Never, T, Super))) @@ -178,15 +209,25 @@ def constrained_by_gradual[T: (Base, Any)](): # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any] # revealed: ty_extensions.Specialization[T@constrained_by_gradual = Base] reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, object))) + # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any] + # revealed: ty_extensions.Specialization[T@constrained_by_gradual = Base & Any] + reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, Any))) # revealed: None reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.never())) # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any] # revealed: ty_extensions.Specialization[T@constrained_by_gradual = Base] reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, Base))) + # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any] + # revealed: ty_extensions.Specialization[T@constrained_by_gradual = Base] + reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Base, T, object))) + # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any] # revealed: ty_extensions.Specialization[T@constrained_by_gradual = Unrelated] reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, Unrelated))) + # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any] + # revealed: ty_extensions.Specialization[T@constrained_by_gradual = object] + reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Unrelated, T, object))) # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any] # revealed: ty_extensions.Specialization[T@constrained_by_gradual = Base] @@ -206,6 +247,11 @@ def constrained_by_two_gradual[T: (Any, Any)](): # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any] # revealed: ty_extensions.Specialization[T@constrained_by_two_gradual = object] reveal_type(generic_context(constrained_by_two_gradual).specialize_constrained(ConstraintSet.always())) + # TODO: revealed: ty_extensions.Specialization[T@constrained_by_two_gradual = Any] + # revealed: ty_extensions.Specialization[T@constrained_by_two_gradual = object] + reveal_type(generic_context(constrained_by_two_gradual).specialize_constrained(ConstraintSet.range(Never, T, object))) + # revealed: ty_extensions.Specialization[T@constrained_by_two_gradual = Any] + reveal_type(generic_context(constrained_by_two_gradual).specialize_constrained(ConstraintSet.range(Never, T, Any))) # revealed: None reveal_type(generic_context(constrained_by_two_gradual).specialize_constrained(ConstraintSet.never())) @@ -233,14 +279,24 @@ def constrained_by_two_gradual[T: (Any, Any)](): def constrained_by_gradual_list[T: (list[Base], list[Any])](): # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list = list[Base]] reveal_type(generic_context(constrained_by_gradual_list).specialize_constrained(ConstraintSet.always())) + # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list = list[object]] + reveal_type(generic_context(constrained_by_gradual_list).specialize_constrained(ConstraintSet.range(Never, T, list[object]))) + # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual_list = list[Any]] + # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list = list[Base] & list[Any]] + reveal_type(generic_context(constrained_by_gradual_list).specialize_constrained(ConstraintSet.range(Never, T, list[Any]))) # revealed: None reveal_type(generic_context(constrained_by_gradual_list).specialize_constrained(ConstraintSet.never())) # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list = list[Base]] reveal_type(generic_context(constrained_by_gradual_list).specialize_constrained(ConstraintSet.range(Never, T, list[Base]))) - # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Any]] + # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list = list[Base]] + reveal_type(generic_context(constrained_by_gradual_list).specialize_constrained(ConstraintSet.range(list[Base], T, object))) + # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list = list[Unrelated]] reveal_type(generic_context(constrained_by_gradual_list).specialize_constrained(ConstraintSet.range(Never, T, list[Unrelated]))) + # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Unrelated]] + # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list = Top[list[Any]]] + reveal_type(generic_context(constrained_by_gradual_list).specialize_constrained(ConstraintSet.range(list[Unrelated], T, object))) # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Any]] # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list = list[Super]] @@ -257,14 +313,25 @@ def constrained_by_gradual_list[T: (list[Base], list[Any])](): def constrained_by_gradual_list_reverse[T: (list[Any], list[Base])](): # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list_reverse = list[Base]] reveal_type(generic_context(constrained_by_gradual_list_reverse).specialize_constrained(ConstraintSet.always())) + # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list_reverse = list[object]] + reveal_type(generic_context(constrained_by_gradual_list_reverse).specialize_constrained(ConstraintSet.range(Never, T, list[object]))) + # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual_list_reverse = list[Any]] + # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list_reverse = list[Base] & list[Any]] + reveal_type(generic_context(constrained_by_gradual_list_reverse).specialize_constrained(ConstraintSet.range(Never, T, list[Any]))) # revealed: None reveal_type(generic_context(constrained_by_gradual_list_reverse).specialize_constrained(ConstraintSet.never())) # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list_reverse = list[Base]] reveal_type(generic_context(constrained_by_gradual_list_reverse).specialize_constrained(ConstraintSet.range(Never, T, list[Base]))) + # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list_reverse = list[Base]] + reveal_type(generic_context(constrained_by_gradual_list_reverse).specialize_constrained(ConstraintSet.range(list[Base], T, object))) + # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Any]] # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list_reverse = list[Unrelated]] reveal_type(generic_context(constrained_by_gradual_list_reverse).specialize_constrained(ConstraintSet.range(Never, T, list[Unrelated]))) + # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Unrelated]] + # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list_reverse = Top[list[Any]]] + reveal_type(generic_context(constrained_by_gradual_list_reverse).specialize_constrained(ConstraintSet.range(list[Unrelated], T, object))) # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Any]] # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list_reverse = list[Super]] @@ -280,15 +347,26 @@ def constrained_by_two_gradual_lists[T: (list[Any], list[Any])](): # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Any]] # revealed: ty_extensions.Specialization[T@constrained_by_two_gradual_lists = Top[list[Any]]] reveal_type(generic_context(constrained_by_two_gradual_lists).specialize_constrained(ConstraintSet.always())) + # TODO: revealed: ty_extensions.Specialization[T@constrained_by_two_gradual_lists = list[Any]] + # revealed: ty_extensions.Specialization[T@constrained_by_two_gradual_lists = Top[list[Any]]] + reveal_type(generic_context(constrained_by_two_gradual_lists).specialize_constrained(ConstraintSet.range(Never, T, object))) + # revealed: ty_extensions.Specialization[T@constrained_by_two_gradual_lists = list[Any]] + reveal_type(generic_context(constrained_by_two_gradual_lists).specialize_constrained(ConstraintSet.range(Never, T, list[Any]))) # revealed: None reveal_type(generic_context(constrained_by_two_gradual_lists).specialize_constrained(ConstraintSet.never())) - # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Any]] # revealed: ty_extensions.Specialization[T@constrained_by_two_gradual_lists = list[Base]] reveal_type(generic_context(constrained_by_two_gradual_lists).specialize_constrained(ConstraintSet.range(Never, T, list[Base]))) + # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Base]] + # revealed: ty_extensions.Specialization[T@constrained_by_two_gradual_lists = Top[list[Any]]] + reveal_type(generic_context(constrained_by_two_gradual_lists).specialize_constrained(ConstraintSet.range(list[Base], T, object))) + # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Any]] # revealed: ty_extensions.Specialization[T@constrained_by_two_gradual_lists = list[Unrelated]] reveal_type(generic_context(constrained_by_two_gradual_lists).specialize_constrained(ConstraintSet.range(Never, T, list[Unrelated]))) + # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Unrelated]] + # revealed: ty_extensions.Specialization[T@constrained_by_two_gradual_lists = Top[list[Any]]] + reveal_type(generic_context(constrained_by_two_gradual_lists).specialize_constrained(ConstraintSet.range(list[Unrelated], T, object))) # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Any]] # revealed: ty_extensions.Specialization[T@constrained_by_two_gradual_lists = list[Super]] diff --git a/crates/ty_python_semantic/src/types/builder.rs b/crates/ty_python_semantic/src/types/builder.rs index 36977078e9..ae2c21759b 100644 --- a/crates/ty_python_semantic/src/types/builder.rs +++ b/crates/ty_python_semantic/src/types/builder.rs @@ -668,6 +668,7 @@ pub(crate) struct IntersectionBuilder<'db> { // but if a union is added to the intersection, we'll distribute ourselves over that union and // create a union of intersections. intersections: Vec>, + order_elements: bool, db: &'db dyn Db, } @@ -675,6 +676,7 @@ impl<'db> IntersectionBuilder<'db> { pub(crate) fn new(db: &'db dyn Db) -> Self { Self { db, + order_elements: false, intersections: vec![InnerIntersectionBuilder::default()], } } @@ -682,14 +684,25 @@ impl<'db> IntersectionBuilder<'db> { fn empty(db: &'db dyn Db) -> Self { Self { db, + order_elements: false, intersections: vec![], } } + pub(crate) fn order_elements(mut self, val: bool) -> Self { + self.order_elements = val; + self + } + pub(crate) fn add_positive(self, ty: Type<'db>) -> Self { self.add_positive_impl(ty, &mut vec![]) } + pub(crate) fn add_positive_in_place(&mut self, ty: Type<'db>) { + let updated = std::mem::replace(self, Self::empty(self.db)).add_positive(ty); + *self = updated; + } + pub(crate) fn add_positive_impl( mut self, ty: Type<'db>, @@ -898,13 +911,16 @@ impl<'db> IntersectionBuilder<'db> { pub(crate) fn build(mut self) -> Type<'db> { // Avoid allocating the UnionBuilder unnecessarily if we have just one intersection: if self.intersections.len() == 1 { - self.intersections.pop().unwrap().build(self.db) + self.intersections + .pop() + .unwrap() + .build(self.db, self.order_elements) } else { UnionType::from_elements( self.db, self.intersections .into_iter() - .map(|inner| inner.build(self.db)), + .map(|inner| inner.build(self.db, self.order_elements)), ) } } @@ -1238,7 +1254,7 @@ impl<'db> InnerIntersectionBuilder<'db> { } } - fn build(mut self, db: &'db dyn Db) -> Type<'db> { + fn build(mut self, db: &'db dyn Db, order_elements: bool) -> Type<'db> { self.simplify_constrained_typevars(db); // If any typevars are in `self.positive`, speculatively solve all bounded type variables @@ -1279,6 +1295,12 @@ impl<'db> InnerIntersectionBuilder<'db> { _ => { self.positive.shrink_to_fit(); self.negative.shrink_to_fit(); + if order_elements { + self.positive + .sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r)); + self.negative + .sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r)); + } Type::Intersection(IntersectionType::new(db, self.positive, self.negative)) } } diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs index 289e134324..d4f3e436f6 100644 --- a/crates/ty_python_semantic/src/types/constraints.rs +++ b/crates/ty_python_semantic/src/types/constraints.rs @@ -78,8 +78,8 @@ use salsa::plumbing::AsId; use crate::types::generics::{GenericContext, InferableTypeVars, Specialization}; use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion_guard}; use crate::types::{ - BoundTypeVarIdentity, BoundTypeVarInstance, IntersectionType, Type, TypeRelation, - TypeVarBoundOrConstraints, UnionType, walk_bound_type_var_type, + BoundTypeVarIdentity, BoundTypeVarInstance, IntersectionBuilder, IntersectionType, Type, + TypeVarBoundOrConstraints, UnionBuilder, UnionType, walk_bound_type_var_type, }; use crate::{Db, FxOrderSet}; @@ -198,21 +198,7 @@ impl<'db> ConstraintSet<'db> { typevar: BoundTypeVarInstance<'db>, lower: Type<'db>, upper: Type<'db>, - relation: TypeRelation<'db>, ) -> Self { - let (lower, upper) = match relation { - TypeRelation::Subtyping - | TypeRelation::Redundancy - | TypeRelation::SubtypingAssuming(_) => ( - lower.top_materialization(db), - upper.bottom_materialization(db), - ), - TypeRelation::Assignability => ( - lower.bottom_materialization(db), - upper.top_materialization(db), - ), - }; - Self { node: ConstrainedTypeVar::new_node(db, typevar, lower, upper), } @@ -428,7 +414,7 @@ impl<'db> ConstraintSet<'db> { typevar: BoundTypeVarInstance<'db>, upper: Type<'db>, ) -> Self { - Self::constrain_typevar(db, typevar, lower, upper, TypeRelation::Assignability) + Self::constrain_typevar(db, typevar, lower, upper) } #[expect(dead_code)] // Keep this around for debugging purposes @@ -499,9 +485,6 @@ impl<'db> ConstrainedTypeVar<'db> { mut lower: Type<'db>, mut upper: Type<'db>, ) -> Node<'db> { - debug_assert_eq!(lower, lower.bottom_materialization(db)); - debug_assert_eq!(upper, upper.top_materialization(db)); - // It's not useful for an upper bound to be an intersection type, or for a lower bound to // be a union type. Because the following equivalences hold, we can break these bounds // apart and create an equivalent BDD with more nodes but simpler constraints. (Fewer, @@ -594,7 +577,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_assignable_to(db, upper) { return Node::AlwaysFalse; } @@ -712,8 +695,8 @@ 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_assignable_to(db, self.lower(db)) + && self.upper(db).is_assignable_to(db, other.upper(db)) } /// Returns the intersection of two range constraints, or `None` if the intersection is empty. @@ -724,7 +707,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_assignable_to(db, upper) { return IntersectionResult::Disjoint; } @@ -1268,7 +1251,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_assignable_to(db, least_upper_bound) } )); @@ -3397,8 +3380,8 @@ impl<'db> GenericContext<'db> { // do with that, so instead we just report the ambiguity as a specialization failure. let mut satisfied = false; let mut unconstrained = false; - let mut greatest_lower_bound = Type::Never; - let mut least_upper_bound = Type::object(); + let mut greatest_lower_bound = UnionBuilder::new(db).order_elements(true); + let mut least_upper_bound = IntersectionBuilder::new(db).order_elements(true); let identity = bound_typevar.identity(db); tracing::trace!( target: "ty_python_semantic::types::constraints::specialize_constrained", @@ -3417,10 +3400,8 @@ impl<'db> GenericContext<'db> { upper_bound = %upper_bound.display(db), "found representative type", ); - greatest_lower_bound = - UnionType::from_elements(db, [greatest_lower_bound, lower_bound]); - least_upper_bound = - IntersectionType::from_elements(db, [least_upper_bound, upper_bound]); + greatest_lower_bound.add_in_place(lower_bound); + least_upper_bound.add_positive_in_place(upper_bound); } None => { unconstrained = true; @@ -3454,7 +3435,9 @@ 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) { + let greatest_lower_bound = greatest_lower_bound.build(); + let least_upper_bound = least_upper_bound.build(); + if !greatest_lower_bound.is_assignable_to(db, least_upper_bound) { tracing::debug!( target: "ty_python_semantic::types::constraints::specialize_constrained", bound_typevar = %identity.display(db),