diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index f462a4021b..051f36b5a8 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -39,7 +39,7 @@ use crate::suppression::check_suppressions; use crate::types::call::{Binding, Bindings, CallArguments, CallableBinding}; pub(crate) use crate::types::class_base::ClassBase; use crate::types::constraints::{ - ConstraintSet, Constraints, IteratorConstraintsExtension, OptionConstraintsExtension, + ConstraintSet, IteratorConstraintsExtension, OptionConstraintsExtension, }; use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder}; use crate::types::diagnostic::{INVALID_AWAIT, INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION}; @@ -183,16 +183,31 @@ fn definition_expression_type<'db>( pub(crate) type ApplyTypeMappingVisitor<'db> = TypeTransformer<'db, TypeMapping<'db, 'db>>; /// A [`PairVisitor`] that is used in `has_relation_to` methods. -pub(crate) type HasRelationToVisitor<'db, C> = - CycleDetector, Type<'db>, TypeRelation), C>; +pub(crate) type HasRelationToVisitor<'db> = + CycleDetector, Type<'db>, TypeRelation), ConstraintSet<'db>>; +impl Default for HasRelationToVisitor<'_> { + fn default() -> Self { + HasRelationToVisitor::new(ConstraintSet::from(true)) + } +} /// A [`PairVisitor`] that is used in `is_disjoint_from` methods. -pub(crate) type IsDisjointVisitor<'db, C> = PairVisitor<'db, IsDisjoint, C>; +pub(crate) type IsDisjointVisitor<'db> = PairVisitor<'db, IsDisjoint, ConstraintSet<'db>>; pub(crate) struct IsDisjoint; +impl Default for IsDisjointVisitor<'_> { + fn default() -> Self { + IsDisjointVisitor::new(ConstraintSet::from(false)) + } +} /// A [`PairVisitor`] that is used in `is_equivalent` methods. -pub(crate) type IsEquivalentVisitor<'db, C> = PairVisitor<'db, IsEquivalent, C>; +pub(crate) type IsEquivalentVisitor<'db> = PairVisitor<'db, IsEquivalent, ConstraintSet<'db>>; pub(crate) struct IsEquivalent; +impl Default for IsEquivalentVisitor<'_> { + fn default() -> Self { + IsEquivalentVisitor::new(ConstraintSet::from(true)) + } +} /// A [`CycleDetector`] that is used in `find_legacy_typevars` methods. pub(crate) type FindLegacyTypeVarsVisitor<'db> = CycleDetector, ()>; @@ -535,43 +550,39 @@ impl<'db> PropertyInstanceType<'db> { } } - fn when_equivalent_to>(self, db: &'db dyn Db, other: Self) -> C { - self.is_equivalent_to_impl( - db, - other, - &IsEquivalentVisitor::new(C::always_satisfiable(db)), - ) + fn when_equivalent_to(self, db: &'db dyn Db, other: Self) -> ConstraintSet<'db> { + self.is_equivalent_to_impl(db, other, &IsEquivalentVisitor::default()) } - fn is_equivalent_to_impl>( + fn is_equivalent_to_impl( self, db: &'db dyn Db, other: Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { + visitor: &IsEquivalentVisitor<'db>, + ) -> ConstraintSet<'db> { let getter_equivalence = if let Some(getter) = self.getter(db) { let Some(other_getter) = other.getter(db) else { - return C::unsatisfiable(db); + return ConstraintSet::from(false); }; getter.is_equivalent_to_impl(db, other_getter, visitor) } else { if other.getter(db).is_some() { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } - C::always_satisfiable(db) + ConstraintSet::from(true) }; let setter_equivalence = || { if let Some(setter) = self.setter(db) { let Some(other_setter) = other.setter(db) else { - return C::unsatisfiable(db); + return ConstraintSet::from(false); }; setter.is_equivalent_to_impl(db, other_setter, visitor) } else { if other.setter(db).is_some() { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } - C::always_satisfiable(db) + ConstraintSet::from(true) } }; @@ -1391,11 +1402,10 @@ impl<'db> Type<'db> { /// intersection simplification dependent on the order in which elements are added), so we do /// not use this more general definition of subtyping. pub(crate) fn is_subtype_of(self, db: &'db dyn Db, target: Type<'db>) -> bool { - self.when_subtype_of::(db, target) - .is_always_satisfied(db) + self.when_subtype_of(db, target).is_always_satisfied() } - fn when_subtype_of>(self, db: &'db dyn Db, target: Type<'db>) -> C { + fn when_subtype_of(self, db: &'db dyn Db, target: Type<'db>) -> ConstraintSet<'db> { self.has_relation_to(db, target, TypeRelation::Subtyping) } @@ -1403,61 +1413,55 @@ impl<'db> Type<'db> { /// /// [assignable to]: https://typing.python.org/en/latest/spec/concepts.html#the-assignable-to-or-consistent-subtyping-relation pub(crate) fn is_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> bool { - self.when_assignable_to::(db, target) - .is_always_satisfied(db) + self.when_assignable_to(db, target).is_always_satisfied() } - fn when_assignable_to>(self, db: &'db dyn Db, target: Type<'db>) -> C { + fn when_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> ConstraintSet<'db> { self.has_relation_to(db, target, TypeRelation::Assignability) } - fn has_relation_to>( + fn has_relation_to( self, db: &'db dyn Db, target: Type<'db>, relation: TypeRelation, - ) -> C { - self.has_relation_to_impl( - db, - target, - relation, - &HasRelationToVisitor::new(C::always_satisfiable(db)), - ) + ) -> ConstraintSet<'db> { + self.has_relation_to_impl(db, target, relation, &HasRelationToVisitor::default()) } - fn has_relation_to_impl>( + fn has_relation_to_impl( self, db: &'db dyn Db, target: Type<'db>, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { // Subtyping implies assignability, so if subtyping is reflexive and the two types are // equal, it is both a subtype and assignable. Assignability is always reflexive. // // Note that we could do a full equivalence check here, but that would be both expensive // and unnecessary. This early return is only an optimisation. if (relation.is_assignability() || self.subtyping_is_always_reflexive()) && self == target { - return C::always_satisfiable(db); + return ConstraintSet::from(true); } match (self, target) { // Everything is a subtype of `object`. (_, Type::NominalInstance(instance)) if instance.is_object() => { - C::always_satisfiable(db) + ConstraintSet::from(true) } (_, Type::ProtocolInstance(target)) if target.is_equivalent_to_object(db) => { - C::always_satisfiable(db) + ConstraintSet::from(true) } // `Never` is the bottom type, the empty set. // It is a subtype of all other types. - (Type::Never, _) => C::always_satisfiable(db), + (Type::Never, _) => ConstraintSet::from(true), // Dynamic is only a subtype of `object` and only a supertype of `Never`; both were // handled above. It's always assignable, though. (Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => { - C::from_bool(db, relation.is_assignability()) + ConstraintSet::from(relation.is_assignability()) } (Type::TypeAlias(self_alias), _) => visitor.visit((self, target, relation), || { @@ -1483,7 +1487,7 @@ impl<'db> Type<'db> { (Type::TypedDict(_), _) | (_, Type::TypedDict(_)) => { // TODO: Implement assignability and subtyping for TypedDict - C::from_bool(db, relation.is_assignability()) + ConstraintSet::from(relation.is_assignability()) } // In general, a TypeVar `T` is not a subtype of a type `S` unless one of the two conditions is satisfied: @@ -1497,17 +1501,17 @@ impl<'db> Type<'db> { (Type::NonInferableTypeVar(_), Type::Union(union)) if union.elements(db).contains(&self) => { - C::always_satisfiable(db) + ConstraintSet::from(true) } (Type::Intersection(intersection), Type::NonInferableTypeVar(_)) if intersection.positive(db).contains(&target) => { - C::always_satisfiable(db) + ConstraintSet::from(true) } (Type::Intersection(intersection), Type::NonInferableTypeVar(_)) if intersection.negative(db).contains(&target) => { - C::always_satisfiable(db) + ConstraintSet::from(true) } // Two identical typevars must always solve to the same type, so they are always @@ -1518,7 +1522,7 @@ impl<'db> Type<'db> { ( Type::NonInferableTypeVar(lhs_bound_typevar), Type::NonInferableTypeVar(rhs_bound_typevar), - ) if lhs_bound_typevar == rhs_bound_typevar => C::always_satisfiable(db), + ) if lhs_bound_typevar == rhs_bound_typevar => ConstraintSet::from(true), // A fully static typevar is a subtype of its upper bound, and to something similar to // the union of its constraints. An unbound, unconstrained, fully static typevar has an @@ -1546,12 +1550,12 @@ impl<'db> Type<'db> { if !bound_typevar .typevar(db) .constraints(db) - .when_some_and(db, |constraints| { + .when_some_and(|constraints| { constraints.iter().when_all(db, |constraint| { self.has_relation_to_impl(db, *constraint, relation, visitor) }) }) - .is_never_satisfied(db) => + .is_never_satisfied() => { // TODO: The repetition here isn't great, but we really need the fallthrough logic, // where this arm only engages if it returns true (or in the world of constraints, @@ -1560,7 +1564,7 @@ impl<'db> Type<'db> { bound_typevar .typevar(db) .constraints(db) - .when_some_and(db, |constraints| { + .when_some_and(|constraints| { constraints.iter().when_all(db, |constraint| { self.has_relation_to_impl(db, *constraint, relation, visitor) }) @@ -1570,7 +1574,7 @@ impl<'db> Type<'db> { // `Never` is the bottom type, the empty set. // Other than one unlikely edge case (TypeVars bound to `Never`), // no other type is a subtype of or assignable to `Never`. - (_, Type::Never) => C::unsatisfiable(db), + (_, Type::Never) => ConstraintSet::from(false), (Type::Union(union), _) => union.elements(db).iter().when_all(db, |&elem_ty| { elem_ty.has_relation_to_impl(db, target, relation, visitor) @@ -1604,15 +1608,15 @@ impl<'db> Type<'db> { // (If the typevar is bounded, it might be specialized to a smaller type than the // bound. This is true even if the bound is a final class, since the typevar can still // be specialized to `Never`.) - (_, Type::NonInferableTypeVar(_)) => C::unsatisfiable(db), + (_, Type::NonInferableTypeVar(_)) => ConstraintSet::from(false), // TODO: Infer specializations here - (Type::TypeVar(_), _) | (_, Type::TypeVar(_)) => C::unsatisfiable(db), + (Type::TypeVar(_), _) | (_, Type::TypeVar(_)) => ConstraintSet::from(false), // Note that the definition of `Type::AlwaysFalsy` depends on the return value of `__bool__`. // If `__bool__` always returns True or False, it can be treated as a subtype of `AlwaysTruthy` or `AlwaysFalsy`, respectively. - (left, Type::AlwaysFalsy) => C::from_bool(db, left.bool(db).is_always_false()), - (left, Type::AlwaysTruthy) => C::from_bool(db, left.bool(db).is_always_true()), + (left, Type::AlwaysFalsy) => ConstraintSet::from(left.bool(db).is_always_false()), + (left, Type::AlwaysTruthy) => ConstraintSet::from(left.bool(db).is_always_true()), // Currently, the only supertype of `AlwaysFalsy` and `AlwaysTruthy` is the universal set (object instance). (Type::AlwaysFalsy | Type::AlwaysTruthy, _) => { target.when_equivalent_to(db, Type::object()) @@ -1652,13 +1656,13 @@ impl<'db> Type<'db> { | Type::FunctionLiteral(_) | Type::ModuleLiteral(_) | Type::EnumLiteral(_), - ) => C::unsatisfiable(db), + ) => ConstraintSet::from(false), (Type::Callable(self_callable), Type::Callable(other_callable)) => { self_callable.has_relation_to_impl(db, other_callable, relation, visitor) } - (_, Type::Callable(_)) => self.into_callable(db).when_some_and(db, |callable| { + (_, Type::Callable(_)) => self.into_callable(db).when_some_and(|callable| { callable.has_relation_to_impl(db, target, relation, visitor) }), @@ -1667,22 +1671,22 @@ impl<'db> Type<'db> { } // A protocol instance can never be a subtype of a nominal type, with the *sole* exception of `object`. - (Type::ProtocolInstance(_), _) => C::unsatisfiable(db), + (Type::ProtocolInstance(_), _) => ConstraintSet::from(false), // All `StringLiteral` types are a subtype of `LiteralString`. - (Type::StringLiteral(_), Type::LiteralString) => C::always_satisfiable(db), + (Type::StringLiteral(_), Type::LiteralString) => ConstraintSet::from(true), // An instance is a subtype of an enum literal, if it is an instance of the enum class // and the enum has only one member. (Type::NominalInstance(_), Type::EnumLiteral(target_enum_literal)) => { if target_enum_literal.enum_class_instance(db) != self { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } - C::from_bool( + ConstraintSet::from(is_single_member_enum( db, - is_single_member_enum(db, target_enum_literal.enum_class(db)), - ) + target_enum_literal.enum_class(db), + )) } // Except for the special `LiteralString` case above, @@ -1697,7 +1701,7 @@ impl<'db> Type<'db> { | Type::ModuleLiteral(_) | Type::EnumLiteral(_), _, - ) => (self.literal_fallback_instance(db)).when_some_and(db, |instance| { + ) => (self.literal_fallback_instance(db)).when_some_and(|instance| { instance.has_relation_to_impl(db, target, relation, visitor) }), @@ -1721,7 +1725,7 @@ impl<'db> Type<'db> { (Type::DataclassDecorator(_) | Type::DataclassTransformer(_), _) => { // TODO: Implement subtyping using an equivalent `Callable` type. - C::unsatisfiable(db) + ConstraintSet::from(false) } // `TypeIs` is invariant. @@ -1749,7 +1753,7 @@ impl<'db> Type<'db> { .has_relation_to_impl(db, target, relation, visitor) } - (Type::Callable(_), _) => C::unsatisfiable(db), + (Type::Callable(_), _) => ConstraintSet::from(false), (Type::BoundSuper(_), Type::BoundSuper(_)) => self.when_equivalent_to(db, target), (Type::BoundSuper(_), _) => KnownClass::Super @@ -1769,7 +1773,7 @@ impl<'db> Type<'db> { visitor, ) }) - .unwrap_or_else(|| C::from_bool(db, relation.is_assignability())), + .unwrap_or_else(|| ConstraintSet::from(relation.is_assignability())), (Type::GenericAlias(alias), Type::SubclassOf(target_subclass_ty)) => target_subclass_ty .subclass_of() .into_class() @@ -1781,7 +1785,7 @@ impl<'db> Type<'db> { visitor, ) }) - .unwrap_or_else(|| C::from_bool(db, relation.is_assignability())), + .unwrap_or_else(|| ConstraintSet::from(relation.is_assignability())), // This branch asks: given two types `type[T]` and `type[S]`, is `type[T]` a subtype of `type[S]`? (Type::SubclassOf(self_subclass_ty), Type::SubclassOf(target_subclass_ty)) => { @@ -1804,7 +1808,7 @@ impl<'db> Type<'db> { .to_instance(db) .has_relation_to_impl(db, other, relation, visitor) .or(db, || { - C::from_bool(db, relation.is_assignability()).and(db, || { + ConstraintSet::from(relation.is_assignability()).and(db, || { other.has_relation_to_impl( db, KnownClass::Type.to_instance(db), @@ -1867,7 +1871,9 @@ impl<'db> Type<'db> { // Other than the special cases enumerated above, `Instance` types and typevars are // never subtypes of any other variants - (Type::NominalInstance(_) | Type::NonInferableTypeVar(_), _) => C::unsatisfiable(db), + (Type::NominalInstance(_) | Type::NonInferableTypeVar(_), _) => { + ConstraintSet::from(false) + } } } @@ -1884,42 +1890,39 @@ impl<'db> Type<'db> { /// /// [equivalent to]: https://typing.python.org/en/latest/spec/glossary.html#term-equivalent pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Type<'db>) -> bool { - self.when_equivalent_to::(db, other) - .is_always_satisfied(db) + self.when_equivalent_to(db, other).is_always_satisfied() } - fn when_equivalent_to>(self, db: &'db dyn Db, other: Type<'db>) -> C { - self.is_equivalent_to_impl( - db, - other, - &IsEquivalentVisitor::new(C::always_satisfiable(db)), - ) + fn when_equivalent_to(self, db: &'db dyn Db, other: Type<'db>) -> ConstraintSet<'db> { + self.is_equivalent_to_impl(db, other, &IsEquivalentVisitor::default()) } - pub(crate) fn is_equivalent_to_impl>( + pub(crate) fn is_equivalent_to_impl( self, db: &'db dyn Db, other: Type<'db>, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { + visitor: &IsEquivalentVisitor<'db>, + ) -> ConstraintSet<'db> { if self == other { - return C::always_satisfiable(db); + return ConstraintSet::from(true); } match (self, other) { // The `Divergent` type is a special type that is not equivalent to other kinds of dynamic types, // which prevents `Divergent` from being eliminated during union reduction. (Type::Dynamic(_), Type::Dynamic(DynamicType::Divergent(_))) - | (Type::Dynamic(DynamicType::Divergent(_)), Type::Dynamic(_)) => C::unsatisfiable(db), - (Type::Dynamic(_), Type::Dynamic(_)) => C::always_satisfiable(db), + | (Type::Dynamic(DynamicType::Divergent(_)), Type::Dynamic(_)) => { + ConstraintSet::from(false) + } + (Type::Dynamic(_), Type::Dynamic(_)) => ConstraintSet::from(true), (Type::SubclassOf(first), Type::SubclassOf(second)) => { match (first.subclass_of(), second.subclass_of()) { - (first, second) if first == second => C::always_satisfiable(db), + (first, second) if first == second => ConstraintSet::from(true), (SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => { - C::always_satisfiable(db) + ConstraintSet::from(true) } - _ => C::unsatisfiable(db), + _ => ConstraintSet::from(false), } } @@ -1967,23 +1970,23 @@ impl<'db> Type<'db> { } (Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n)) | (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => { - C::from_bool(db, n.is_object() && protocol.normalized(db) == nominal) + ConstraintSet::from(n.is_object() && protocol.normalized(db) == nominal) } // An instance of an enum class is equivalent to an enum literal of that class, // if that enum has only has one member. (Type::NominalInstance(instance), Type::EnumLiteral(literal)) | (Type::EnumLiteral(literal), Type::NominalInstance(instance)) => { if literal.enum_class_instance(db) != Type::NominalInstance(instance) { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } - C::from_bool(db, is_single_member_enum(db, instance.class_literal(db))) + ConstraintSet::from(is_single_member_enum(db, instance.class_literal(db))) } (Type::PropertyInstance(left), Type::PropertyInstance(right)) => { left.is_equivalent_to_impl(db, right, visitor) } - _ => C::unsatisfiable(db), + _ => ConstraintSet::from(false), } } @@ -1992,41 +1995,40 @@ impl<'db> Type<'db> { /// Note: This function aims to have no false positives, but might return /// wrong `false` answers in some cases. pub(crate) fn is_disjoint_from(self, db: &'db dyn Db, other: Type<'db>) -> bool { - self.when_disjoint_from::(db, other) - .is_always_satisfied(db) + self.when_disjoint_from(db, other).is_always_satisfied() } - fn when_disjoint_from>(self, db: &'db dyn Db, other: Type<'db>) -> C { - self.is_disjoint_from_impl(db, other, &IsDisjointVisitor::new(C::unsatisfiable(db))) + fn when_disjoint_from(self, db: &'db dyn Db, other: Type<'db>) -> ConstraintSet<'db> { + self.is_disjoint_from_impl(db, other, &IsDisjointVisitor::default()) } - pub(crate) fn is_disjoint_from_impl>( + pub(crate) fn is_disjoint_from_impl( self, db: &'db dyn Db, other: Type<'db>, - visitor: &IsDisjointVisitor<'db, C>, - ) -> C { - fn any_protocol_members_absent_or_disjoint<'db, C: Constraints<'db>>( + visitor: &IsDisjointVisitor<'db>, + ) -> ConstraintSet<'db> { + fn any_protocol_members_absent_or_disjoint<'db>( db: &'db dyn Db, protocol: ProtocolInstanceType<'db>, other: Type<'db>, - visitor: &IsDisjointVisitor<'db, C>, - ) -> C { + visitor: &IsDisjointVisitor<'db>, + ) -> ConstraintSet<'db> { protocol.interface(db).members(db).when_any(db, |member| { other .member(db, member.name()) .place .ignore_possibly_unbound() - .when_none_or(db, |attribute_type| { + .when_none_or(|attribute_type| { member.has_disjoint_type_from(db, attribute_type, visitor) }) }) } match (self, other) { - (Type::Never, _) | (_, Type::Never) => C::always_satisfiable(db), + (Type::Never, _) | (_, Type::Never) => ConstraintSet::from(true), - (Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => C::unsatisfiable(db), + (Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => ConstraintSet::from(false), (Type::TypeAlias(alias), _) => { let self_alias_ty = alias.value_type(db); @@ -2044,7 +2046,7 @@ impl<'db> Type<'db> { (Type::TypedDict(_), _) | (_, Type::TypedDict(_)) => { // TODO: Implement disjointness for TypedDict - C::unsatisfiable(db) + ConstraintSet::from(false) } // A typevar is never disjoint from itself, since all occurrences of the typevar must @@ -2054,13 +2056,13 @@ impl<'db> Type<'db> { ( Type::NonInferableTypeVar(self_bound_typevar), Type::NonInferableTypeVar(other_bound_typevar), - ) if self_bound_typevar == other_bound_typevar => C::unsatisfiable(db), + ) if self_bound_typevar == other_bound_typevar => ConstraintSet::from(false), (tvar @ Type::NonInferableTypeVar(_), Type::Intersection(intersection)) | (Type::Intersection(intersection), tvar @ Type::NonInferableTypeVar(_)) if intersection.negative(db).contains(&tvar) => { - C::always_satisfiable(db) + ConstraintSet::from(true) } // An unbounded typevar is never disjoint from any other type, since it might be @@ -2070,7 +2072,7 @@ impl<'db> Type<'db> { (Type::NonInferableTypeVar(bound_typevar), other) | (other, Type::NonInferableTypeVar(bound_typevar)) => { match bound_typevar.typevar(db).bound_or_constraints(db) { - None => C::unsatisfiable(db), + None => ConstraintSet::from(false), Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { bound.is_disjoint_from_impl(db, other, visitor) } @@ -2083,7 +2085,7 @@ impl<'db> Type<'db> { } // TODO: Infer specializations here - (Type::TypeVar(_), _) | (_, Type::TypeVar(_)) => C::unsatisfiable(db), + (Type::TypeVar(_), _) | (_, Type::TypeVar(_)) => ConstraintSet::from(false), (Type::Union(union), other) | (other, Type::Union(union)) => union .elements(db) @@ -2152,7 +2154,7 @@ impl<'db> Type<'db> { | Type::GenericAlias(..) | Type::SpecialForm(..) | Type::KnownInstance(..)), - ) => C::from_bool(db, left != right), + ) => ConstraintSet::from(left != right), ( Type::SubclassOf(_), @@ -2181,16 +2183,16 @@ impl<'db> Type<'db> { | Type::WrapperDescriptor(..) | Type::ModuleLiteral(..), Type::SubclassOf(_), - ) => C::always_satisfiable(db), + ) => ConstraintSet::from(true), (Type::AlwaysTruthy, ty) | (ty, Type::AlwaysTruthy) => { // `Truthiness::Ambiguous` may include `AlwaysTrue` as a subset, so it's not guaranteed to be disjoint. // Thus, they are only disjoint if `ty.bool() == AlwaysFalse`. - C::from_bool(db, ty.bool(db).is_always_false()) + ConstraintSet::from(ty.bool(db).is_always_false()) } (Type::AlwaysFalsy, ty) | (ty, Type::AlwaysFalsy) => { // Similarly, they are only disjoint if `ty.bool() == AlwaysTrue`. - C::from_bool(db, ty.bool(db).is_always_true()) + ConstraintSet::from(ty.bool(db).is_always_true()) } (Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => { @@ -2295,7 +2297,7 @@ impl<'db> Type<'db> { Place::Type(attribute_type, _) => { member.has_disjoint_type_from(db, attribute_type, visitor) } - Place::Unbound => C::unsatisfiable(db), + Place::Unbound => ConstraintSet::from(false), } }) }), @@ -2303,9 +2305,9 @@ impl<'db> Type<'db> { (Type::SubclassOf(subclass_of_ty), Type::ClassLiteral(class_b)) | (Type::ClassLiteral(class_b), Type::SubclassOf(subclass_of_ty)) => { match subclass_of_ty.subclass_of() { - SubclassOfInner::Dynamic(_) => C::unsatisfiable(db), + SubclassOfInner::Dynamic(_) => ConstraintSet::from(false), SubclassOfInner::Class(class_a) => { - class_b.when_subclass_of::(db, None, class_a).negate(db) + class_b.when_subclass_of(db, None, class_a).negate(db) } } } @@ -2313,9 +2315,9 @@ impl<'db> Type<'db> { (Type::SubclassOf(subclass_of_ty), Type::GenericAlias(alias_b)) | (Type::GenericAlias(alias_b), Type::SubclassOf(subclass_of_ty)) => { match subclass_of_ty.subclass_of() { - SubclassOfInner::Dynamic(_) => C::unsatisfiable(db), + SubclassOfInner::Dynamic(_) => ConstraintSet::from(false), SubclassOfInner::Class(class_a) => ClassType::from(alias_b) - .when_subclass_of::(db, class_a) + .when_subclass_of(db, class_a) .negate(db), } } @@ -2338,12 +2340,12 @@ impl<'db> Type<'db> { (Type::SpecialForm(special_form), Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => { - C::from_bool(db, !special_form.is_instance_of(db, instance.class(db))) + ConstraintSet::from(!special_form.is_instance_of(db, instance.class(db))) } (Type::KnownInstance(known_instance), Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::KnownInstance(known_instance)) => { - C::from_bool(db, !known_instance.is_instance_of(db, instance.class(db))) + ConstraintSet::from(!known_instance.is_instance_of(db, instance.class(db))) } (Type::BooleanLiteral(..) | Type::TypeIs(_), Type::NominalInstance(instance)) @@ -2351,45 +2353,45 @@ impl<'db> Type<'db> { // A `Type::BooleanLiteral()` must be an instance of exactly `bool` // (it cannot be an instance of a `bool` subclass) KnownClass::Bool - .when_subclass_of::(db, instance.class(db)) + .when_subclass_of(db, instance.class(db)) .negate(db) } (Type::BooleanLiteral(..) | Type::TypeIs(_), _) - | (_, Type::BooleanLiteral(..) | Type::TypeIs(_)) => C::always_satisfiable(db), + | (_, Type::BooleanLiteral(..) | Type::TypeIs(_)) => ConstraintSet::from(true), (Type::IntLiteral(..), Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::IntLiteral(..)) => { // A `Type::IntLiteral()` must be an instance of exactly `int` // (it cannot be an instance of an `int` subclass) KnownClass::Int - .when_subclass_of::(db, instance.class(db)) + .when_subclass_of(db, instance.class(db)) .negate(db) } - (Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => C::always_satisfiable(db), + (Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => ConstraintSet::from(true), (Type::StringLiteral(..), Type::LiteralString) - | (Type::LiteralString, Type::StringLiteral(..)) => C::unsatisfiable(db), + | (Type::LiteralString, Type::StringLiteral(..)) => ConstraintSet::from(false), (Type::StringLiteral(..) | Type::LiteralString, Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::StringLiteral(..) | Type::LiteralString) => { // A `Type::StringLiteral()` or a `Type::LiteralString` must be an instance of exactly `str` // (it cannot be an instance of a `str` subclass) KnownClass::Str - .when_subclass_of::(db, instance.class(db)) + .when_subclass_of(db, instance.class(db)) .negate(db) } - (Type::LiteralString, Type::LiteralString) => C::unsatisfiable(db), - (Type::LiteralString, _) | (_, Type::LiteralString) => C::always_satisfiable(db), + (Type::LiteralString, Type::LiteralString) => ConstraintSet::from(false), + (Type::LiteralString, _) | (_, Type::LiteralString) => ConstraintSet::from(true), (Type::BytesLiteral(..), Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::BytesLiteral(..)) => { // A `Type::BytesLiteral()` must be an instance of exactly `bytes` // (it cannot be an instance of a `bytes` subclass) KnownClass::Bytes - .when_subclass_of::(db, instance.class(db)) + .when_subclass_of(db, instance.class(db)) .negate(db) } @@ -2397,10 +2399,10 @@ impl<'db> Type<'db> { | (instance @ Type::NominalInstance(_), Type::EnumLiteral(enum_literal)) => { enum_literal .enum_class_instance(db) - .when_subtype_of::(db, instance) + .when_subtype_of(db, instance) .negate(db) } - (Type::EnumLiteral(..), _) | (_, Type::EnumLiteral(..)) => C::always_satisfiable(db), + (Type::EnumLiteral(..), _) | (_, Type::EnumLiteral(..)) => ConstraintSet::from(true), // A class-literal type `X` is always disjoint from an instance type `Y`, // unless the type expressing "all instances of `Z`" is a subtype of of `Y`, @@ -2408,13 +2410,13 @@ impl<'db> Type<'db> { (Type::ClassLiteral(class), instance @ Type::NominalInstance(_)) | (instance @ Type::NominalInstance(_), Type::ClassLiteral(class)) => class .metaclass_instance_type(db) - .when_subtype_of::(db, instance) + .when_subtype_of(db, instance) .negate(db), (Type::GenericAlias(alias), instance @ Type::NominalInstance(_)) | (instance @ Type::NominalInstance(_), Type::GenericAlias(alias)) => { ClassType::from(alias) .metaclass_instance_type(db) - .when_subtype_of::(db, instance) + .when_subtype_of(db, instance) .negate(db) } @@ -2423,7 +2425,7 @@ impl<'db> Type<'db> { // A `Type::FunctionLiteral()` must be an instance of exactly `types.FunctionType` // (it cannot be an instance of a `types.FunctionType` subclass) KnownClass::FunctionType - .when_subclass_of::(db, instance.class(db)) + .when_subclass_of(db, instance.class(db)) .negate(db) } @@ -2449,7 +2451,7 @@ impl<'db> Type<'db> { // No two callable types are ever disjoint because // `(*args: object, **kwargs: object) -> Never` is a subtype of all fully static // callable types. - C::unsatisfiable(db) + ConstraintSet::from(false) } (Type::Callable(_), Type::StringLiteral(_) | Type::BytesLiteral(_)) @@ -2457,7 +2459,7 @@ impl<'db> Type<'db> { // A callable type is disjoint from other literal types. For example, // `Type::StringLiteral` must be an instance of exactly `str`, not a subclass // of `str`, and `str` is not callable. The same applies to other literal types. - C::always_satisfiable(db) + ConstraintSet::from(true) } (Type::Callable(_), Type::SpecialForm(special_form)) @@ -2466,7 +2468,7 @@ impl<'db> Type<'db> { // that are callable (like TypedDict and collection constructors). // Most special forms are type constructors/annotations (like `typing.Literal`, // `typing.Union`, etc.) that are subscripted, not called. - C::from_bool(db, !special_form.is_callable()) + ConstraintSet::from(!special_form.is_callable()) } ( @@ -2484,9 +2486,9 @@ impl<'db> Type<'db> { ) .place .ignore_possibly_unbound() - .when_none_or(db, |dunder_call| { + .when_none_or(|dunder_call| { dunder_call - .when_assignable_to::(db, CallableType::unknown(db)) + .when_assignable_to(db, CallableType::unknown(db)) .negate(db) }), @@ -2499,7 +2501,7 @@ impl<'db> Type<'db> { Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_), ) => { // TODO: Implement disjointness for general callable type with other types - C::unsatisfiable(db) + ConstraintSet::from(false) } (Type::ModuleLiteral(..), other @ Type::NominalInstance(..)) @@ -2519,7 +2521,7 @@ impl<'db> Type<'db> { } (Type::BoundSuper(_), Type::BoundSuper(_)) => { - self.when_equivalent_to::(db, other).negate(db) + self.when_equivalent_to(db, other).negate(db) } (Type::BoundSuper(_), other) | (other, Type::BoundSuper(_)) => KnownClass::Super .to_instance(db) @@ -3842,7 +3844,7 @@ impl<'db> Type<'db> { Type::KnownInstance(KnownInstanceType::ConstraintSet(tracked_set)) => { let constraints = tracked_set.constraints(db); - Truthiness::from(constraints.is_always_satisfied(db)) + Truthiness::from(constraints.is_always_satisfied()) } Type::FunctionLiteral(_) @@ -6917,9 +6919,9 @@ impl<'db> KnownInstanceType<'db> { } KnownInstanceType::ConstraintSet(tracked_set) => { let constraints = tracked_set.constraints(self.db); - if constraints.is_always_satisfied(self.db) { + if constraints.is_always_satisfied() { f.write_str("ty_extensions.ConstraintSet[always]") - } else if constraints.is_never_satisfied(self.db) { + } else if constraints.is_never_satisfied() { f.write_str("ty_extensions.ConstraintSet[never]") } else { write!( @@ -8965,13 +8967,13 @@ impl<'db> BoundMethodType<'db> { ) } - fn has_relation_to_impl>( + fn has_relation_to_impl( self, db: &'db dyn Db, other: Self, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { // A bound method is a typically a subtype of itself. However, we must explicitly verify // the subtyping of the underlying function signatures (since they might be specialized // differently), and of the bound self parameter (taking care that parameters, including a @@ -8988,12 +8990,12 @@ impl<'db> BoundMethodType<'db> { }) } - fn is_equivalent_to_impl>( + fn is_equivalent_to_impl( self, db: &'db dyn Db, other: Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { + visitor: &IsEquivalentVisitor<'db>, + ) -> ConstraintSet<'db> { self.function(db) .is_equivalent_to_impl(db, other.function(db), visitor) .and(db, || { @@ -9121,15 +9123,15 @@ impl<'db> CallableType<'db> { /// Check whether this callable type has the given relation to another callable type. /// /// See [`Type::is_subtype_of`] and [`Type::is_assignable_to`] for more details. - fn has_relation_to_impl>( + fn has_relation_to_impl( self, db: &'db dyn Db, other: Self, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { if other.is_function_like(db) && !self.is_function_like(db) { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } self.signatures(db) .has_relation_to_impl(db, other.signatures(db), relation, visitor) @@ -9138,17 +9140,17 @@ impl<'db> CallableType<'db> { /// Check whether this callable type is equivalent to another callable type. /// /// See [`Type::is_equivalent_to`] for more details. - fn is_equivalent_to_impl>( + fn is_equivalent_to_impl( self, db: &'db dyn Db, other: Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { + visitor: &IsEquivalentVisitor<'db>, + ) -> ConstraintSet<'db> { if self == other { - return C::always_satisfiable(db); + return ConstraintSet::from(true); } - C::from_bool(db, self.is_function_like(db) == other.is_function_like(db)).and(db, || { + ConstraintSet::from(self.is_function_like(db) == other.is_function_like(db)).and(db, || { self.signatures(db) .is_equivalent_to_impl(db, other.signatures(db), visitor) }) @@ -9204,13 +9206,13 @@ pub(super) fn walk_method_wrapper_type<'db, V: visitor::TypeVisitor<'db> + ?Size } impl<'db> KnownBoundMethodType<'db> { - fn has_relation_to_impl>( + fn has_relation_to_impl( self, db: &'db dyn Db, other: Self, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { match (self, other) { ( KnownBoundMethodType::FunctionTypeDunderGet(self_function), @@ -9232,7 +9234,7 @@ impl<'db> KnownBoundMethodType<'db> { ) => self_property.when_equivalent_to(db, other_property), (KnownBoundMethodType::StrStartswith(_), KnownBoundMethodType::StrStartswith(_)) => { - C::from_bool(db, self == other) + ConstraintSet::from(self == other) } ( @@ -9246,16 +9248,16 @@ impl<'db> KnownBoundMethodType<'db> { | KnownBoundMethodType::PropertyDunderGet(_) | KnownBoundMethodType::PropertyDunderSet(_) | KnownBoundMethodType::StrStartswith(_), - ) => C::unsatisfiable(db), + ) => ConstraintSet::from(false), } } - fn is_equivalent_to_impl>( + fn is_equivalent_to_impl( self, db: &'db dyn Db, other: Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { + visitor: &IsEquivalentVisitor<'db>, + ) -> ConstraintSet<'db> { match (self, other) { ( KnownBoundMethodType::FunctionTypeDunderGet(self_function), @@ -9277,7 +9279,7 @@ impl<'db> KnownBoundMethodType<'db> { ) => self_property.is_equivalent_to_impl(db, other_property, visitor), (KnownBoundMethodType::StrStartswith(_), KnownBoundMethodType::StrStartswith(_)) => { - C::from_bool(db, self == other) + ConstraintSet::from(self == other) } ( @@ -9291,7 +9293,7 @@ impl<'db> KnownBoundMethodType<'db> { | KnownBoundMethodType::PropertyDunderGet(_) | KnownBoundMethodType::PropertyDunderSet(_) | KnownBoundMethodType::StrStartswith(_), - ) => C::unsatisfiable(db), + ) => ConstraintSet::from(false), } } @@ -10102,30 +10104,30 @@ impl<'db> UnionType<'db> { .build() } - pub(crate) fn is_equivalent_to_impl>( + pub(crate) fn is_equivalent_to_impl( self, db: &'db dyn Db, other: Self, - _visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { + _visitor: &IsEquivalentVisitor<'db>, + ) -> ConstraintSet<'db> { if self == other { - return C::always_satisfiable(db); + return ConstraintSet::from(true); } let self_elements = self.elements(db); let other_elements = other.elements(db); if self_elements.len() != other_elements.len() { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } let sorted_self = self.normalized(db); if sorted_self == Type::Union(other) { - return C::always_satisfiable(db); + return ConstraintSet::from(true); } - C::from_bool(db, sorted_self == other.normalized(db)) + ConstraintSet::from(sorted_self == other.normalized(db)) } } @@ -10203,37 +10205,37 @@ impl<'db> IntersectionType<'db> { } /// Return `true` if `self` represents exactly the same set of possible runtime objects as `other` - pub(crate) fn is_equivalent_to_impl>( + pub(crate) fn is_equivalent_to_impl( self, db: &'db dyn Db, other: Self, - _visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { + _visitor: &IsEquivalentVisitor<'db>, + ) -> ConstraintSet<'db> { if self == other { - return C::always_satisfiable(db); + return ConstraintSet::from(true); } let self_positive = self.positive(db); let other_positive = other.positive(db); if self_positive.len() != other_positive.len() { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } let self_negative = self.negative(db); let other_negative = other.negative(db); if self_negative.len() != other_negative.len() { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } let sorted_self = self.normalized(db); if sorted_self == other { - return C::always_satisfiable(db); + return ConstraintSet::from(true); } - C::from_bool(db, sorted_self == other.normalized(db)) + ConstraintSet::from(sorted_self == other.normalized(db)) } /// Returns an iterator over the positive elements of the intersection. If diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index d79493ff81..6444e33b03 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -17,7 +17,6 @@ use crate::db::Db; use crate::dunder_all::dunder_all_names; use crate::place::{Boundness, Place}; use crate::types::call::arguments::{Expansion, is_expandable_type}; -use crate::types::constraints::{ConstraintSet, Constraints}; use crate::types::diagnostic::{ CALL_NON_CALLABLE, CONFLICTING_ARGUMENT_FORMS, INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT, NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS, @@ -586,8 +585,7 @@ impl<'db> Bindings<'db> { Type::FunctionLiteral(function_type) => match function_type.known(db) { Some(KnownFunction::IsEquivalentTo) => { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { - let constraints = - ty_a.when_equivalent_to::(db, *ty_b); + let constraints = ty_a.when_equivalent_to(db, *ty_b); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -597,7 +595,7 @@ impl<'db> Bindings<'db> { Some(KnownFunction::IsSubtypeOf) => { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { - let constraints = ty_a.when_subtype_of::(db, *ty_b); + let constraints = ty_a.when_subtype_of(db, *ty_b); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -607,8 +605,7 @@ impl<'db> Bindings<'db> { Some(KnownFunction::IsAssignableTo) => { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { - let constraints = - ty_a.when_assignable_to::(db, *ty_b); + let constraints = ty_a.when_assignable_to(db, *ty_b); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -618,8 +615,7 @@ impl<'db> Bindings<'db> { Some(KnownFunction::IsDisjointFrom) => { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { - let constraints = - ty_a.when_disjoint_from::(db, *ty_b); + let constraints = ty_a.when_disjoint_from(db, *ty_b); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -2252,8 +2248,8 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { // constraint set that we get from this assignability check, instead of inferring and // building them in an earlier separate step. if argument_type - .when_assignable_to::(self.db, expected_ty) - .is_never_satisfied(self.db) + .when_assignable_to(self.db, expected_ty) + .is_never_satisfied() { let positional = matches!(argument, Argument::Positional | Argument::Synthetic) && !parameter.is_variadic(); diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 726cb1104e..464a44e822 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -14,7 +14,7 @@ use crate::semantic_index::symbol::Symbol; use crate::semantic_index::{ DeclarationWithConstraint, SemanticIndex, attribute_declarations, attribute_scopes, }; -use crate::types::constraints::{ConstraintSet, Constraints, IteratorConstraintsExtension}; +use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension}; use crate::types::context::InferContext; use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE}; use crate::types::enums::enum_metadata; @@ -524,46 +524,45 @@ impl<'db> ClassType<'db> { /// Return `true` if `other` is present in this class's MRO. pub(super) fn is_subclass_of(self, db: &'db dyn Db, other: ClassType<'db>) -> bool { - self.when_subclass_of::(db, other) - .is_always_satisfied(db) + self.when_subclass_of(db, other).is_always_satisfied() } - pub(super) fn when_subclass_of>( + pub(super) fn when_subclass_of( self, db: &'db dyn Db, other: ClassType<'db>, - ) -> C { + ) -> ConstraintSet<'db> { self.has_relation_to_impl( db, other, TypeRelation::Subtyping, - &HasRelationToVisitor::new(C::always_satisfiable(db)), + &HasRelationToVisitor::default(), ) } - pub(super) fn has_relation_to_impl>( + pub(super) fn has_relation_to_impl( self, db: &'db dyn Db, other: Self, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { self.iter_mro(db).when_any(db, |base| { match base { ClassBase::Dynamic(_) => match relation { - TypeRelation::Subtyping => C::from_bool(db, other.is_object(db)), - TypeRelation::Assignability => C::from_bool(db, !other.is_final(db)), + TypeRelation::Subtyping => ConstraintSet::from(other.is_object(db)), + TypeRelation::Assignability => ConstraintSet::from(!other.is_final(db)), }, // Protocol and Generic are not represented by a ClassType. - ClassBase::Protocol | ClassBase::Generic => C::unsatisfiable(db), + ClassBase::Protocol | ClassBase::Generic => ConstraintSet::from(false), ClassBase::Class(base) => match (base, other) { (ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => { - C::from_bool(db, base == other) + ConstraintSet::from(base == other) } (ClassType::Generic(base), ClassType::Generic(other)) => { - C::from_bool(db, base.origin(db) == other.origin(db)).and(db, || { + ConstraintSet::from(base.origin(db) == other.origin(db)).and(db, || { base.specialization(db).has_relation_to_impl( db, other.specialization(db), @@ -573,34 +572,38 @@ impl<'db> ClassType<'db> { }) } (ClassType::Generic(_), ClassType::NonGeneric(_)) - | (ClassType::NonGeneric(_), ClassType::Generic(_)) => C::unsatisfiable(db), + | (ClassType::NonGeneric(_), ClassType::Generic(_)) => { + ConstraintSet::from(false) + } }, ClassBase::TypedDict => { // TODO: Implement subclassing and assignability for TypedDicts. - C::always_satisfiable(db) + ConstraintSet::from(true) } } }) } - pub(super) fn is_equivalent_to_impl>( + pub(super) fn is_equivalent_to_impl( self, db: &'db dyn Db, other: ClassType<'db>, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { + visitor: &IsEquivalentVisitor<'db>, + ) -> ConstraintSet<'db> { if self == other { - return C::always_satisfiable(db); + return ConstraintSet::from(true); } match (self, other) { // A non-generic class is never equivalent to a generic class. // Two non-generic classes are only equivalent if they are equal (handled above). - (ClassType::NonGeneric(_), _) | (_, ClassType::NonGeneric(_)) => C::unsatisfiable(db), + (ClassType::NonGeneric(_), _) | (_, ClassType::NonGeneric(_)) => { + ConstraintSet::from(false) + } (ClassType::Generic(this), ClassType::Generic(other)) => { - C::from_bool(db, this.origin(db) == other.origin(db)).and(db, || { + ConstraintSet::from(this.origin(db) == other.origin(db)).and(db, || { this.specialization(db).is_equivalent_to_impl( db, other.specialization(db), @@ -1701,13 +1704,13 @@ impl<'db> ClassLiteral<'db> { .contains(&ClassBase::Class(other)) } - pub(super) fn when_subclass_of>( + pub(super) fn when_subclass_of( self, db: &'db dyn Db, specialization: Option>, other: ClassType<'db>, - ) -> C { - C::from_bool(db, self.is_subclass_of(db, specialization, other)) + ) -> ConstraintSet<'db> { + ConstraintSet::from(self.is_subclass_of(db, specialization, other)) } /// Return `true` if this class constitutes a typed dict specification (inherits from @@ -4360,12 +4363,12 @@ impl KnownClass { .is_ok_and(|class| class.is_subclass_of(db, None, other)) } - pub(super) fn when_subclass_of<'db, C: Constraints<'db>>( + pub(super) fn when_subclass_of<'db>( self, db: &'db dyn Db, other: ClassType<'db>, - ) -> C { - C::from_bool(db, self.is_subclass_of(db, other)) + ) -> ConstraintSet<'db> { + ConstraintSet::from(self.is_subclass_of(db, other)) } /// Return the module in which we should look up the definition for this class diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs index 546d77e4f5..635936747b 100644 --- a/crates/ty_python_semantic/src/types/constraints.rs +++ b/crates/ty_python_semantic/src/types/constraints.rs @@ -43,12 +43,10 @@ //! — must be fully static. We take the bottom and top materializations of the types to remove any //! gradual forms if needed. //! -//! NOTE: This module is currently in a transitional state: we've added a [`Constraints`] trait, -//! and updated all of our type property implementations to work on any impl of that trait. We have -//! added the DNF [`ConstraintSet`] representation, and updated all of our property checks to build -//! up a constraint set and then check whether it is ever or always satisfiable, as appropriate. We -//! are not yet inferring specializations from those constraints, and we will likely remove the -//! [`Constraints`] trait once everything has stabilized. +//! NOTE: This module is currently in a transitional state. We've added the DNF [`ConstraintSet`] +//! representation, and updated all of our property checks to build up a constraint set and then +//! check whether it is ever or always satisfiable, as appropriate. We are not yet inferring +//! specializations from those constraints. //! //! ### Examples //! @@ -110,86 +108,29 @@ fn incomparable<'db>(db: &'db dyn Db, left: Type<'db>, right: Type<'db>) -> bool !comparable(db, left, right) } -/// Encodes the constraints under which a type property (e.g. assignability) holds. -pub(crate) trait Constraints<'db>: Clone + Sized + std::fmt::Debug { - /// Returns a constraint set that never holds - fn unsatisfiable(db: &'db dyn Db) -> Self; - - /// Returns a constraint set that always holds - fn always_satisfiable(db: &'db dyn Db) -> Self; - - /// Returns whether this constraint set never holds - fn is_never_satisfied(&self, db: &'db dyn Db) -> bool; - - /// Returns whether this constraint set always holds - fn is_always_satisfied(&self, db: &'db dyn Db) -> bool; - - /// Updates this constraint set to hold the union of itself and another constraint set. - fn union(&mut self, db: &'db dyn Db, other: Self) -> &Self; - - /// Updates this constraint set to hold the intersection of itself and another constraint set. - fn intersect(&mut self, db: &'db dyn Db, other: Self) -> &Self; - - /// Returns the negation of this constraint set. - fn negate(self, db: &'db dyn Db) -> Self; - - /// Returns a constraint set representing a boolean condition. - fn from_bool(db: &'db dyn Db, b: bool) -> Self { - if b { - Self::always_satisfiable(db) - } else { - Self::unsatisfiable(db) - } - } - - /// Returns the intersection of this constraint set and another. The other constraint set is - /// provided as a thunk, to implement short-circuiting: the thunk is not forced if the - /// constraint set is already saturated. - fn and(mut self, db: &'db dyn Db, other: impl FnOnce() -> Self) -> Self { - if !self.is_never_satisfied(db) { - self.intersect(db, other()); - } - self - } - - /// Returns the union of this constraint set and another. The other constraint set is provided - /// as a thunk, to implement short-circuiting: the thunk is not forced if the constraint set is - /// already saturated. - fn or(mut self, db: &'db dyn Db, other: impl FnOnce() -> Self) -> Self { - if !self.is_always_satisfied(db) { - self.union(db, other()); - } - self - } - - // This is here so that we can easily print constraint sets when debugging. - fn display(&self, db: &'db dyn Db) -> impl Display; -} - /// An extension trait for building constraint sets from [`Option`] values. pub(crate) trait OptionConstraintsExtension { - /// Returns [`always_satisfiable`][Constraints::always_satisfiable] if the option is `None`; - /// otherwise applies a function to determine under what constraints the value inside of it - /// holds. - fn when_none_or<'db, C: Constraints<'db>>(self, db: &'db dyn Db, f: impl FnOnce(T) -> C) -> C; - - /// Returns [`unsatisfiable`][Constraints::unsatisfiable] if the option is `None`; otherwise + /// Returns a constraint set that is always satisfiable if the option is `None`; otherwise /// applies a function to determine under what constraints the value inside of it holds. - fn when_some_and<'db, C: Constraints<'db>>(self, db: &'db dyn Db, f: impl FnOnce(T) -> C) -> C; + fn when_none_or<'db>(self, f: impl FnOnce(T) -> ConstraintSet<'db>) -> ConstraintSet<'db>; + + /// Returns a constraint set that is never satisfiable if the option is `None`; otherwise + /// applies a function to determine under what constraints the value inside of it holds. + fn when_some_and<'db>(self, f: impl FnOnce(T) -> ConstraintSet<'db>) -> ConstraintSet<'db>; } impl OptionConstraintsExtension for Option { - fn when_none_or<'db, C: Constraints<'db>>(self, db: &'db dyn Db, f: impl FnOnce(T) -> C) -> C { + fn when_none_or<'db>(self, f: impl FnOnce(T) -> ConstraintSet<'db>) -> ConstraintSet<'db> { match self { Some(value) => f(value), - None => C::always_satisfiable(db), + None => ConstraintSet::always(), } } - fn when_some_and<'db, C: Constraints<'db>>(self, db: &'db dyn Db, f: impl FnOnce(T) -> C) -> C { + fn when_some_and<'db>(self, f: impl FnOnce(T) -> ConstraintSet<'db>) -> ConstraintSet<'db> { match self { Some(value) => f(value), - None => C::unsatisfiable(db), + None => ConstraintSet::never(), } } } @@ -199,36 +140,52 @@ pub(crate) trait IteratorConstraintsExtension { /// Returns the constraints under which any element of the iterator holds. /// /// This method short-circuits; if we encounter any element that - /// [`is_always_satisfied`][Constraints::is_always_satisfied] true, then the overall result + /// [`is_always_satisfied`][ConstraintSet::is_always_satisfied] true, then the overall result /// must be as well, and we stop consuming elements from the iterator. - fn when_any<'db, C: Constraints<'db>>(self, db: &'db dyn Db, f: impl FnMut(T) -> C) -> C; + fn when_any<'db>( + self, + db: &'db dyn Db, + f: impl FnMut(T) -> ConstraintSet<'db>, + ) -> ConstraintSet<'db>; /// Returns the constraints under which every element of the iterator holds. /// /// This method short-circuits; if we encounter any element that - /// [`is_never_satisfied`][Constraints::is_never_satisfied] true, then the overall result must - /// be as well, and we stop consuming elements from the iterator. - fn when_all<'db, C: Constraints<'db>>(self, db: &'db dyn Db, f: impl FnMut(T) -> C) -> C; + /// [`is_never_satisfied`][ConstraintSet::is_never_satisfied] true, then the overall result + /// must be as well, and we stop consuming elements from the iterator. + fn when_all<'db>( + self, + db: &'db dyn Db, + f: impl FnMut(T) -> ConstraintSet<'db>, + ) -> ConstraintSet<'db>; } impl IteratorConstraintsExtension for I where I: Iterator, { - fn when_any<'db, C: Constraints<'db>>(self, db: &'db dyn Db, mut f: impl FnMut(T) -> C) -> C { - let mut result = C::unsatisfiable(db); + fn when_any<'db>( + self, + db: &'db dyn Db, + mut f: impl FnMut(T) -> ConstraintSet<'db>, + ) -> ConstraintSet<'db> { + let mut result = ConstraintSet::never(); for child in self { - if result.union(db, f(child)).is_always_satisfied(db) { + if result.union(db, f(child)).is_always_satisfied() { return result; } } result } - fn when_all<'db, C: Constraints<'db>>(self, db: &'db dyn Db, mut f: impl FnMut(T) -> C) -> C { - let mut result = C::always_satisfiable(db); + fn when_all<'db>( + self, + db: &'db dyn Db, + mut f: impl FnMut(T) -> ConstraintSet<'db>, + ) -> ConstraintSet<'db> { + let mut result = ConstraintSet::always(); for child in self { - if result.intersect(db, f(child)).is_never_satisfied(db) { + if result.intersect(db, &f(child)).is_never_satisfied() { return result; } } @@ -259,13 +216,67 @@ pub struct ConstraintSet<'db> { } impl<'db> ConstraintSet<'db> { - /// Returns the constraint set that is never satisfiable. fn never() -> Self { Self { clauses: smallvec![], } } + fn always() -> Self { + Self::singleton(ConstraintClause::always()) + } + + /// Returns whether this constraint set never holds + pub(crate) fn is_never_satisfied(&self) -> bool { + self.clauses.is_empty() + } + + /// Returns whether this constraint set always holds + pub(crate) fn is_always_satisfied(&self) -> bool { + self.clauses.len() == 1 && self.clauses[0].is_always() + } + + /// Updates this constraint set to hold the union of itself and another constraint set. + pub(crate) fn union(&mut self, db: &'db dyn Db, other: Self) -> &Self { + self.union_set(db, other); + self + } + + /// Updates this constraint set to hold the intersection of itself and another constraint set. + pub(crate) fn intersect(&mut self, db: &'db dyn Db, other: &Self) -> &Self { + self.intersect_set(db, other); + self + } + + /// Returns the negation of this constraint set. + pub(crate) fn negate(&self, db: &'db dyn Db) -> Self { + let mut result = Self::always(); + for clause in &self.clauses { + result.intersect_set(db, &clause.negate(db)); + } + result + } + + /// Returns the intersection of this constraint set and another. The other constraint set is + /// provided as a thunk, to implement short-circuiting: the thunk is not forced if the + /// constraint set is already saturated. + pub(crate) fn and(mut self, db: &'db dyn Db, other: impl FnOnce() -> Self) -> Self { + if !self.is_never_satisfied() { + self.intersect(db, &other()); + } + self + } + + /// Returns the union of this constraint set and another. The other constraint set is provided + /// as a thunk, to implement short-circuiting: the thunk is not forced if the constraint set is + /// already saturated. + pub(crate) fn or(mut self, db: &'db dyn Db, other: impl FnOnce() -> Self) -> Self { + if !self.is_always_satisfied() { + self.union(db, other()); + } + self + } + /// Returns a constraint set that contains a single clause. fn singleton(clause: ConstraintClause<'db>) -> Self { Self { @@ -402,44 +413,8 @@ impl<'db> ConstraintSet<'db> { } } } -} -impl<'db> Constraints<'db> for ConstraintSet<'db> { - fn unsatisfiable(_db: &'db dyn Db) -> Self { - Self::never() - } - - fn always_satisfiable(_db: &'db dyn Db) -> Self { - Self::singleton(ConstraintClause::always()) - } - - fn is_never_satisfied(&self, _db: &'db dyn Db) -> bool { - self.clauses.is_empty() - } - - fn is_always_satisfied(&self, _db: &'db dyn Db) -> bool { - self.clauses.len() == 1 && self.clauses[0].is_always() - } - - fn union(&mut self, db: &'db dyn Db, other: Self) -> &Self { - self.union_set(db, other); - self - } - - fn intersect(&mut self, db: &'db dyn Db, other: Self) -> &Self { - self.intersect_set(db, &other); - self - } - - fn negate(self, db: &'db dyn Db) -> Self { - let mut result = Self::always_satisfiable(db); - for clause in self.clauses { - result.intersect_set(db, &clause.negate(db)); - } - result - } - - fn display(&self, db: &'db dyn Db) -> impl Display { + pub(crate) fn display(&self, db: &'db dyn Db) -> impl Display { struct DisplayConstraintSet<'a, 'db> { set: &'a ConstraintSet<'db>, db: &'db dyn Db, @@ -464,6 +439,12 @@ impl<'db> Constraints<'db> for ConstraintSet<'db> { } } +impl From for ConstraintSet<'_> { + fn from(b: bool) -> Self { + if b { Self::always() } else { Self::never() } + } +} + /// The intersection of zero or more individual constraints. /// /// This is called a "constraint set", and denoted _C_, in [[POPL2015][]]. diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index be9976fe6f..a2a8547faa 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -65,7 +65,7 @@ use crate::semantic_index::definition::Definition; use crate::semantic_index::scope::ScopeId; use crate::semantic_index::{FileScopeId, SemanticIndex, semantic_index}; use crate::types::call::{Binding, CallArguments}; -use crate::types::constraints::{ConstraintSet, Constraints}; +use crate::types::constraints::ConstraintSet; use crate::types::context::InferContext; use crate::types::diagnostic::{ INVALID_ARGUMENT_TYPE, REDUNDANT_CAST, STATIC_ASSERT_ERROR, TYPE_ASSERTION_FAILURE, @@ -944,16 +944,16 @@ impl<'db> FunctionType<'db> { BoundMethodType::new(db, self, self_instance) } - pub(crate) fn has_relation_to_impl>( + pub(crate) fn has_relation_to_impl( self, db: &'db dyn Db, other: Self, relation: TypeRelation, - _visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + _visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { match relation { - TypeRelation::Subtyping => C::from_bool(db, self.is_subtype_of(db, other)), - TypeRelation::Assignability => C::from_bool(db, self.is_assignable_to(db, other)), + TypeRelation::Subtyping => ConstraintSet::from(self.is_subtype_of(db, other)), + TypeRelation::Assignability => ConstraintSet::from(self.is_assignable_to(db, other)), } } @@ -982,17 +982,17 @@ impl<'db> FunctionType<'db> { && self.signature(db).is_assignable_to(db, other.signature(db)) } - pub(crate) fn is_equivalent_to_impl>( + pub(crate) fn is_equivalent_to_impl( self, db: &'db dyn Db, other: Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { + visitor: &IsEquivalentVisitor<'db>, + ) -> ConstraintSet<'db> { if self.normalized(db) == other.normalized(db) { - return C::always_satisfiable(db); + return ConstraintSet::from(true); } if self.literal(db) != other.literal(db) { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } let self_signature = self.signature(db); let other_signature = other.signature(db); diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 12f40c6e21..9b87da7fd9 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -1,5 +1,7 @@ use std::borrow::Cow; +use crate::types::constraints::ConstraintSet; + use ruff_db::parsed::ParsedModuleRef; use ruff_python_ast as ast; use rustc_hash::FxHashMap; @@ -9,7 +11,6 @@ use crate::semantic_index::definition::Definition; use crate::semantic_index::scope::{FileScopeId, NodeWithScopeKind}; use crate::types::class::ClassType; use crate::types::class_base::ClassBase; -use crate::types::constraints::Constraints; use crate::types::infer::infer_definition_types; use crate::types::instance::{Protocol, ProtocolInstanceType}; use crate::types::signatures::{Parameter, Parameters, Signature}; @@ -464,14 +465,14 @@ pub(super) fn walk_specialization<'db, V: super::visitor::TypeVisitor<'db> + ?Si } } -fn is_subtype_in_invariant_position<'db, C: Constraints<'db>>( +fn is_subtype_in_invariant_position<'db>( db: &'db dyn Db, derived_type: &Type<'db>, derived_materialization: MaterializationKind, base_type: &Type<'db>, base_materialization: MaterializationKind, - visitor: &HasRelationToVisitor<'db, C>, -) -> C { + visitor: &HasRelationToVisitor<'db>, +) -> ConstraintSet<'db> { let derived_top = derived_type.top_materialization(db); let derived_bottom = derived_type.bottom_materialization(db); let base_top = base_type.top_materialization(db); @@ -520,15 +521,15 @@ fn is_subtype_in_invariant_position<'db, C: Constraints<'db>>( /// Whether two types encountered in an invariant position /// have a relation (subtyping or assignability), taking into account /// that the two types may come from a top or bottom materialization. -fn has_relation_in_invariant_position<'db, C: Constraints<'db>>( +fn has_relation_in_invariant_position<'db>( db: &'db dyn Db, derived_type: &Type<'db>, derived_materialization: Option, base_type: &Type<'db>, base_materialization: Option, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db, C>, -) -> C { + visitor: &HasRelationToVisitor<'db>, +) -> ConstraintSet<'db> { match (derived_materialization, base_materialization, relation) { // Top and bottom materializations are fully static types, so subtyping // is the same as assignability. @@ -791,16 +792,16 @@ impl<'db> Specialization<'db> { ) } - pub(crate) fn has_relation_to_impl>( + pub(crate) fn has_relation_to_impl( self, db: &'db dyn Db, other: Self, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { let generic_context = self.generic_context(db); if generic_context != other.generic_context(db) { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } if let (Some(self_tuple), Some(other_tuple)) = (self.tuple_inner(db), other.tuple_inner(db)) @@ -811,7 +812,7 @@ impl<'db> Specialization<'db> { let self_materialization_kind = self.materialization_kind(db); let other_materialization_kind = other.materialization_kind(db); - let mut result = C::always_satisfiable(db); + let mut result = ConstraintSet::from(true); for ((bound_typevar, self_type), other_type) in (generic_context.variables(db).into_iter()) .zip(self.types(db)) .zip(other.types(db)) @@ -824,7 +825,7 @@ impl<'db> Specialization<'db> { { match relation { TypeRelation::Assignability => continue, - TypeRelation::Subtyping => return C::unsatisfiable(db), + TypeRelation::Subtyping => return ConstraintSet::from(false), } } @@ -850,9 +851,9 @@ impl<'db> Specialization<'db> { TypeVarVariance::Contravariant => { other_type.has_relation_to_impl(db, *self_type, relation, visitor) } - TypeVarVariance::Bivariant => C::always_satisfiable(db), + TypeVarVariance::Bivariant => ConstraintSet::from(true), }; - if result.intersect(db, compatible).is_never_satisfied(db) { + if result.intersect(db, &compatible).is_never_satisfied() { return result; } } @@ -860,21 +861,21 @@ impl<'db> Specialization<'db> { result } - pub(crate) fn is_equivalent_to_impl>( + pub(crate) fn is_equivalent_to_impl( self, db: &'db dyn Db, other: Specialization<'db>, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { + visitor: &IsEquivalentVisitor<'db>, + ) -> ConstraintSet<'db> { if self.materialization_kind(db) != other.materialization_kind(db) { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } let generic_context = self.generic_context(db); if generic_context != other.generic_context(db) { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } - let mut result = C::always_satisfiable(db); + let mut result = ConstraintSet::from(true); for ((bound_typevar, self_type), other_type) in (generic_context.variables(db).into_iter()) .zip(self.types(db)) .zip(other.types(db)) @@ -891,19 +892,19 @@ impl<'db> Specialization<'db> { | TypeVarVariance::Contravariant => { self_type.is_equivalent_to_impl(db, *other_type, visitor) } - TypeVarVariance::Bivariant => C::always_satisfiable(db), + TypeVarVariance::Bivariant => ConstraintSet::from(true), }; - if result.intersect(db, compatible).is_never_satisfied(db) { + if result.intersect(db, &compatible).is_never_satisfied() { return result; } } match (self.tuple_inner(db), other.tuple_inner(db)) { - (Some(_), None) | (None, Some(_)) => return C::unsatisfiable(db), + (Some(_), None) | (None, Some(_)) => return ConstraintSet::from(false), (None, None) => {} (Some(self_tuple), Some(other_tuple)) => { let compatible = self_tuple.is_equivalent_to_impl(db, other_tuple, visitor); - if result.intersect(db, compatible).is_never_satisfied(db) { + if result.intersect(db, &compatible).is_never_satisfied() { return result; } } diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 1f01021651..ab51e9de07 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -44,7 +44,6 @@ use crate::semantic_index::{ }; use crate::types::call::{Binding, Bindings, CallArguments, CallError, CallErrorKind}; use crate::types::class::{CodeGeneratorKind, FieldKind, MetaclassErrorKind, MethodDecorator}; -use crate::types::constraints::Constraints; use crate::types::context::{InNoTypeCheck, InferContext}; use crate::types::diagnostic::{ CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, CYCLIC_CLASS_DEFINITION, diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index 414d5d92e4..003e4ff459 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -7,7 +7,7 @@ use super::protocol_class::ProtocolInterface; use super::{BoundTypeVarInstance, ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance}; use crate::place::PlaceAndQualifiers; use crate::semantic_index::definition::Definition; -use crate::types::constraints::{ConstraintSet, Constraints, IteratorConstraintsExtension}; +use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension}; use crate::types::enums::is_single_member_enum; use crate::types::protocol_class::walk_protocol_interface; use crate::types::tuple::{TupleSpec, TupleType}; @@ -116,13 +116,13 @@ impl<'db> Type<'db> { } /// Return `true` if `self` conforms to the interface described by `protocol`. - pub(super) fn satisfies_protocol>( + pub(super) fn satisfies_protocol( self, db: &'db dyn Db, protocol: ProtocolInstanceType<'db>, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { let structurally_satisfied = if let Type::ProtocolInstance(self_protocol) = self { self_protocol.interface(db).extends_interface_of( db, @@ -155,7 +155,7 @@ impl<'db> Type<'db> { visitor, ) } else { - C::unsatisfiable(db) + ConstraintSet::from(false) } }) } @@ -336,15 +336,15 @@ impl<'db> NominalInstanceType<'db> { } } - pub(super) fn has_relation_to_impl>( + pub(super) fn has_relation_to_impl( self, db: &'db dyn Db, other: Self, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { match (self.0, other.0) { - (_, NominalInstanceInner::Object) => C::always_satisfiable(db), + (_, NominalInstanceInner::Object) => ConstraintSet::from(true), ( NominalInstanceInner::ExactTuple(tuple1), NominalInstanceInner::ExactTuple(tuple2), @@ -355,50 +355,47 @@ impl<'db> NominalInstanceType<'db> { } } - pub(super) fn is_equivalent_to_impl>( + pub(super) fn is_equivalent_to_impl( self, db: &'db dyn Db, other: Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { + visitor: &IsEquivalentVisitor<'db>, + ) -> ConstraintSet<'db> { match (self.0, other.0) { ( NominalInstanceInner::ExactTuple(tuple1), NominalInstanceInner::ExactTuple(tuple2), ) => tuple1.is_equivalent_to_impl(db, tuple2, visitor), (NominalInstanceInner::Object, NominalInstanceInner::Object) => { - C::always_satisfiable(db) + ConstraintSet::from(true) } (NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => { class1.is_equivalent_to_impl(db, class2, visitor) } - _ => C::unsatisfiable(db), + _ => ConstraintSet::from(false), } } - pub(super) fn is_disjoint_from_impl>( + pub(super) fn is_disjoint_from_impl( self, db: &'db dyn Db, other: Self, - visitor: &IsDisjointVisitor<'db, C>, - ) -> C { + visitor: &IsDisjointVisitor<'db>, + ) -> ConstraintSet<'db> { if self.is_object() || other.is_object() { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } - let mut result = C::unsatisfiable(db); + let mut result = ConstraintSet::from(false); if let Some(self_spec) = self.tuple_spec(db) { if let Some(other_spec) = other.tuple_spec(db) { let compatible = self_spec.is_disjoint_from_impl(db, &other_spec, visitor); - if result.union(db, compatible).is_always_satisfied(db) { + if result.union(db, compatible).is_always_satisfied() { return result; } } } result.or(db, || { - C::from_bool( - db, - !(self.class(db)).could_coexist_in_mro_with(db, other.class(db)), - ) + ConstraintSet::from(!(self.class(db)).could_coexist_in_mro_with(db, other.class(db))) }) } @@ -589,9 +586,9 @@ impl<'db> ProtocolInstanceType<'db> { db, protocol, TypeRelation::Subtyping, - &HasRelationToVisitor::new(ConstraintSet::always_satisfiable(db)), + &HasRelationToVisitor::default(), ) - .is_always_satisfied(db) + .is_always_satisfied() } #[expect(clippy::trivially_copy_pass_by_ref)] @@ -641,20 +638,20 @@ impl<'db> ProtocolInstanceType<'db> { /// Return `true` if this protocol type is equivalent to the protocol `other`. /// /// TODO: consider the types of the members as well as their existence - pub(super) fn is_equivalent_to_impl>( + pub(super) fn is_equivalent_to_impl( self, db: &'db dyn Db, other: Self, - _visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { + _visitor: &IsEquivalentVisitor<'db>, + ) -> ConstraintSet<'db> { if self == other { - return C::always_satisfiable(db); + return ConstraintSet::from(true); } let self_normalized = self.normalized(db); if self_normalized == Type::ProtocolInstance(other) { - return C::always_satisfiable(db); + return ConstraintSet::from(true); } - C::from_bool(db, self_normalized == other.normalized(db)) + ConstraintSet::from(self_normalized == other.normalized(db)) } /// Return `true` if this protocol type is disjoint from the protocol `other`. @@ -662,13 +659,13 @@ impl<'db> ProtocolInstanceType<'db> { /// TODO: a protocol `X` is disjoint from a protocol `Y` if `X` and `Y` /// have a member with the same name but disjoint types #[expect(clippy::unused_self)] - pub(super) fn is_disjoint_from_impl>( + pub(super) fn is_disjoint_from_impl( self, - db: &'db dyn Db, + _db: &'db dyn Db, _other: Self, - _visitor: &IsDisjointVisitor<'db, C>, - ) -> C { - C::unsatisfiable(db) + _visitor: &IsDisjointVisitor<'db>, + ) -> ConstraintSet<'db> { + ConstraintSet::from(false) } pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> { diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 3b8cea026d..f9870203e7 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -21,7 +21,7 @@ use crate::{ FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, KnownFunction, NormalizedVisitor, PropertyInstanceType, Signature, Type, TypeMapping, TypeQualifiers, TypeRelation, VarianceInferable, - constraints::{Constraints, IteratorConstraintsExtension}, + constraints::{ConstraintSet, IteratorConstraintsExtension}, signatures::{Parameter, Parameters}, }, }; @@ -234,17 +234,17 @@ impl<'db> ProtocolInterface<'db> { /// all members on `other` are also members of `self`. /// /// TODO: this method should consider the types of the members as well as their names. - pub(super) fn extends_interface_of>( + pub(super) fn extends_interface_of( self, db: &'db dyn Db, other: Self, _relation: TypeRelation, - _visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + _visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { // TODO: This could just return a bool as written, but this form is what will be needed to // combine the constraints when we do assignability checks on each member. other.inner(db).keys().when_all(db, |member_name| { - C::from_bool(db, self.inner(db).contains_key(member_name)) + ConstraintSet::from(self.inner(db).contains_key(member_name)) }) } @@ -514,51 +514,47 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { } } - pub(super) fn has_disjoint_type_from>( + pub(super) fn has_disjoint_type_from( &self, db: &'db dyn Db, other: Type<'db>, - visitor: &IsDisjointVisitor<'db, C>, - ) -> C { + visitor: &IsDisjointVisitor<'db>, + ) -> ConstraintSet<'db> { match &self.kind { // TODO: implement disjointness for property/method members as well as attribute members - ProtocolMemberKind::Property(_) | ProtocolMemberKind::Method(_) => C::unsatisfiable(db), + ProtocolMemberKind::Property(_) | ProtocolMemberKind::Method(_) => { + ConstraintSet::from(false) + } ProtocolMemberKind::Other(ty) => ty.is_disjoint_from_impl(db, other, visitor), } } /// Return `true` if `other` contains an attribute/method/property that satisfies /// the part of the interface defined by this protocol member. - pub(super) fn is_satisfied_by>( + pub(super) fn is_satisfied_by( &self, db: &'db dyn Db, other: Type<'db>, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { match &self.kind { // TODO: consider the types of the attribute on `other` for method members - ProtocolMemberKind::Method(_) => C::from_bool( - db, - matches!( - other.to_meta_type(db).member(db, self.name).place, - Place::Type(ty, Boundness::Bound) - if ty.is_assignable_to(db, CallableType::single(db, Signature::dynamic(Type::any()))) - ), - ), + ProtocolMemberKind::Method(_) => ConstraintSet::from(matches!( + other.to_meta_type(db).member(db, self.name).place, + Place::Type(ty, Boundness::Bound) + if ty.is_assignable_to(db, CallableType::single(db, Signature::dynamic(Type::any()))) + )), // TODO: consider the types of the attribute on `other` for property members - ProtocolMemberKind::Property(_) => C::from_bool( - db, - matches!( - other.member(db, self.name).place, - Place::Type(_, Boundness::Bound) - ), - ), + ProtocolMemberKind::Property(_) => ConstraintSet::from(matches!( + other.member(db, self.name).place, + Place::Type(_, Boundness::Bound) + )), ProtocolMemberKind::Other(member_type) => { let Place::Type(attribute_type, Boundness::Bound) = other.member(db, self.name).place else { - return C::unsatisfiable(db); + return ConstraintSet::from(false); }; member_type .has_relation_to_impl(db, attribute_type, relation, visitor) diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index f8f0fcb2f5..115b6b46de 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -17,7 +17,7 @@ use smallvec::{SmallVec, smallvec_inline}; use super::{DynamicType, Type, TypeVarVariance, definition_expression_type}; use crate::semantic_index::definition::Definition; -use crate::types::constraints::{ConstraintSet, Constraints, IteratorConstraintsExtension}; +use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension}; use crate::types::generics::{GenericContext, walk_generic_context}; use crate::types::{ ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, @@ -112,16 +112,15 @@ impl<'db> CallableSignature<'db> { /// /// See [`Type::is_subtype_of`] for more details. pub(crate) fn is_subtype_of(&self, db: &'db dyn Db, other: &Self) -> bool { - self.is_subtype_of_impl::(db, other) - .is_always_satisfied(db) + self.is_subtype_of_impl(db, other).is_always_satisfied() } - fn is_subtype_of_impl>(&self, db: &'db dyn Db, other: &Self) -> C { + fn is_subtype_of_impl(&self, db: &'db dyn Db, other: &Self) -> ConstraintSet<'db> { self.has_relation_to_impl( db, other, TypeRelation::Subtyping, - &HasRelationToVisitor::new(C::always_satisfiable(db)), + &HasRelationToVisitor::default(), ) } @@ -133,30 +132,30 @@ impl<'db> CallableSignature<'db> { db, other, TypeRelation::Assignability, - &HasRelationToVisitor::new(ConstraintSet::always_satisfiable(db)), + &HasRelationToVisitor::default(), ) - .is_always_satisfied(db) + .is_always_satisfied() } - pub(crate) fn has_relation_to_impl>( + pub(crate) fn has_relation_to_impl( &self, db: &'db dyn Db, other: &Self, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { Self::has_relation_to_inner(db, &self.overloads, &other.overloads, relation, visitor) } /// Implementation of subtyping and assignability between two, possible overloaded, callable /// types. - fn has_relation_to_inner>( + fn has_relation_to_inner( db: &'db dyn Db, self_signatures: &[Signature<'db>], other_signatures: &[Signature<'db>], relation: TypeRelation, - visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { match (self_signatures, other_signatures) { ([self_signature], [other_signature]) => { // Base case: both callable types contain a single signature. @@ -201,12 +200,12 @@ impl<'db> CallableSignature<'db> { /// Check whether this callable type is equivalent to another callable type. /// /// See [`Type::is_equivalent_to`] for more details. - pub(crate) fn is_equivalent_to_impl>( + pub(crate) fn is_equivalent_to_impl( &self, db: &'db dyn Db, other: &Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { + visitor: &IsEquivalentVisitor<'db>, + ) -> ConstraintSet<'db> { match (self.overloads.as_slice(), other.overloads.as_slice()) { ([self_signature], [other_signature]) => { // Common case: both callable types contain a single signature, use the custom @@ -215,9 +214,9 @@ impl<'db> CallableSignature<'db> { } (_, _) => { if self == other { - return C::always_satisfiable(db); + return ConstraintSet::from(true); } - self.is_subtype_of_impl::(db, other) + self.is_subtype_of_impl(db, other) .and(db, || other.is_subtype_of_impl(db, self)) } } @@ -533,27 +532,30 @@ impl<'db> Signature<'db> { /// Return `true` if `self` has exactly the same set of possible static materializations as /// `other` (if `self` represents the same set of possible sets of possible runtime objects as /// `other`). - pub(crate) fn is_equivalent_to_impl>( + pub(crate) fn is_equivalent_to_impl( &self, db: &'db dyn Db, other: &Signature<'db>, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { - let mut result = C::always_satisfiable(db); + visitor: &IsEquivalentVisitor<'db>, + ) -> ConstraintSet<'db> { + let mut result = ConstraintSet::from(true); let mut check_types = |self_type: Option>, other_type: Option>| { let self_type = self_type.unwrap_or(Type::unknown()); let other_type = other_type.unwrap_or(Type::unknown()); !result - .intersect(db, self_type.is_equivalent_to_impl(db, other_type, visitor)) - .is_never_satisfied(db) + .intersect( + db, + &self_type.is_equivalent_to_impl(db, other_type, visitor), + ) + .is_never_satisfied() }; if self.parameters.is_gradual() != other.parameters.is_gradual() { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } if self.parameters.len() != other.parameters.len() { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } if !check_types(self.return_ty, other.return_ty) { @@ -601,7 +603,7 @@ impl<'db> Signature<'db> { (ParameterKind::KeywordVariadic { .. }, ParameterKind::KeywordVariadic { .. }) => {} - _ => return C::unsatisfiable(db), + _ => return ConstraintSet::from(false), } if !check_types( @@ -616,13 +618,13 @@ impl<'db> Signature<'db> { } /// Implementation of subtyping and assignability for signature. - fn has_relation_to_impl>( + fn has_relation_to_impl( &self, db: &'db dyn Db, other: &Signature<'db>, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { /// A helper struct to zip two slices of parameters together that provides control over the /// two iterators individually. It also keeps track of the current parameter in each /// iterator. @@ -684,13 +686,16 @@ impl<'db> Signature<'db> { } } - let mut result = C::always_satisfiable(db); + let mut result = ConstraintSet::from(true); let mut check_types = |type1: Option>, type2: Option>| { let type1 = type1.unwrap_or(Type::unknown()); let type2 = type2.unwrap_or(Type::unknown()); !result - .intersect(db, type1.has_relation_to_impl(db, type2, relation, visitor)) - .is_never_satisfied(db) + .intersect( + db, + &type1.has_relation_to_impl(db, type2, relation, visitor), + ) + .is_never_satisfied() }; // Return types are covariant. @@ -710,13 +715,13 @@ impl<'db> Signature<'db> { .keyword_variadic() .is_some_and(|(_, param)| param.annotated_type().is_some_and(|ty| ty.is_object())) { - return C::always_satisfiable(db); + return ConstraintSet::from(true); } // If either of the parameter lists is gradual (`...`), then it is assignable to and from // any other parameter list, but not a subtype or supertype of any other parameter list. if self.parameters.is_gradual() || other.parameters.is_gradual() { - return C::from_bool(db, relation.is_assignability()); + return ConstraintSet::from(relation.is_assignability()); } let mut parameters = ParametersZip { @@ -754,7 +759,7 @@ impl<'db> Signature<'db> { // `other`, then the non-variadic parameters in `self` must have a default // value. if default_type.is_none() { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } } ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => { @@ -766,7 +771,7 @@ impl<'db> Signature<'db> { EitherOrBoth::Right(_) => { // If there are more parameters in `other` than in `self`, then `self` is not a // subtype of `other`. - return C::unsatisfiable(db); + return ConstraintSet::from(false); } EitherOrBoth::Both(self_parameter, other_parameter) => { @@ -786,7 +791,7 @@ impl<'db> Signature<'db> { }, ) => { if self_default.is_none() && other_default.is_some() { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } if !check_types( other_parameter.annotated_type(), @@ -807,11 +812,11 @@ impl<'db> Signature<'db> { }, ) => { if self_name != other_name { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } // The following checks are the same as positional-only parameters. if self_default.is_none() && other_default.is_some() { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } if !check_types( other_parameter.annotated_type(), @@ -896,7 +901,7 @@ impl<'db> Signature<'db> { break; } - _ => return C::unsatisfiable(db), + _ => return ConstraintSet::from(false), } } } @@ -930,7 +935,7 @@ impl<'db> Signature<'db> { // previous loop. They cannot be matched against any parameter in `other` which // only contains keyword-only and keyword-variadic parameters so the subtype // relation is invalid. - return C::unsatisfiable(db); + return ConstraintSet::from(false); } ParameterKind::Variadic { .. } => {} } @@ -957,7 +962,7 @@ impl<'db> Signature<'db> { .. } => { if self_default.is_none() && other_default.is_some() { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } if !check_types( other_parameter.annotated_type(), @@ -978,14 +983,14 @@ impl<'db> Signature<'db> { return result; } } else { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } } ParameterKind::KeywordVariadic { .. } => { let Some(self_keyword_variadic_type) = self_keyword_variadic else { // For a `self <: other` relationship, if `other` has a keyword variadic // parameter, `self` must also have a keyword variadic parameter. - return C::unsatisfiable(db); + return ConstraintSet::from(false); }; if !check_types(other_parameter.annotated_type(), self_keyword_variadic_type) { return result; @@ -993,7 +998,7 @@ impl<'db> Signature<'db> { } _ => { // This can only occur in case of a syntax error. - return C::unsatisfiable(db); + return ConstraintSet::from(false); } } } @@ -1002,7 +1007,7 @@ impl<'db> Signature<'db> { // optional otherwise the subtype relation is invalid. for (_, self_parameter) in self_keywords { if self_parameter.default_type().is_none() { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } } diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index 35ecbac7ee..1b0db0c808 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -1,6 +1,6 @@ use crate::place::PlaceAndQualifiers; use crate::semantic_index::definition::Definition; -use crate::types::constraints::Constraints; +use crate::types::constraints::ConstraintSet; use crate::types::variance::VarianceInferable; use crate::types::{ ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassType, DynamicType, @@ -129,22 +129,22 @@ impl<'db> SubclassOfType<'db> { } /// Return `true` if `self` has a certain relation to `other`. - pub(crate) fn has_relation_to_impl>( + pub(crate) fn has_relation_to_impl( self, db: &'db dyn Db, other: SubclassOfType<'db>, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { match (self.subclass_of, other.subclass_of) { (SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => { - C::from_bool(db, relation.is_assignability()) + ConstraintSet::from(relation.is_assignability()) } (SubclassOfInner::Dynamic(_), SubclassOfInner::Class(other_class)) => { - C::from_bool(db, other_class.is_object(db) || relation.is_assignability()) + ConstraintSet::from(other_class.is_object(db) || relation.is_assignability()) } (SubclassOfInner::Class(_), SubclassOfInner::Dynamic(_)) => { - C::from_bool(db, relation.is_assignability()) + ConstraintSet::from(relation.is_assignability()) } // For example, `type[bool]` describes all possible runtime subclasses of the class `bool`, @@ -159,18 +159,18 @@ impl<'db> SubclassOfType<'db> { /// Return` true` if `self` is a disjoint type from `other`. /// /// See [`Type::is_disjoint_from`] for more details. - pub(crate) fn is_disjoint_from_impl>( + pub(crate) fn is_disjoint_from_impl( self, db: &'db dyn Db, other: Self, - _visitor: &IsDisjointVisitor<'db, C>, - ) -> C { + _visitor: &IsDisjointVisitor<'db>, + ) -> ConstraintSet<'db> { match (self.subclass_of, other.subclass_of) { (SubclassOfInner::Dynamic(_), _) | (_, SubclassOfInner::Dynamic(_)) => { - C::unsatisfiable(db) + ConstraintSet::from(false) } (SubclassOfInner::Class(self_class), SubclassOfInner::Class(other_class)) => { - C::from_bool(db, !self_class.could_coexist_in_mro_with(db, other_class)) + ConstraintSet::from(!self_class.could_coexist_in_mro_with(db, other_class)) } } } diff --git a/crates/ty_python_semantic/src/types/tuple.rs b/crates/ty_python_semantic/src/types/tuple.rs index cb947d2695..e85fd9902f 100644 --- a/crates/ty_python_semantic/src/types/tuple.rs +++ b/crates/ty_python_semantic/src/types/tuple.rs @@ -24,7 +24,7 @@ use itertools::{Either, EitherOrBoth, Itertools}; use crate::semantic_index::definition::Definition; use crate::types::Truthiness; use crate::types::class::{ClassType, KnownClass}; -use crate::types::constraints::{Constraints, IteratorConstraintsExtension}; +use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension}; use crate::types::{ ApplyTypeMappingVisitor, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, Type, TypeMapping, TypeRelation, @@ -253,23 +253,23 @@ impl<'db> TupleType<'db> { .find_legacy_typevars_impl(db, binding_context, typevars, visitor); } - pub(crate) fn has_relation_to_impl>( + pub(crate) fn has_relation_to_impl( self, db: &'db dyn Db, other: Self, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { self.tuple(db) .has_relation_to_impl(db, other.tuple(db), relation, visitor) } - pub(crate) fn is_equivalent_to_impl>( + pub(crate) fn is_equivalent_to_impl( self, db: &'db dyn Db, other: Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { + visitor: &IsEquivalentVisitor<'db>, + ) -> ConstraintSet<'db> { self.tuple(db) .is_equivalent_to_impl(db, other.tuple(db), visitor) } @@ -411,47 +411,49 @@ impl<'db> FixedLengthTuple> { } } - fn has_relation_to_impl>( + fn has_relation_to_impl( &self, db: &'db dyn Db, other: &Tuple>, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { match other { - Tuple::Fixed(other) => C::from_bool(db, self.0.len() == other.0.len()).and(db, || { - (self.0.iter().zip(&other.0)).when_all(db, |(self_ty, other_ty)| { - self_ty.has_relation_to_impl(db, *other_ty, relation, visitor) + Tuple::Fixed(other) => { + ConstraintSet::from(self.0.len() == other.0.len()).and(db, || { + (self.0.iter().zip(&other.0)).when_all(db, |(self_ty, other_ty)| { + self_ty.has_relation_to_impl(db, *other_ty, relation, visitor) + }) }) - }), + } Tuple::Variable(other) => { // This tuple must have enough elements to match up with the other tuple's prefix // and suffix, and each of those elements must pairwise satisfy the relation. - let mut result = C::always_satisfiable(db); + let mut result = ConstraintSet::from(true); let mut self_iter = self.0.iter(); for other_ty in &other.prefix { let Some(self_ty) = self_iter.next() else { - return C::unsatisfiable(db); + return ConstraintSet::from(false); }; let element_constraints = self_ty.has_relation_to_impl(db, *other_ty, relation, visitor); if result - .intersect(db, element_constraints) - .is_never_satisfied(db) + .intersect(db, &element_constraints) + .is_never_satisfied() { return result; } } for other_ty in other.suffix.iter().rev() { let Some(self_ty) = self_iter.next_back() else { - return C::unsatisfiable(db); + return ConstraintSet::from(false); }; let element_constraints = self_ty.has_relation_to_impl(db, *other_ty, relation, visitor); if result - .intersect(db, element_constraints) - .is_never_satisfied(db) + .intersect(db, &element_constraints) + .is_never_satisfied() { return result; } @@ -468,13 +470,13 @@ impl<'db> FixedLengthTuple> { } } - fn is_equivalent_to_impl>( + fn is_equivalent_to_impl( &self, db: &'db dyn Db, other: &Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { - C::from_bool(db, self.0.len() == other.0.len()).and(db, || { + visitor: &IsEquivalentVisitor<'db>, + ) -> ConstraintSet<'db> { + ConstraintSet::from(self.0.len() == other.0.len()).and(db, || { (self.0.iter()) .zip(&other.0) .when_all(db, |(self_ty, other_ty)| { @@ -732,13 +734,13 @@ impl<'db> VariableLengthTuple> { } } - fn has_relation_to_impl>( + fn has_relation_to_impl( &self, db: &'db dyn Db, other: &Tuple>, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { match other { Tuple::Fixed(other) => { // The `...` length specifier of a variable-length tuple type is interpreted @@ -753,23 +755,23 @@ impl<'db> VariableLengthTuple> { // length. if relation == TypeRelation::Subtyping || !matches!(self.variable, Type::Dynamic(_)) { - return C::unsatisfiable(db); + return ConstraintSet::from(false); } // In addition, the other tuple must have enough elements to match up with this // tuple's prefix and suffix, and each of those elements must pairwise satisfy the // relation. - let mut result = C::always_satisfiable(db); + let mut result = ConstraintSet::from(true); let mut other_iter = other.elements().copied(); for self_ty in self.prenormalized_prefix_elements(db, None) { let Some(other_ty) = other_iter.next() else { - return C::unsatisfiable(db); + return ConstraintSet::from(false); }; let element_constraints = self_ty.has_relation_to_impl(db, other_ty, relation, visitor); if result - .intersect(db, element_constraints) - .is_never_satisfied(db) + .intersect(db, &element_constraints) + .is_never_satisfied() { return result; } @@ -777,13 +779,13 @@ impl<'db> VariableLengthTuple> { let suffix: Vec<_> = self.prenormalized_suffix_elements(db, None).collect(); for self_ty in suffix.iter().rev() { let Some(other_ty) = other_iter.next_back() else { - return C::unsatisfiable(db); + return ConstraintSet::from(false); }; let element_constraints = self_ty.has_relation_to_impl(db, other_ty, relation, visitor); if result - .intersect(db, element_constraints) - .is_never_satisfied(db) + .intersect(db, &element_constraints) + .is_never_satisfied() { return result; } @@ -807,7 +809,7 @@ impl<'db> VariableLengthTuple> { // The overlapping parts of the prefixes and suffixes must satisfy the relation. // Any remaining parts must satisfy the relation with the other tuple's // variable-length part. - let mut result = C::always_satisfiable(db); + let mut result = ConstraintSet::from(true); let pairwise = (self.prenormalized_prefix_elements(db, self_prenormalize_variable)) .zip_longest( other.prenormalized_prefix_elements(db, other_prenormalize_variable), @@ -823,13 +825,10 @@ impl<'db> VariableLengthTuple> { EitherOrBoth::Right(_) => { // The rhs has a required element that the lhs is not guaranteed to // provide. - return C::unsatisfiable(db); + return ConstraintSet::from(false); } }; - if result - .intersect(db, pair_constraints) - .is_never_satisfied(db) - { + if result.intersect(db, &pair_constraints).is_never_satisfied() { return result; } } @@ -852,13 +851,10 @@ impl<'db> VariableLengthTuple> { EitherOrBoth::Right(_) => { // The rhs has a required element that the lhs is not guaranteed to // provide. - return C::unsatisfiable(db); + return ConstraintSet::from(false); } }; - if result - .intersect(db, pair_constraints) - .is_never_satisfied(db) - { + if result.intersect(db, &pair_constraints).is_never_satisfied() { return result; } } @@ -872,12 +868,12 @@ impl<'db> VariableLengthTuple> { } } - fn is_equivalent_to_impl>( + fn is_equivalent_to_impl( &self, db: &'db dyn Db, other: &Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { + visitor: &IsEquivalentVisitor<'db>, + ) -> ConstraintSet<'db> { self.variable .is_equivalent_to_impl(db, other.variable, visitor) .and(db, || { @@ -887,7 +883,9 @@ impl<'db> VariableLengthTuple> { EitherOrBoth::Both(self_ty, other_ty) => { self_ty.is_equivalent_to_impl(db, other_ty, visitor) } - EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => C::unsatisfiable(db), + EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => { + ConstraintSet::from(false) + } }) }) .and(db, || { @@ -897,7 +895,9 @@ impl<'db> VariableLengthTuple> { EitherOrBoth::Both(self_ty, other_ty) => { self_ty.is_equivalent_to_impl(db, other_ty, visitor) } - EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => C::unsatisfiable(db), + EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => { + ConstraintSet::from(false) + } }) }) } @@ -1076,13 +1076,13 @@ impl<'db> Tuple> { } } - fn has_relation_to_impl>( + fn has_relation_to_impl( &self, db: &'db dyn Db, other: &Self, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db, C>, - ) -> C { + visitor: &HasRelationToVisitor<'db>, + ) -> ConstraintSet<'db> { match self { Tuple::Fixed(self_tuple) => { self_tuple.has_relation_to_impl(db, other, relation, visitor) @@ -1093,12 +1093,12 @@ impl<'db> Tuple> { } } - fn is_equivalent_to_impl>( + fn is_equivalent_to_impl( &self, db: &'db dyn Db, other: &Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { + visitor: &IsEquivalentVisitor<'db>, + ) -> ConstraintSet<'db> { match (self, other) { (Tuple::Fixed(self_tuple), Tuple::Fixed(other_tuple)) => { self_tuple.is_equivalent_to_impl(db, other_tuple, visitor) @@ -1107,35 +1107,35 @@ impl<'db> Tuple> { self_tuple.is_equivalent_to_impl(db, other_tuple, visitor) } (Tuple::Fixed(_), Tuple::Variable(_)) | (Tuple::Variable(_), Tuple::Fixed(_)) => { - C::unsatisfiable(db) + ConstraintSet::from(false) } } } - pub(super) fn is_disjoint_from_impl>( + pub(super) fn is_disjoint_from_impl( &self, db: &'db dyn Db, other: &Self, - visitor: &IsDisjointVisitor<'db, C>, - ) -> C { + visitor: &IsDisjointVisitor<'db>, + ) -> ConstraintSet<'db> { // Two tuples with an incompatible number of required elements must always be disjoint. let (self_min, self_max) = self.len().size_hint(); let (other_min, other_max) = other.len().size_hint(); if self_max.is_some_and(|max| max < other_min) { - return C::always_satisfiable(db); + return ConstraintSet::from(true); } if other_max.is_some_and(|max| max < self_min) { - return C::always_satisfiable(db); + return ConstraintSet::from(true); } // If any of the required elements are pairwise disjoint, the tuples are disjoint as well. #[allow(clippy::items_after_statements)] - fn any_disjoint<'s, 'db, C: Constraints<'db>>( + fn any_disjoint<'s, 'db>( db: &'db dyn Db, a: impl IntoIterator>, b: impl IntoIterator>, - visitor: &IsDisjointVisitor<'db, C>, - ) -> C + visitor: &IsDisjointVisitor<'db>, + ) -> ConstraintSet<'db> where 'db: 's, {