[ty] Remove the Constraints trait (#20355)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

This PR removes the `Constraints` trait. We removed the `bool`
implementation several weeks back, and are using `ConstraintSet`
everywhere. There have been discussions about trying to include the
reason for an assignability failure as part of the result, but that
there are no concrete plans to do so soon, and it's not clear that we'll
need the `Constraints` trait to do that. (We can ideally just update the
`ConstraintSet` type directly.)

In the meantime, this just complicates the code for no good reason.

This PR is a pure refactoring, and contains no behavioral changes.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Douglas Creager 2025-09-11 20:55:28 -04:00 committed by GitHub
parent 36888198a6
commit 1cd8ab3f26
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 552 additions and 572 deletions

View file

@ -39,7 +39,7 @@ use crate::suppression::check_suppressions;
use crate::types::call::{Binding, Bindings, CallArguments, CallableBinding}; use crate::types::call::{Binding, Bindings, CallArguments, CallableBinding};
pub(crate) use crate::types::class_base::ClassBase; pub(crate) use crate::types::class_base::ClassBase;
use crate::types::constraints::{ use crate::types::constraints::{
ConstraintSet, Constraints, IteratorConstraintsExtension, OptionConstraintsExtension, ConstraintSet, IteratorConstraintsExtension, OptionConstraintsExtension,
}; };
use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder}; use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder};
use crate::types::diagnostic::{INVALID_AWAIT, INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION}; 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>>; pub(crate) type ApplyTypeMappingVisitor<'db> = TypeTransformer<'db, TypeMapping<'db, 'db>>;
/// A [`PairVisitor`] that is used in `has_relation_to` methods. /// A [`PairVisitor`] that is used in `has_relation_to` methods.
pub(crate) type HasRelationToVisitor<'db, C> = pub(crate) type HasRelationToVisitor<'db> =
CycleDetector<TypeRelation, (Type<'db>, Type<'db>, TypeRelation), C>; CycleDetector<TypeRelation, (Type<'db>, 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. /// 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; 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. /// 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; 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. /// A [`CycleDetector`] that is used in `find_legacy_typevars` methods.
pub(crate) type FindLegacyTypeVarsVisitor<'db> = CycleDetector<FindLegacyTypeVars, Type<'db>, ()>; pub(crate) type FindLegacyTypeVarsVisitor<'db> = CycleDetector<FindLegacyTypeVars, Type<'db>, ()>;
@ -535,43 +550,39 @@ impl<'db> PropertyInstanceType<'db> {
} }
} }
fn when_equivalent_to<C: Constraints<'db>>(self, db: &'db dyn Db, other: Self) -> C { fn when_equivalent_to(self, db: &'db dyn Db, other: Self) -> ConstraintSet<'db> {
self.is_equivalent_to_impl( self.is_equivalent_to_impl(db, other, &IsEquivalentVisitor::default())
db,
other,
&IsEquivalentVisitor::new(C::always_satisfiable(db)),
)
} }
fn is_equivalent_to_impl<C: Constraints<'db>>( fn is_equivalent_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
visitor: &IsEquivalentVisitor<'db, C>, visitor: &IsEquivalentVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
let getter_equivalence = if let Some(getter) = self.getter(db) { let getter_equivalence = if let Some(getter) = self.getter(db) {
let Some(other_getter) = other.getter(db) else { 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) getter.is_equivalent_to_impl(db, other_getter, visitor)
} else { } else {
if other.getter(db).is_some() { if other.getter(db).is_some() {
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
C::always_satisfiable(db) ConstraintSet::from(true)
}; };
let setter_equivalence = || { let setter_equivalence = || {
if let Some(setter) = self.setter(db) { if let Some(setter) = self.setter(db) {
let Some(other_setter) = other.setter(db) else { 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) setter.is_equivalent_to_impl(db, other_setter, visitor)
} else { } else {
if other.setter(db).is_some() { 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 /// intersection simplification dependent on the order in which elements are added), so we do
/// not use this more general definition of subtyping. /// not use this more general definition of subtyping.
pub(crate) fn is_subtype_of(self, db: &'db dyn Db, target: Type<'db>) -> bool { pub(crate) fn is_subtype_of(self, db: &'db dyn Db, target: Type<'db>) -> bool {
self.when_subtype_of::<ConstraintSet>(db, target) self.when_subtype_of(db, target).is_always_satisfied()
.is_always_satisfied(db)
} }
fn when_subtype_of<C: Constraints<'db>>(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) 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 /// [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 { pub(crate) fn is_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> bool {
self.when_assignable_to::<ConstraintSet>(db, target) self.when_assignable_to(db, target).is_always_satisfied()
.is_always_satisfied(db)
} }
fn when_assignable_to<C: Constraints<'db>>(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) self.has_relation_to(db, target, TypeRelation::Assignability)
} }
fn has_relation_to<C: Constraints<'db>>( fn has_relation_to(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
target: Type<'db>, target: Type<'db>,
relation: TypeRelation, relation: TypeRelation,
) -> C { ) -> ConstraintSet<'db> {
self.has_relation_to_impl( self.has_relation_to_impl(db, target, relation, &HasRelationToVisitor::default())
db,
target,
relation,
&HasRelationToVisitor::new(C::always_satisfiable(db)),
)
} }
fn has_relation_to_impl<C: Constraints<'db>>( fn has_relation_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
target: Type<'db>, target: Type<'db>,
relation: TypeRelation, relation: TypeRelation,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
// Subtyping implies assignability, so if subtyping is reflexive and the two types are // 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. // 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 // 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. // and unnecessary. This early return is only an optimisation.
if (relation.is_assignability() || self.subtyping_is_always_reflexive()) && self == target { if (relation.is_assignability() || self.subtyping_is_always_reflexive()) && self == target {
return C::always_satisfiable(db); return ConstraintSet::from(true);
} }
match (self, target) { match (self, target) {
// Everything is a subtype of `object`. // Everything is a subtype of `object`.
(_, Type::NominalInstance(instance)) if instance.is_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) => { (_, 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. // `Never` is the bottom type, the empty set.
// It is a subtype of all other types. // 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 // Dynamic is only a subtype of `object` and only a supertype of `Never`; both were
// handled above. It's always assignable, though. // handled above. It's always assignable, though.
(Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => { (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), || { (Type::TypeAlias(self_alias), _) => visitor.visit((self, target, relation), || {
@ -1483,7 +1487,7 @@ impl<'db> Type<'db> {
(Type::TypedDict(_), _) | (_, Type::TypedDict(_)) => { (Type::TypedDict(_), _) | (_, Type::TypedDict(_)) => {
// TODO: Implement assignability and subtyping for 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: // 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)) (Type::NonInferableTypeVar(_), Type::Union(union))
if union.elements(db).contains(&self) => if union.elements(db).contains(&self) =>
{ {
C::always_satisfiable(db) ConstraintSet::from(true)
} }
(Type::Intersection(intersection), Type::NonInferableTypeVar(_)) (Type::Intersection(intersection), Type::NonInferableTypeVar(_))
if intersection.positive(db).contains(&target) => if intersection.positive(db).contains(&target) =>
{ {
C::always_satisfiable(db) ConstraintSet::from(true)
} }
(Type::Intersection(intersection), Type::NonInferableTypeVar(_)) (Type::Intersection(intersection), Type::NonInferableTypeVar(_))
if intersection.negative(db).contains(&target) => 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 // 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(lhs_bound_typevar),
Type::NonInferableTypeVar(rhs_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 // 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 // the union of its constraints. An unbound, unconstrained, fully static typevar has an
@ -1546,12 +1550,12 @@ impl<'db> Type<'db> {
if !bound_typevar if !bound_typevar
.typevar(db) .typevar(db)
.constraints(db) .constraints(db)
.when_some_and(db, |constraints| { .when_some_and(|constraints| {
constraints.iter().when_all(db, |constraint| { constraints.iter().when_all(db, |constraint| {
self.has_relation_to_impl(db, *constraint, relation, visitor) 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, // 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, // 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 bound_typevar
.typevar(db) .typevar(db)
.constraints(db) .constraints(db)
.when_some_and(db, |constraints| { .when_some_and(|constraints| {
constraints.iter().when_all(db, |constraint| { constraints.iter().when_all(db, |constraint| {
self.has_relation_to_impl(db, *constraint, relation, visitor) 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. // `Never` is the bottom type, the empty set.
// Other than one unlikely edge case (TypeVars bound to `Never`), // Other than one unlikely edge case (TypeVars bound to `Never`),
// no other type is a subtype of or assignable 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| { (Type::Union(union), _) => union.elements(db).iter().when_all(db, |&elem_ty| {
elem_ty.has_relation_to_impl(db, target, relation, visitor) 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 // (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 // bound. This is true even if the bound is a final class, since the typevar can still
// be specialized to `Never`.) // be specialized to `Never`.)
(_, Type::NonInferableTypeVar(_)) => C::unsatisfiable(db), (_, Type::NonInferableTypeVar(_)) => ConstraintSet::from(false),
// TODO: Infer specializations here // 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__`. // 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. // 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::AlwaysFalsy) => ConstraintSet::from(left.bool(db).is_always_false()),
(left, Type::AlwaysTruthy) => C::from_bool(db, left.bool(db).is_always_true()), (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). // Currently, the only supertype of `AlwaysFalsy` and `AlwaysTruthy` is the universal set (object instance).
(Type::AlwaysFalsy | Type::AlwaysTruthy, _) => { (Type::AlwaysFalsy | Type::AlwaysTruthy, _) => {
target.when_equivalent_to(db, Type::object()) target.when_equivalent_to(db, Type::object())
@ -1652,13 +1656,13 @@ impl<'db> Type<'db> {
| Type::FunctionLiteral(_) | Type::FunctionLiteral(_)
| Type::ModuleLiteral(_) | Type::ModuleLiteral(_)
| Type::EnumLiteral(_), | Type::EnumLiteral(_),
) => C::unsatisfiable(db), ) => ConstraintSet::from(false),
(Type::Callable(self_callable), Type::Callable(other_callable)) => { (Type::Callable(self_callable), Type::Callable(other_callable)) => {
self_callable.has_relation_to_impl(db, other_callable, relation, visitor) 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) 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`. // 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`. // 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 // 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. // and the enum has only one member.
(Type::NominalInstance(_), Type::EnumLiteral(target_enum_literal)) => { (Type::NominalInstance(_), Type::EnumLiteral(target_enum_literal)) => {
if target_enum_literal.enum_class_instance(db) != self { 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, 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, // Except for the special `LiteralString` case above,
@ -1697,7 +1701,7 @@ impl<'db> Type<'db> {
| Type::ModuleLiteral(_) | Type::ModuleLiteral(_)
| Type::EnumLiteral(_), | 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) instance.has_relation_to_impl(db, target, relation, visitor)
}), }),
@ -1721,7 +1725,7 @@ impl<'db> Type<'db> {
(Type::DataclassDecorator(_) | Type::DataclassTransformer(_), _) => { (Type::DataclassDecorator(_) | Type::DataclassTransformer(_), _) => {
// TODO: Implement subtyping using an equivalent `Callable` type. // TODO: Implement subtyping using an equivalent `Callable` type.
C::unsatisfiable(db) ConstraintSet::from(false)
} }
// `TypeIs` is invariant. // `TypeIs` is invariant.
@ -1749,7 +1753,7 @@ impl<'db> Type<'db> {
.has_relation_to_impl(db, target, relation, visitor) .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(_), Type::BoundSuper(_)) => self.when_equivalent_to(db, target),
(Type::BoundSuper(_), _) => KnownClass::Super (Type::BoundSuper(_), _) => KnownClass::Super
@ -1769,7 +1773,7 @@ impl<'db> Type<'db> {
visitor, 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 (Type::GenericAlias(alias), Type::SubclassOf(target_subclass_ty)) => target_subclass_ty
.subclass_of() .subclass_of()
.into_class() .into_class()
@ -1781,7 +1785,7 @@ impl<'db> Type<'db> {
visitor, 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]`? // 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)) => { (Type::SubclassOf(self_subclass_ty), Type::SubclassOf(target_subclass_ty)) => {
@ -1804,7 +1808,7 @@ impl<'db> Type<'db> {
.to_instance(db) .to_instance(db)
.has_relation_to_impl(db, other, relation, visitor) .has_relation_to_impl(db, other, relation, visitor)
.or(db, || { .or(db, || {
C::from_bool(db, relation.is_assignability()).and(db, || { ConstraintSet::from(relation.is_assignability()).and(db, || {
other.has_relation_to_impl( other.has_relation_to_impl(
db, db,
KnownClass::Type.to_instance(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 // Other than the special cases enumerated above, `Instance` types and typevars are
// never subtypes of any other variants // 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 /// [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 { pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Type<'db>) -> bool {
self.when_equivalent_to::<ConstraintSet>(db, other) self.when_equivalent_to(db, other).is_always_satisfied()
.is_always_satisfied(db)
} }
fn when_equivalent_to<C: Constraints<'db>>(self, db: &'db dyn Db, other: Type<'db>) -> C { fn when_equivalent_to(self, db: &'db dyn Db, other: Type<'db>) -> ConstraintSet<'db> {
self.is_equivalent_to_impl( self.is_equivalent_to_impl(db, other, &IsEquivalentVisitor::default())
db,
other,
&IsEquivalentVisitor::new(C::always_satisfiable(db)),
)
} }
pub(crate) fn is_equivalent_to_impl<C: Constraints<'db>>( pub(crate) fn is_equivalent_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Type<'db>, other: Type<'db>,
visitor: &IsEquivalentVisitor<'db, C>, visitor: &IsEquivalentVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
if self == other { if self == other {
return C::always_satisfiable(db); return ConstraintSet::from(true);
} }
match (self, other) { match (self, other) {
// The `Divergent` type is a special type that is not equivalent to other kinds of dynamic types, // 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. // which prevents `Divergent` from being eliminated during union reduction.
(Type::Dynamic(_), Type::Dynamic(DynamicType::Divergent(_))) (Type::Dynamic(_), Type::Dynamic(DynamicType::Divergent(_)))
| (Type::Dynamic(DynamicType::Divergent(_)), Type::Dynamic(_)) => C::unsatisfiable(db), | (Type::Dynamic(DynamicType::Divergent(_)), Type::Dynamic(_)) => {
(Type::Dynamic(_), Type::Dynamic(_)) => C::always_satisfiable(db), ConstraintSet::from(false)
}
(Type::Dynamic(_), Type::Dynamic(_)) => ConstraintSet::from(true),
(Type::SubclassOf(first), Type::SubclassOf(second)) => { (Type::SubclassOf(first), Type::SubclassOf(second)) => {
match (first.subclass_of(), second.subclass_of()) { 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(_)) => { (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)) (Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n))
| (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => { | (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, // An instance of an enum class is equivalent to an enum literal of that class,
// if that enum has only has one member. // if that enum has only has one member.
(Type::NominalInstance(instance), Type::EnumLiteral(literal)) (Type::NominalInstance(instance), Type::EnumLiteral(literal))
| (Type::EnumLiteral(literal), Type::NominalInstance(instance)) => { | (Type::EnumLiteral(literal), Type::NominalInstance(instance)) => {
if literal.enum_class_instance(db) != 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)) => { (Type::PropertyInstance(left), Type::PropertyInstance(right)) => {
left.is_equivalent_to_impl(db, right, visitor) 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 /// Note: This function aims to have no false positives, but might return
/// wrong `false` answers in some cases. /// wrong `false` answers in some cases.
pub(crate) fn is_disjoint_from(self, db: &'db dyn Db, other: Type<'db>) -> bool { pub(crate) fn is_disjoint_from(self, db: &'db dyn Db, other: Type<'db>) -> bool {
self.when_disjoint_from::<ConstraintSet>(db, other) self.when_disjoint_from(db, other).is_always_satisfied()
.is_always_satisfied(db)
} }
fn when_disjoint_from<C: Constraints<'db>>(self, db: &'db dyn Db, other: Type<'db>) -> C { fn when_disjoint_from(self, db: &'db dyn Db, other: Type<'db>) -> ConstraintSet<'db> {
self.is_disjoint_from_impl(db, other, &IsDisjointVisitor::new(C::unsatisfiable(db))) self.is_disjoint_from_impl(db, other, &IsDisjointVisitor::default())
} }
pub(crate) fn is_disjoint_from_impl<C: Constraints<'db>>( pub(crate) fn is_disjoint_from_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Type<'db>, other: Type<'db>,
visitor: &IsDisjointVisitor<'db, C>, visitor: &IsDisjointVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
fn any_protocol_members_absent_or_disjoint<'db, C: Constraints<'db>>( fn any_protocol_members_absent_or_disjoint<'db>(
db: &'db dyn Db, db: &'db dyn Db,
protocol: ProtocolInstanceType<'db>, protocol: ProtocolInstanceType<'db>,
other: Type<'db>, other: Type<'db>,
visitor: &IsDisjointVisitor<'db, C>, visitor: &IsDisjointVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
protocol.interface(db).members(db).when_any(db, |member| { protocol.interface(db).members(db).when_any(db, |member| {
other other
.member(db, member.name()) .member(db, member.name())
.place .place
.ignore_possibly_unbound() .ignore_possibly_unbound()
.when_none_or(db, |attribute_type| { .when_none_or(|attribute_type| {
member.has_disjoint_type_from(db, attribute_type, visitor) member.has_disjoint_type_from(db, attribute_type, visitor)
}) })
}) })
} }
match (self, other) { 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), _) => { (Type::TypeAlias(alias), _) => {
let self_alias_ty = alias.value_type(db); let self_alias_ty = alias.value_type(db);
@ -2044,7 +2046,7 @@ impl<'db> Type<'db> {
(Type::TypedDict(_), _) | (_, Type::TypedDict(_)) => { (Type::TypedDict(_), _) | (_, Type::TypedDict(_)) => {
// TODO: Implement disjointness for 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 // 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(self_bound_typevar),
Type::NonInferableTypeVar(other_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)) (tvar @ Type::NonInferableTypeVar(_), Type::Intersection(intersection))
| (Type::Intersection(intersection), tvar @ Type::NonInferableTypeVar(_)) | (Type::Intersection(intersection), tvar @ Type::NonInferableTypeVar(_))
if intersection.negative(db).contains(&tvar) => 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 // 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) (Type::NonInferableTypeVar(bound_typevar), other)
| (other, Type::NonInferableTypeVar(bound_typevar)) => { | (other, Type::NonInferableTypeVar(bound_typevar)) => {
match bound_typevar.typevar(db).bound_or_constraints(db) { match bound_typevar.typevar(db).bound_or_constraints(db) {
None => C::unsatisfiable(db), None => ConstraintSet::from(false),
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
bound.is_disjoint_from_impl(db, other, visitor) bound.is_disjoint_from_impl(db, other, visitor)
} }
@ -2083,7 +2085,7 @@ impl<'db> Type<'db> {
} }
// TODO: Infer specializations here // 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 (Type::Union(union), other) | (other, Type::Union(union)) => union
.elements(db) .elements(db)
@ -2152,7 +2154,7 @@ impl<'db> Type<'db> {
| Type::GenericAlias(..) | Type::GenericAlias(..)
| Type::SpecialForm(..) | Type::SpecialForm(..)
| Type::KnownInstance(..)), | Type::KnownInstance(..)),
) => C::from_bool(db, left != right), ) => ConstraintSet::from(left != right),
( (
Type::SubclassOf(_), Type::SubclassOf(_),
@ -2181,16 +2183,16 @@ impl<'db> Type<'db> {
| Type::WrapperDescriptor(..) | Type::WrapperDescriptor(..)
| Type::ModuleLiteral(..), | Type::ModuleLiteral(..),
Type::SubclassOf(_), Type::SubclassOf(_),
) => C::always_satisfiable(db), ) => ConstraintSet::from(true),
(Type::AlwaysTruthy, ty) | (ty, Type::AlwaysTruthy) => { (Type::AlwaysTruthy, ty) | (ty, Type::AlwaysTruthy) => {
// `Truthiness::Ambiguous` may include `AlwaysTrue` as a subset, so it's not guaranteed to be disjoint. // `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`. // 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) => { (Type::AlwaysFalsy, ty) | (ty, Type::AlwaysFalsy) => {
// Similarly, they are only disjoint if `ty.bool() == AlwaysTrue`. // 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)) => { (Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => {
@ -2295,7 +2297,7 @@ impl<'db> Type<'db> {
Place::Type(attribute_type, _) => { Place::Type(attribute_type, _) => {
member.has_disjoint_type_from(db, attribute_type, visitor) 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::SubclassOf(subclass_of_ty), Type::ClassLiteral(class_b))
| (Type::ClassLiteral(class_b), Type::SubclassOf(subclass_of_ty)) => { | (Type::ClassLiteral(class_b), Type::SubclassOf(subclass_of_ty)) => {
match subclass_of_ty.subclass_of() { match subclass_of_ty.subclass_of() {
SubclassOfInner::Dynamic(_) => C::unsatisfiable(db), SubclassOfInner::Dynamic(_) => ConstraintSet::from(false),
SubclassOfInner::Class(class_a) => { SubclassOfInner::Class(class_a) => {
class_b.when_subclass_of::<C>(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::SubclassOf(subclass_of_ty), Type::GenericAlias(alias_b))
| (Type::GenericAlias(alias_b), Type::SubclassOf(subclass_of_ty)) => { | (Type::GenericAlias(alias_b), Type::SubclassOf(subclass_of_ty)) => {
match subclass_of_ty.subclass_of() { match subclass_of_ty.subclass_of() {
SubclassOfInner::Dynamic(_) => C::unsatisfiable(db), SubclassOfInner::Dynamic(_) => ConstraintSet::from(false),
SubclassOfInner::Class(class_a) => ClassType::from(alias_b) SubclassOfInner::Class(class_a) => ClassType::from(alias_b)
.when_subclass_of::<C>(db, class_a) .when_subclass_of(db, class_a)
.negate(db), .negate(db),
} }
} }
@ -2338,12 +2340,12 @@ impl<'db> Type<'db> {
(Type::SpecialForm(special_form), Type::NominalInstance(instance)) (Type::SpecialForm(special_form), Type::NominalInstance(instance))
| (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => { | (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::KnownInstance(known_instance), Type::NominalInstance(instance))
| (Type::NominalInstance(instance), Type::KnownInstance(known_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)) (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` // A `Type::BooleanLiteral()` must be an instance of exactly `bool`
// (it cannot be an instance of a `bool` subclass) // (it cannot be an instance of a `bool` subclass)
KnownClass::Bool KnownClass::Bool
.when_subclass_of::<C>(db, instance.class(db)) .when_subclass_of(db, instance.class(db))
.negate(db) .negate(db)
} }
(Type::BooleanLiteral(..) | Type::TypeIs(_), _) (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::IntLiteral(..), Type::NominalInstance(instance))
| (Type::NominalInstance(instance), Type::IntLiteral(..)) => { | (Type::NominalInstance(instance), Type::IntLiteral(..)) => {
// A `Type::IntLiteral()` must be an instance of exactly `int` // A `Type::IntLiteral()` must be an instance of exactly `int`
// (it cannot be an instance of an `int` subclass) // (it cannot be an instance of an `int` subclass)
KnownClass::Int KnownClass::Int
.when_subclass_of::<C>(db, instance.class(db)) .when_subclass_of(db, instance.class(db))
.negate(db) .negate(db)
} }
(Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => C::always_satisfiable(db), (Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => ConstraintSet::from(true),
(Type::StringLiteral(..), Type::LiteralString) (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::StringLiteral(..) | Type::LiteralString, Type::NominalInstance(instance))
| (Type::NominalInstance(instance), Type::StringLiteral(..) | Type::LiteralString) => { | (Type::NominalInstance(instance), Type::StringLiteral(..) | Type::LiteralString) => {
// A `Type::StringLiteral()` or a `Type::LiteralString` must be an instance of exactly `str` // A `Type::StringLiteral()` or a `Type::LiteralString` must be an instance of exactly `str`
// (it cannot be an instance of a `str` subclass) // (it cannot be an instance of a `str` subclass)
KnownClass::Str KnownClass::Str
.when_subclass_of::<C>(db, instance.class(db)) .when_subclass_of(db, instance.class(db))
.negate(db) .negate(db)
} }
(Type::LiteralString, Type::LiteralString) => C::unsatisfiable(db), (Type::LiteralString, Type::LiteralString) => ConstraintSet::from(false),
(Type::LiteralString, _) | (_, Type::LiteralString) => C::always_satisfiable(db), (Type::LiteralString, _) | (_, Type::LiteralString) => ConstraintSet::from(true),
(Type::BytesLiteral(..), Type::NominalInstance(instance)) (Type::BytesLiteral(..), Type::NominalInstance(instance))
| (Type::NominalInstance(instance), Type::BytesLiteral(..)) => { | (Type::NominalInstance(instance), Type::BytesLiteral(..)) => {
// A `Type::BytesLiteral()` must be an instance of exactly `bytes` // A `Type::BytesLiteral()` must be an instance of exactly `bytes`
// (it cannot be an instance of a `bytes` subclass) // (it cannot be an instance of a `bytes` subclass)
KnownClass::Bytes KnownClass::Bytes
.when_subclass_of::<C>(db, instance.class(db)) .when_subclass_of(db, instance.class(db))
.negate(db) .negate(db)
} }
@ -2397,10 +2399,10 @@ impl<'db> Type<'db> {
| (instance @ Type::NominalInstance(_), Type::EnumLiteral(enum_literal)) => { | (instance @ Type::NominalInstance(_), Type::EnumLiteral(enum_literal)) => {
enum_literal enum_literal
.enum_class_instance(db) .enum_class_instance(db)
.when_subtype_of::<C>(db, instance) .when_subtype_of(db, instance)
.negate(db) .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`, // 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`, // 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(_)) (Type::ClassLiteral(class), instance @ Type::NominalInstance(_))
| (instance @ Type::NominalInstance(_), Type::ClassLiteral(class)) => class | (instance @ Type::NominalInstance(_), Type::ClassLiteral(class)) => class
.metaclass_instance_type(db) .metaclass_instance_type(db)
.when_subtype_of::<C>(db, instance) .when_subtype_of(db, instance)
.negate(db), .negate(db),
(Type::GenericAlias(alias), instance @ Type::NominalInstance(_)) (Type::GenericAlias(alias), instance @ Type::NominalInstance(_))
| (instance @ Type::NominalInstance(_), Type::GenericAlias(alias)) => { | (instance @ Type::NominalInstance(_), Type::GenericAlias(alias)) => {
ClassType::from(alias) ClassType::from(alias)
.metaclass_instance_type(db) .metaclass_instance_type(db)
.when_subtype_of::<C>(db, instance) .when_subtype_of(db, instance)
.negate(db) .negate(db)
} }
@ -2423,7 +2425,7 @@ impl<'db> Type<'db> {
// A `Type::FunctionLiteral()` must be an instance of exactly `types.FunctionType` // A `Type::FunctionLiteral()` must be an instance of exactly `types.FunctionType`
// (it cannot be an instance of a `types.FunctionType` subclass) // (it cannot be an instance of a `types.FunctionType` subclass)
KnownClass::FunctionType KnownClass::FunctionType
.when_subclass_of::<C>(db, instance.class(db)) .when_subclass_of(db, instance.class(db))
.negate(db) .negate(db)
} }
@ -2449,7 +2451,7 @@ impl<'db> Type<'db> {
// No two callable types are ever disjoint because // No two callable types are ever disjoint because
// `(*args: object, **kwargs: object) -> Never` is a subtype of all fully static // `(*args: object, **kwargs: object) -> Never` is a subtype of all fully static
// callable types. // callable types.
C::unsatisfiable(db) ConstraintSet::from(false)
} }
(Type::Callable(_), Type::StringLiteral(_) | Type::BytesLiteral(_)) (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, // A callable type is disjoint from other literal types. For example,
// `Type::StringLiteral` must be an instance of exactly `str`, not a subclass // `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. // 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)) (Type::Callable(_), Type::SpecialForm(special_form))
@ -2466,7 +2468,7 @@ impl<'db> Type<'db> {
// that are callable (like TypedDict and collection constructors). // that are callable (like TypedDict and collection constructors).
// Most special forms are type constructors/annotations (like `typing.Literal`, // Most special forms are type constructors/annotations (like `typing.Literal`,
// `typing.Union`, etc.) that are subscripted, not called. // `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 .place
.ignore_possibly_unbound() .ignore_possibly_unbound()
.when_none_or(db, |dunder_call| { .when_none_or(|dunder_call| {
dunder_call dunder_call
.when_assignable_to::<C>(db, CallableType::unknown(db)) .when_assignable_to(db, CallableType::unknown(db))
.negate(db) .negate(db)
}), }),
@ -2499,7 +2501,7 @@ impl<'db> Type<'db> {
Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_), Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_),
) => { ) => {
// TODO: Implement disjointness for general callable type with other types // TODO: Implement disjointness for general callable type with other types
C::unsatisfiable(db) ConstraintSet::from(false)
} }
(Type::ModuleLiteral(..), other @ Type::NominalInstance(..)) (Type::ModuleLiteral(..), other @ Type::NominalInstance(..))
@ -2519,7 +2521,7 @@ impl<'db> Type<'db> {
} }
(Type::BoundSuper(_), Type::BoundSuper(_)) => { (Type::BoundSuper(_), Type::BoundSuper(_)) => {
self.when_equivalent_to::<C>(db, other).negate(db) self.when_equivalent_to(db, other).negate(db)
} }
(Type::BoundSuper(_), other) | (other, Type::BoundSuper(_)) => KnownClass::Super (Type::BoundSuper(_), other) | (other, Type::BoundSuper(_)) => KnownClass::Super
.to_instance(db) .to_instance(db)
@ -3842,7 +3844,7 @@ impl<'db> Type<'db> {
Type::KnownInstance(KnownInstanceType::ConstraintSet(tracked_set)) => { Type::KnownInstance(KnownInstanceType::ConstraintSet(tracked_set)) => {
let constraints = tracked_set.constraints(db); let constraints = tracked_set.constraints(db);
Truthiness::from(constraints.is_always_satisfied(db)) Truthiness::from(constraints.is_always_satisfied())
} }
Type::FunctionLiteral(_) Type::FunctionLiteral(_)
@ -6917,9 +6919,9 @@ impl<'db> KnownInstanceType<'db> {
} }
KnownInstanceType::ConstraintSet(tracked_set) => { KnownInstanceType::ConstraintSet(tracked_set) => {
let constraints = tracked_set.constraints(self.db); 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]") 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]") f.write_str("ty_extensions.ConstraintSet[never]")
} else { } else {
write!( write!(
@ -8965,13 +8967,13 @@ impl<'db> BoundMethodType<'db> {
) )
} }
fn has_relation_to_impl<C: Constraints<'db>>( fn has_relation_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
relation: TypeRelation, relation: TypeRelation,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
// A bound method is a typically a subtype of itself. However, we must explicitly verify // 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 // 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 // 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<C: Constraints<'db>>( fn is_equivalent_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
visitor: &IsEquivalentVisitor<'db, C>, visitor: &IsEquivalentVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
self.function(db) self.function(db)
.is_equivalent_to_impl(db, other.function(db), visitor) .is_equivalent_to_impl(db, other.function(db), visitor)
.and(db, || { .and(db, || {
@ -9121,15 +9123,15 @@ impl<'db> CallableType<'db> {
/// Check whether this callable type has the given relation to another callable type. /// 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. /// See [`Type::is_subtype_of`] and [`Type::is_assignable_to`] for more details.
fn has_relation_to_impl<C: Constraints<'db>>( fn has_relation_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
relation: TypeRelation, relation: TypeRelation,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
if other.is_function_like(db) && !self.is_function_like(db) { if other.is_function_like(db) && !self.is_function_like(db) {
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
self.signatures(db) self.signatures(db)
.has_relation_to_impl(db, other.signatures(db), relation, visitor) .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. /// Check whether this callable type is equivalent to another callable type.
/// ///
/// See [`Type::is_equivalent_to`] for more details. /// See [`Type::is_equivalent_to`] for more details.
fn is_equivalent_to_impl<C: Constraints<'db>>( fn is_equivalent_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
visitor: &IsEquivalentVisitor<'db, C>, visitor: &IsEquivalentVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
if self == other { 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) self.signatures(db)
.is_equivalent_to_impl(db, other.signatures(db), visitor) .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> { impl<'db> KnownBoundMethodType<'db> {
fn has_relation_to_impl<C: Constraints<'db>>( fn has_relation_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
relation: TypeRelation, relation: TypeRelation,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
match (self, other) { match (self, other) {
( (
KnownBoundMethodType::FunctionTypeDunderGet(self_function), KnownBoundMethodType::FunctionTypeDunderGet(self_function),
@ -9232,7 +9234,7 @@ impl<'db> KnownBoundMethodType<'db> {
) => self_property.when_equivalent_to(db, other_property), ) => self_property.when_equivalent_to(db, other_property),
(KnownBoundMethodType::StrStartswith(_), KnownBoundMethodType::StrStartswith(_)) => { (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::PropertyDunderGet(_)
| KnownBoundMethodType::PropertyDunderSet(_) | KnownBoundMethodType::PropertyDunderSet(_)
| KnownBoundMethodType::StrStartswith(_), | KnownBoundMethodType::StrStartswith(_),
) => C::unsatisfiable(db), ) => ConstraintSet::from(false),
} }
} }
fn is_equivalent_to_impl<C: Constraints<'db>>( fn is_equivalent_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
visitor: &IsEquivalentVisitor<'db, C>, visitor: &IsEquivalentVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
match (self, other) { match (self, other) {
( (
KnownBoundMethodType::FunctionTypeDunderGet(self_function), KnownBoundMethodType::FunctionTypeDunderGet(self_function),
@ -9277,7 +9279,7 @@ impl<'db> KnownBoundMethodType<'db> {
) => self_property.is_equivalent_to_impl(db, other_property, visitor), ) => self_property.is_equivalent_to_impl(db, other_property, visitor),
(KnownBoundMethodType::StrStartswith(_), KnownBoundMethodType::StrStartswith(_)) => { (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::PropertyDunderGet(_)
| KnownBoundMethodType::PropertyDunderSet(_) | KnownBoundMethodType::PropertyDunderSet(_)
| KnownBoundMethodType::StrStartswith(_), | KnownBoundMethodType::StrStartswith(_),
) => C::unsatisfiable(db), ) => ConstraintSet::from(false),
} }
} }
@ -10102,30 +10104,30 @@ impl<'db> UnionType<'db> {
.build() .build()
} }
pub(crate) fn is_equivalent_to_impl<C: Constraints<'db>>( pub(crate) fn is_equivalent_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
_visitor: &IsEquivalentVisitor<'db, C>, _visitor: &IsEquivalentVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
if self == other { if self == other {
return C::always_satisfiable(db); return ConstraintSet::from(true);
} }
let self_elements = self.elements(db); let self_elements = self.elements(db);
let other_elements = other.elements(db); let other_elements = other.elements(db);
if self_elements.len() != other_elements.len() { if self_elements.len() != other_elements.len() {
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
let sorted_self = self.normalized(db); let sorted_self = self.normalized(db);
if sorted_self == Type::Union(other) { 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` /// Return `true` if `self` represents exactly the same set of possible runtime objects as `other`
pub(crate) fn is_equivalent_to_impl<C: Constraints<'db>>( pub(crate) fn is_equivalent_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
_visitor: &IsEquivalentVisitor<'db, C>, _visitor: &IsEquivalentVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
if self == other { if self == other {
return C::always_satisfiable(db); return ConstraintSet::from(true);
} }
let self_positive = self.positive(db); let self_positive = self.positive(db);
let other_positive = other.positive(db); let other_positive = other.positive(db);
if self_positive.len() != other_positive.len() { if self_positive.len() != other_positive.len() {
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
let self_negative = self.negative(db); let self_negative = self.negative(db);
let other_negative = other.negative(db); let other_negative = other.negative(db);
if self_negative.len() != other_negative.len() { if self_negative.len() != other_negative.len() {
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
let sorted_self = self.normalized(db); let sorted_self = self.normalized(db);
if sorted_self == other { 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 /// Returns an iterator over the positive elements of the intersection. If

View file

@ -17,7 +17,6 @@ use crate::db::Db;
use crate::dunder_all::dunder_all_names; use crate::dunder_all::dunder_all_names;
use crate::place::{Boundness, Place}; use crate::place::{Boundness, Place};
use crate::types::call::arguments::{Expansion, is_expandable_type}; use crate::types::call::arguments::{Expansion, is_expandable_type};
use crate::types::constraints::{ConstraintSet, Constraints};
use crate::types::diagnostic::{ use crate::types::diagnostic::{
CALL_NON_CALLABLE, CONFLICTING_ARGUMENT_FORMS, INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT, CALL_NON_CALLABLE, CONFLICTING_ARGUMENT_FORMS, INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT,
NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS, 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) { Type::FunctionLiteral(function_type) => match function_type.known(db) {
Some(KnownFunction::IsEquivalentTo) => { Some(KnownFunction::IsEquivalentTo) => {
if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() {
let constraints = let constraints = ty_a.when_equivalent_to(db, *ty_b);
ty_a.when_equivalent_to::<ConstraintSet>(db, *ty_b);
let tracked = TrackedConstraintSet::new(db, constraints); let tracked = TrackedConstraintSet::new(db, constraints);
overload.set_return_type(Type::KnownInstance( overload.set_return_type(Type::KnownInstance(
KnownInstanceType::ConstraintSet(tracked), KnownInstanceType::ConstraintSet(tracked),
@ -597,7 +595,7 @@ impl<'db> Bindings<'db> {
Some(KnownFunction::IsSubtypeOf) => { Some(KnownFunction::IsSubtypeOf) => {
if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() {
let constraints = ty_a.when_subtype_of::<ConstraintSet>(db, *ty_b); let constraints = ty_a.when_subtype_of(db, *ty_b);
let tracked = TrackedConstraintSet::new(db, constraints); let tracked = TrackedConstraintSet::new(db, constraints);
overload.set_return_type(Type::KnownInstance( overload.set_return_type(Type::KnownInstance(
KnownInstanceType::ConstraintSet(tracked), KnownInstanceType::ConstraintSet(tracked),
@ -607,8 +605,7 @@ impl<'db> Bindings<'db> {
Some(KnownFunction::IsAssignableTo) => { Some(KnownFunction::IsAssignableTo) => {
if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() {
let constraints = let constraints = ty_a.when_assignable_to(db, *ty_b);
ty_a.when_assignable_to::<ConstraintSet>(db, *ty_b);
let tracked = TrackedConstraintSet::new(db, constraints); let tracked = TrackedConstraintSet::new(db, constraints);
overload.set_return_type(Type::KnownInstance( overload.set_return_type(Type::KnownInstance(
KnownInstanceType::ConstraintSet(tracked), KnownInstanceType::ConstraintSet(tracked),
@ -618,8 +615,7 @@ impl<'db> Bindings<'db> {
Some(KnownFunction::IsDisjointFrom) => { Some(KnownFunction::IsDisjointFrom) => {
if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() {
let constraints = let constraints = ty_a.when_disjoint_from(db, *ty_b);
ty_a.when_disjoint_from::<ConstraintSet>(db, *ty_b);
let tracked = TrackedConstraintSet::new(db, constraints); let tracked = TrackedConstraintSet::new(db, constraints);
overload.set_return_type(Type::KnownInstance( overload.set_return_type(Type::KnownInstance(
KnownInstanceType::ConstraintSet(tracked), 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 // constraint set that we get from this assignability check, instead of inferring and
// building them in an earlier separate step. // building them in an earlier separate step.
if argument_type if argument_type
.when_assignable_to::<ConstraintSet>(self.db, expected_ty) .when_assignable_to(self.db, expected_ty)
.is_never_satisfied(self.db) .is_never_satisfied()
{ {
let positional = matches!(argument, Argument::Positional | Argument::Synthetic) let positional = matches!(argument, Argument::Positional | Argument::Synthetic)
&& !parameter.is_variadic(); && !parameter.is_variadic();

View file

@ -14,7 +14,7 @@ use crate::semantic_index::symbol::Symbol;
use crate::semantic_index::{ use crate::semantic_index::{
DeclarationWithConstraint, SemanticIndex, attribute_declarations, attribute_scopes, 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::context::InferContext;
use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE}; use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE};
use crate::types::enums::enum_metadata; 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. /// 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 { pub(super) fn is_subclass_of(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
self.when_subclass_of::<ConstraintSet>(db, other) self.when_subclass_of(db, other).is_always_satisfied()
.is_always_satisfied(db)
} }
pub(super) fn when_subclass_of<C: Constraints<'db>>( pub(super) fn when_subclass_of(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: ClassType<'db>, other: ClassType<'db>,
) -> C { ) -> ConstraintSet<'db> {
self.has_relation_to_impl( self.has_relation_to_impl(
db, db,
other, other,
TypeRelation::Subtyping, TypeRelation::Subtyping,
&HasRelationToVisitor::new(C::always_satisfiable(db)), &HasRelationToVisitor::default(),
) )
} }
pub(super) fn has_relation_to_impl<C: Constraints<'db>>( pub(super) fn has_relation_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
relation: TypeRelation, relation: TypeRelation,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
self.iter_mro(db).when_any(db, |base| { self.iter_mro(db).when_any(db, |base| {
match base { match base {
ClassBase::Dynamic(_) => match relation { ClassBase::Dynamic(_) => match relation {
TypeRelation::Subtyping => C::from_bool(db, other.is_object(db)), TypeRelation::Subtyping => ConstraintSet::from(other.is_object(db)),
TypeRelation::Assignability => C::from_bool(db, !other.is_final(db)), TypeRelation::Assignability => ConstraintSet::from(!other.is_final(db)),
}, },
// Protocol and Generic are not represented by a ClassType. // 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) { ClassBase::Class(base) => match (base, other) {
(ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => { (ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => {
C::from_bool(db, base == other) ConstraintSet::from(base == other)
} }
(ClassType::Generic(base), ClassType::Generic(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( base.specialization(db).has_relation_to_impl(
db, db,
other.specialization(db), other.specialization(db),
@ -573,34 +572,38 @@ impl<'db> ClassType<'db> {
}) })
} }
(ClassType::Generic(_), ClassType::NonGeneric(_)) (ClassType::Generic(_), ClassType::NonGeneric(_))
| (ClassType::NonGeneric(_), ClassType::Generic(_)) => C::unsatisfiable(db), | (ClassType::NonGeneric(_), ClassType::Generic(_)) => {
ConstraintSet::from(false)
}
}, },
ClassBase::TypedDict => { ClassBase::TypedDict => {
// TODO: Implement subclassing and assignability for TypedDicts. // TODO: Implement subclassing and assignability for TypedDicts.
C::always_satisfiable(db) ConstraintSet::from(true)
} }
} }
}) })
} }
pub(super) fn is_equivalent_to_impl<C: Constraints<'db>>( pub(super) fn is_equivalent_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: ClassType<'db>, other: ClassType<'db>,
visitor: &IsEquivalentVisitor<'db, C>, visitor: &IsEquivalentVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
if self == other { if self == other {
return C::always_satisfiable(db); return ConstraintSet::from(true);
} }
match (self, other) { match (self, other) {
// A non-generic class is never equivalent to a generic class. // A non-generic class is never equivalent to a generic class.
// Two non-generic classes are only equivalent if they are equal (handled above). // 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)) => { (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( this.specialization(db).is_equivalent_to_impl(
db, db,
other.specialization(db), other.specialization(db),
@ -1701,13 +1704,13 @@ impl<'db> ClassLiteral<'db> {
.contains(&ClassBase::Class(other)) .contains(&ClassBase::Class(other))
} }
pub(super) fn when_subclass_of<C: Constraints<'db>>( pub(super) fn when_subclass_of(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
specialization: Option<Specialization<'db>>, specialization: Option<Specialization<'db>>,
other: ClassType<'db>, other: ClassType<'db>,
) -> C { ) -> ConstraintSet<'db> {
C::from_bool(db, self.is_subclass_of(db, specialization, other)) ConstraintSet::from(self.is_subclass_of(db, specialization, other))
} }
/// Return `true` if this class constitutes a typed dict specification (inherits from /// 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)) .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, self,
db: &'db dyn Db, db: &'db dyn Db,
other: ClassType<'db>, other: ClassType<'db>,
) -> C { ) -> ConstraintSet<'db> {
C::from_bool(db, self.is_subclass_of(db, other)) ConstraintSet::from(self.is_subclass_of(db, other))
} }
/// Return the module in which we should look up the definition for this class /// Return the module in which we should look up the definition for this class

View file

@ -43,12 +43,10 @@
//! — must be fully static. We take the bottom and top materializations of the types to remove any //! — must be fully static. We take the bottom and top materializations of the types to remove any
//! gradual forms if needed. //! gradual forms if needed.
//! //!
//! NOTE: This module is currently in a transitional state: we've added a [`Constraints`] trait, //! NOTE: This module is currently in a transitional state. We've added the DNF [`ConstraintSet`]
//! and updated all of our type property implementations to work on any impl of that trait. We have //! representation, and updated all of our property checks to build up a constraint set and then
//! added the DNF [`ConstraintSet`] representation, and updated all of our property checks to build //! check whether it is ever or always satisfiable, as appropriate. We are not yet inferring
//! up a constraint set and then check whether it is ever or always satisfiable, as appropriate. We //! specializations from those constraints.
//! are not yet inferring specializations from those constraints, and we will likely remove the
//! [`Constraints`] trait once everything has stabilized.
//! //!
//! ### Examples //! ### Examples
//! //!
@ -110,86 +108,29 @@ fn incomparable<'db>(db: &'db dyn Db, left: Type<'db>, right: Type<'db>) -> bool
!comparable(db, left, right) !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. /// An extension trait for building constraint sets from [`Option`] values.
pub(crate) trait OptionConstraintsExtension<T> { pub(crate) trait OptionConstraintsExtension<T> {
/// Returns [`always_satisfiable`][Constraints::always_satisfiable] if the option is `None`; /// Returns a constraint set that is always satisfiable if the option is `None`; otherwise
/// 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
/// applies a function to determine under what constraints the value inside of it holds. /// 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<T> OptionConstraintsExtension<T> for Option<T> { impl<T> OptionConstraintsExtension<T> for Option<T> {
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 { match self {
Some(value) => f(value), 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 { match self {
Some(value) => f(value), Some(value) => f(value),
None => C::unsatisfiable(db), None => ConstraintSet::never(),
} }
} }
} }
@ -199,36 +140,52 @@ pub(crate) trait IteratorConstraintsExtension<T> {
/// Returns the constraints under which any element of the iterator holds. /// Returns the constraints under which any element of the iterator holds.
/// ///
/// This method short-circuits; if we encounter any element that /// 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. /// 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. /// Returns the constraints under which every element of the iterator holds.
/// ///
/// This method short-circuits; if we encounter any element that /// This method short-circuits; if we encounter any element that
/// [`is_never_satisfied`][Constraints::is_never_satisfied] true, then the overall result must /// [`is_never_satisfied`][ConstraintSet::is_never_satisfied] true, then the overall result
/// be as well, and we stop consuming elements from the iterator. /// 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; fn when_all<'db>(
self,
db: &'db dyn Db,
f: impl FnMut(T) -> ConstraintSet<'db>,
) -> ConstraintSet<'db>;
} }
impl<I, T> IteratorConstraintsExtension<T> for I impl<I, T> IteratorConstraintsExtension<T> for I
where where
I: Iterator<Item = T>, I: Iterator<Item = T>,
{ {
fn when_any<'db, C: Constraints<'db>>(self, db: &'db dyn Db, mut f: impl FnMut(T) -> C) -> C { fn when_any<'db>(
let mut result = C::unsatisfiable(db); self,
db: &'db dyn Db,
mut f: impl FnMut(T) -> ConstraintSet<'db>,
) -> ConstraintSet<'db> {
let mut result = ConstraintSet::never();
for child in self { 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; return result;
} }
} }
result result
} }
fn when_all<'db, C: Constraints<'db>>(self, db: &'db dyn Db, mut f: impl FnMut(T) -> C) -> C { fn when_all<'db>(
let mut result = C::always_satisfiable(db); self,
db: &'db dyn Db,
mut f: impl FnMut(T) -> ConstraintSet<'db>,
) -> ConstraintSet<'db> {
let mut result = ConstraintSet::always();
for child in self { 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; return result;
} }
} }
@ -259,13 +216,67 @@ pub struct ConstraintSet<'db> {
} }
impl<'db> ConstraintSet<'db> { impl<'db> ConstraintSet<'db> {
/// Returns the constraint set that is never satisfiable.
fn never() -> Self { fn never() -> Self {
Self { Self {
clauses: smallvec![], 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. /// Returns a constraint set that contains a single clause.
fn singleton(clause: ConstraintClause<'db>) -> Self { fn singleton(clause: ConstraintClause<'db>) -> Self {
Self { Self {
@ -402,44 +413,8 @@ impl<'db> ConstraintSet<'db> {
} }
} }
} }
}
impl<'db> Constraints<'db> for ConstraintSet<'db> { pub(crate) fn display(&self, db: &'db dyn Db) -> impl Display {
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 {
struct DisplayConstraintSet<'a, 'db> { struct DisplayConstraintSet<'a, 'db> {
set: &'a ConstraintSet<'db>, set: &'a ConstraintSet<'db>,
db: &'db dyn Db, db: &'db dyn Db,
@ -464,6 +439,12 @@ impl<'db> Constraints<'db> for ConstraintSet<'db> {
} }
} }
impl From<bool> for ConstraintSet<'_> {
fn from(b: bool) -> Self {
if b { Self::always() } else { Self::never() }
}
}
/// The intersection of zero or more individual constraints. /// The intersection of zero or more individual constraints.
/// ///
/// This is called a "constraint set", and denoted _C_, in [[POPL2015][]]. /// This is called a "constraint set", and denoted _C_, in [[POPL2015][]].

View file

@ -65,7 +65,7 @@ use crate::semantic_index::definition::Definition;
use crate::semantic_index::scope::ScopeId; use crate::semantic_index::scope::ScopeId;
use crate::semantic_index::{FileScopeId, SemanticIndex, semantic_index}; use crate::semantic_index::{FileScopeId, SemanticIndex, semantic_index};
use crate::types::call::{Binding, CallArguments}; 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::context::InferContext;
use crate::types::diagnostic::{ use crate::types::diagnostic::{
INVALID_ARGUMENT_TYPE, REDUNDANT_CAST, STATIC_ASSERT_ERROR, TYPE_ASSERTION_FAILURE, 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) BoundMethodType::new(db, self, self_instance)
} }
pub(crate) fn has_relation_to_impl<C: Constraints<'db>>( pub(crate) fn has_relation_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
relation: TypeRelation, relation: TypeRelation,
_visitor: &HasRelationToVisitor<'db, C>, _visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
match relation { match relation {
TypeRelation::Subtyping => C::from_bool(db, self.is_subtype_of(db, other)), TypeRelation::Subtyping => ConstraintSet::from(self.is_subtype_of(db, other)),
TypeRelation::Assignability => C::from_bool(db, self.is_assignable_to(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)) && self.signature(db).is_assignable_to(db, other.signature(db))
} }
pub(crate) fn is_equivalent_to_impl<C: Constraints<'db>>( pub(crate) fn is_equivalent_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
visitor: &IsEquivalentVisitor<'db, C>, visitor: &IsEquivalentVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
if self.normalized(db) == other.normalized(db) { if self.normalized(db) == other.normalized(db) {
return C::always_satisfiable(db); return ConstraintSet::from(true);
} }
if self.literal(db) != other.literal(db) { if self.literal(db) != other.literal(db) {
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
let self_signature = self.signature(db); let self_signature = self.signature(db);
let other_signature = other.signature(db); let other_signature = other.signature(db);

View file

@ -1,5 +1,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use crate::types::constraints::ConstraintSet;
use ruff_db::parsed::ParsedModuleRef; use ruff_db::parsed::ParsedModuleRef;
use ruff_python_ast as ast; use ruff_python_ast as ast;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
@ -9,7 +11,6 @@ use crate::semantic_index::definition::Definition;
use crate::semantic_index::scope::{FileScopeId, NodeWithScopeKind}; use crate::semantic_index::scope::{FileScopeId, NodeWithScopeKind};
use crate::types::class::ClassType; use crate::types::class::ClassType;
use crate::types::class_base::ClassBase; use crate::types::class_base::ClassBase;
use crate::types::constraints::Constraints;
use crate::types::infer::infer_definition_types; use crate::types::infer::infer_definition_types;
use crate::types::instance::{Protocol, ProtocolInstanceType}; use crate::types::instance::{Protocol, ProtocolInstanceType};
use crate::types::signatures::{Parameter, Parameters, Signature}; 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, db: &'db dyn Db,
derived_type: &Type<'db>, derived_type: &Type<'db>,
derived_materialization: MaterializationKind, derived_materialization: MaterializationKind,
base_type: &Type<'db>, base_type: &Type<'db>,
base_materialization: MaterializationKind, base_materialization: MaterializationKind,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
let derived_top = derived_type.top_materialization(db); let derived_top = derived_type.top_materialization(db);
let derived_bottom = derived_type.bottom_materialization(db); let derived_bottom = derived_type.bottom_materialization(db);
let base_top = base_type.top_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 /// Whether two types encountered in an invariant position
/// have a relation (subtyping or assignability), taking into account /// have a relation (subtyping or assignability), taking into account
/// that the two types may come from a top or bottom materialization. /// 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, db: &'db dyn Db,
derived_type: &Type<'db>, derived_type: &Type<'db>,
derived_materialization: Option<MaterializationKind>, derived_materialization: Option<MaterializationKind>,
base_type: &Type<'db>, base_type: &Type<'db>,
base_materialization: Option<MaterializationKind>, base_materialization: Option<MaterializationKind>,
relation: TypeRelation, relation: TypeRelation,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
match (derived_materialization, base_materialization, relation) { match (derived_materialization, base_materialization, relation) {
// Top and bottom materializations are fully static types, so subtyping // Top and bottom materializations are fully static types, so subtyping
// is the same as assignability. // is the same as assignability.
@ -791,16 +792,16 @@ impl<'db> Specialization<'db> {
) )
} }
pub(crate) fn has_relation_to_impl<C: Constraints<'db>>( pub(crate) fn has_relation_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
relation: TypeRelation, relation: TypeRelation,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
let generic_context = self.generic_context(db); let generic_context = self.generic_context(db);
if generic_context != other.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)) 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 self_materialization_kind = self.materialization_kind(db);
let other_materialization_kind = other.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()) for ((bound_typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
.zip(self.types(db)) .zip(self.types(db))
.zip(other.types(db)) .zip(other.types(db))
@ -824,7 +825,7 @@ impl<'db> Specialization<'db> {
{ {
match relation { match relation {
TypeRelation::Assignability => continue, 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 => { TypeVarVariance::Contravariant => {
other_type.has_relation_to_impl(db, *self_type, relation, visitor) 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; return result;
} }
} }
@ -860,21 +861,21 @@ impl<'db> Specialization<'db> {
result result
} }
pub(crate) fn is_equivalent_to_impl<C: Constraints<'db>>( pub(crate) fn is_equivalent_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Specialization<'db>, other: Specialization<'db>,
visitor: &IsEquivalentVisitor<'db, C>, visitor: &IsEquivalentVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
if self.materialization_kind(db) != other.materialization_kind(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); let generic_context = self.generic_context(db);
if generic_context != other.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()) for ((bound_typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
.zip(self.types(db)) .zip(self.types(db))
.zip(other.types(db)) .zip(other.types(db))
@ -891,19 +892,19 @@ impl<'db> Specialization<'db> {
| TypeVarVariance::Contravariant => { | TypeVarVariance::Contravariant => {
self_type.is_equivalent_to_impl(db, *other_type, visitor) 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; return result;
} }
} }
match (self.tuple_inner(db), other.tuple_inner(db)) { 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) => {} (None, None) => {}
(Some(self_tuple), Some(other_tuple)) => { (Some(self_tuple), Some(other_tuple)) => {
let compatible = self_tuple.is_equivalent_to_impl(db, other_tuple, visitor); 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; return result;
} }
} }

View file

@ -44,7 +44,6 @@ use crate::semantic_index::{
}; };
use crate::types::call::{Binding, Bindings, CallArguments, CallError, CallErrorKind}; use crate::types::call::{Binding, Bindings, CallArguments, CallError, CallErrorKind};
use crate::types::class::{CodeGeneratorKind, FieldKind, MetaclassErrorKind, MethodDecorator}; use crate::types::class::{CodeGeneratorKind, FieldKind, MetaclassErrorKind, MethodDecorator};
use crate::types::constraints::Constraints;
use crate::types::context::{InNoTypeCheck, InferContext}; use crate::types::context::{InNoTypeCheck, InferContext};
use crate::types::diagnostic::{ use crate::types::diagnostic::{
CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, CYCLIC_CLASS_DEFINITION, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, CYCLIC_CLASS_DEFINITION,

View file

@ -7,7 +7,7 @@ use super::protocol_class::ProtocolInterface;
use super::{BoundTypeVarInstance, ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance}; use super::{BoundTypeVarInstance, ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance};
use crate::place::PlaceAndQualifiers; use crate::place::PlaceAndQualifiers;
use crate::semantic_index::definition::Definition; 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::enums::is_single_member_enum;
use crate::types::protocol_class::walk_protocol_interface; use crate::types::protocol_class::walk_protocol_interface;
use crate::types::tuple::{TupleSpec, TupleType}; 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`. /// Return `true` if `self` conforms to the interface described by `protocol`.
pub(super) fn satisfies_protocol<C: Constraints<'db>>( pub(super) fn satisfies_protocol(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
protocol: ProtocolInstanceType<'db>, protocol: ProtocolInstanceType<'db>,
relation: TypeRelation, relation: TypeRelation,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
let structurally_satisfied = if let Type::ProtocolInstance(self_protocol) = self { let structurally_satisfied = if let Type::ProtocolInstance(self_protocol) = self {
self_protocol.interface(db).extends_interface_of( self_protocol.interface(db).extends_interface_of(
db, db,
@ -155,7 +155,7 @@ impl<'db> Type<'db> {
visitor, visitor,
) )
} else { } else {
C::unsatisfiable(db) ConstraintSet::from(false)
} }
}) })
} }
@ -336,15 +336,15 @@ impl<'db> NominalInstanceType<'db> {
} }
} }
pub(super) fn has_relation_to_impl<C: Constraints<'db>>( pub(super) fn has_relation_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
relation: TypeRelation, relation: TypeRelation,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
match (self.0, other.0) { match (self.0, other.0) {
(_, NominalInstanceInner::Object) => C::always_satisfiable(db), (_, NominalInstanceInner::Object) => ConstraintSet::from(true),
( (
NominalInstanceInner::ExactTuple(tuple1), NominalInstanceInner::ExactTuple(tuple1),
NominalInstanceInner::ExactTuple(tuple2), NominalInstanceInner::ExactTuple(tuple2),
@ -355,50 +355,47 @@ impl<'db> NominalInstanceType<'db> {
} }
} }
pub(super) fn is_equivalent_to_impl<C: Constraints<'db>>( pub(super) fn is_equivalent_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
visitor: &IsEquivalentVisitor<'db, C>, visitor: &IsEquivalentVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
match (self.0, other.0) { match (self.0, other.0) {
( (
NominalInstanceInner::ExactTuple(tuple1), NominalInstanceInner::ExactTuple(tuple1),
NominalInstanceInner::ExactTuple(tuple2), NominalInstanceInner::ExactTuple(tuple2),
) => tuple1.is_equivalent_to_impl(db, tuple2, visitor), ) => tuple1.is_equivalent_to_impl(db, tuple2, visitor),
(NominalInstanceInner::Object, NominalInstanceInner::Object) => { (NominalInstanceInner::Object, NominalInstanceInner::Object) => {
C::always_satisfiable(db) ConstraintSet::from(true)
} }
(NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => { (NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => {
class1.is_equivalent_to_impl(db, class2, visitor) class1.is_equivalent_to_impl(db, class2, visitor)
} }
_ => C::unsatisfiable(db), _ => ConstraintSet::from(false),
} }
} }
pub(super) fn is_disjoint_from_impl<C: Constraints<'db>>( pub(super) fn is_disjoint_from_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
visitor: &IsDisjointVisitor<'db, C>, visitor: &IsDisjointVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
if self.is_object() || other.is_object() { 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(self_spec) = self.tuple_spec(db) {
if let Some(other_spec) = other.tuple_spec(db) { if let Some(other_spec) = other.tuple_spec(db) {
let compatible = self_spec.is_disjoint_from_impl(db, &other_spec, visitor); 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; return result;
} }
} }
} }
result.or(db, || { result.or(db, || {
C::from_bool( ConstraintSet::from(!(self.class(db)).could_coexist_in_mro_with(db, other.class(db)))
db,
!(self.class(db)).could_coexist_in_mro_with(db, other.class(db)),
)
}) })
} }
@ -589,9 +586,9 @@ impl<'db> ProtocolInstanceType<'db> {
db, db,
protocol, protocol,
TypeRelation::Subtyping, TypeRelation::Subtyping,
&HasRelationToVisitor::new(ConstraintSet::always_satisfiable(db)), &HasRelationToVisitor::default(),
) )
.is_always_satisfied(db) .is_always_satisfied()
} }
#[expect(clippy::trivially_copy_pass_by_ref)] #[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`. /// Return `true` if this protocol type is equivalent to the protocol `other`.
/// ///
/// TODO: consider the types of the members as well as their existence /// TODO: consider the types of the members as well as their existence
pub(super) fn is_equivalent_to_impl<C: Constraints<'db>>( pub(super) fn is_equivalent_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
_visitor: &IsEquivalentVisitor<'db, C>, _visitor: &IsEquivalentVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
if self == other { if self == other {
return C::always_satisfiable(db); return ConstraintSet::from(true);
} }
let self_normalized = self.normalized(db); let self_normalized = self.normalized(db);
if self_normalized == Type::ProtocolInstance(other) { 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`. /// 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` /// TODO: a protocol `X` is disjoint from a protocol `Y` if `X` and `Y`
/// have a member with the same name but disjoint types /// have a member with the same name but disjoint types
#[expect(clippy::unused_self)] #[expect(clippy::unused_self)]
pub(super) fn is_disjoint_from_impl<C: Constraints<'db>>( pub(super) fn is_disjoint_from_impl(
self, self,
db: &'db dyn Db, _db: &'db dyn Db,
_other: Self, _other: Self,
_visitor: &IsDisjointVisitor<'db, C>, _visitor: &IsDisjointVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
C::unsatisfiable(db) ConstraintSet::from(false)
} }
pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> { pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {

View file

@ -21,7 +21,7 @@ use crate::{
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, KnownFunction, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, KnownFunction,
NormalizedVisitor, PropertyInstanceType, Signature, Type, TypeMapping, TypeQualifiers, NormalizedVisitor, PropertyInstanceType, Signature, Type, TypeMapping, TypeQualifiers,
TypeRelation, VarianceInferable, TypeRelation, VarianceInferable,
constraints::{Constraints, IteratorConstraintsExtension}, constraints::{ConstraintSet, IteratorConstraintsExtension},
signatures::{Parameter, Parameters}, signatures::{Parameter, Parameters},
}, },
}; };
@ -234,17 +234,17 @@ impl<'db> ProtocolInterface<'db> {
/// all members on `other` are also members of `self`. /// all members on `other` are also members of `self`.
/// ///
/// TODO: this method should consider the types of the members as well as their names. /// TODO: this method should consider the types of the members as well as their names.
pub(super) fn extends_interface_of<C: Constraints<'db>>( pub(super) fn extends_interface_of(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
_relation: TypeRelation, _relation: TypeRelation,
_visitor: &HasRelationToVisitor<'db, C>, _visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
// TODO: This could just return a bool as written, but this form is what will be needed to // 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. // combine the constraints when we do assignability checks on each member.
other.inner(db).keys().when_all(db, |member_name| { 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<C: Constraints<'db>>( pub(super) fn has_disjoint_type_from(
&self, &self,
db: &'db dyn Db, db: &'db dyn Db,
other: Type<'db>, other: Type<'db>,
visitor: &IsDisjointVisitor<'db, C>, visitor: &IsDisjointVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
match &self.kind { match &self.kind {
// TODO: implement disjointness for property/method members as well as attribute members // 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), ProtocolMemberKind::Other(ty) => ty.is_disjoint_from_impl(db, other, visitor),
} }
} }
/// Return `true` if `other` contains an attribute/method/property that satisfies /// Return `true` if `other` contains an attribute/method/property that satisfies
/// the part of the interface defined by this protocol member. /// the part of the interface defined by this protocol member.
pub(super) fn is_satisfied_by<C: Constraints<'db>>( pub(super) fn is_satisfied_by(
&self, &self,
db: &'db dyn Db, db: &'db dyn Db,
other: Type<'db>, other: Type<'db>,
relation: TypeRelation, relation: TypeRelation,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
match &self.kind { match &self.kind {
// TODO: consider the types of the attribute on `other` for method members // TODO: consider the types of the attribute on `other` for method members
ProtocolMemberKind::Method(_) => C::from_bool( ProtocolMemberKind::Method(_) => ConstraintSet::from(matches!(
db,
matches!(
other.to_meta_type(db).member(db, self.name).place, other.to_meta_type(db).member(db, self.name).place,
Place::Type(ty, Boundness::Bound) Place::Type(ty, Boundness::Bound)
if ty.is_assignable_to(db, CallableType::single(db, Signature::dynamic(Type::any()))) 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 // TODO: consider the types of the attribute on `other` for property members
ProtocolMemberKind::Property(_) => C::from_bool( ProtocolMemberKind::Property(_) => ConstraintSet::from(matches!(
db,
matches!(
other.member(db, self.name).place, other.member(db, self.name).place,
Place::Type(_, Boundness::Bound) Place::Type(_, Boundness::Bound)
), )),
),
ProtocolMemberKind::Other(member_type) => { ProtocolMemberKind::Other(member_type) => {
let Place::Type(attribute_type, Boundness::Bound) = let Place::Type(attribute_type, Boundness::Bound) =
other.member(db, self.name).place other.member(db, self.name).place
else { else {
return C::unsatisfiable(db); return ConstraintSet::from(false);
}; };
member_type member_type
.has_relation_to_impl(db, attribute_type, relation, visitor) .has_relation_to_impl(db, attribute_type, relation, visitor)

View file

@ -17,7 +17,7 @@ use smallvec::{SmallVec, smallvec_inline};
use super::{DynamicType, Type, TypeVarVariance, definition_expression_type}; use super::{DynamicType, Type, TypeVarVariance, definition_expression_type};
use crate::semantic_index::definition::Definition; 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::generics::{GenericContext, walk_generic_context};
use crate::types::{ use crate::types::{
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, FindLegacyTypeVarsVisitor,
@ -112,16 +112,15 @@ impl<'db> CallableSignature<'db> {
/// ///
/// See [`Type::is_subtype_of`] for more details. /// See [`Type::is_subtype_of`] for more details.
pub(crate) fn is_subtype_of(&self, db: &'db dyn Db, other: &Self) -> bool { pub(crate) fn is_subtype_of(&self, db: &'db dyn Db, other: &Self) -> bool {
self.is_subtype_of_impl::<ConstraintSet>(db, other) self.is_subtype_of_impl(db, other).is_always_satisfied()
.is_always_satisfied(db)
} }
fn is_subtype_of_impl<C: Constraints<'db>>(&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( self.has_relation_to_impl(
db, db,
other, other,
TypeRelation::Subtyping, TypeRelation::Subtyping,
&HasRelationToVisitor::new(C::always_satisfiable(db)), &HasRelationToVisitor::default(),
) )
} }
@ -133,30 +132,30 @@ impl<'db> CallableSignature<'db> {
db, db,
other, other,
TypeRelation::Assignability, TypeRelation::Assignability,
&HasRelationToVisitor::new(ConstraintSet::always_satisfiable(db)), &HasRelationToVisitor::default(),
) )
.is_always_satisfied(db) .is_always_satisfied()
} }
pub(crate) fn has_relation_to_impl<C: Constraints<'db>>( pub(crate) fn has_relation_to_impl(
&self, &self,
db: &'db dyn Db, db: &'db dyn Db,
other: &Self, other: &Self,
relation: TypeRelation, relation: TypeRelation,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
Self::has_relation_to_inner(db, &self.overloads, &other.overloads, relation, visitor) Self::has_relation_to_inner(db, &self.overloads, &other.overloads, relation, visitor)
} }
/// Implementation of subtyping and assignability between two, possible overloaded, callable /// Implementation of subtyping and assignability between two, possible overloaded, callable
/// types. /// types.
fn has_relation_to_inner<C: Constraints<'db>>( fn has_relation_to_inner(
db: &'db dyn Db, db: &'db dyn Db,
self_signatures: &[Signature<'db>], self_signatures: &[Signature<'db>],
other_signatures: &[Signature<'db>], other_signatures: &[Signature<'db>],
relation: TypeRelation, relation: TypeRelation,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
match (self_signatures, other_signatures) { match (self_signatures, other_signatures) {
([self_signature], [other_signature]) => { ([self_signature], [other_signature]) => {
// Base case: both callable types contain a single 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. /// Check whether this callable type is equivalent to another callable type.
/// ///
/// See [`Type::is_equivalent_to`] for more details. /// See [`Type::is_equivalent_to`] for more details.
pub(crate) fn is_equivalent_to_impl<C: Constraints<'db>>( pub(crate) fn is_equivalent_to_impl(
&self, &self,
db: &'db dyn Db, db: &'db dyn Db,
other: &Self, other: &Self,
visitor: &IsEquivalentVisitor<'db, C>, visitor: &IsEquivalentVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
match (self.overloads.as_slice(), other.overloads.as_slice()) { match (self.overloads.as_slice(), other.overloads.as_slice()) {
([self_signature], [other_signature]) => { ([self_signature], [other_signature]) => {
// Common case: both callable types contain a single signature, use the custom // Common case: both callable types contain a single signature, use the custom
@ -215,9 +214,9 @@ impl<'db> CallableSignature<'db> {
} }
(_, _) => { (_, _) => {
if self == other { if self == other {
return C::always_satisfiable(db); return ConstraintSet::from(true);
} }
self.is_subtype_of_impl::<C>(db, other) self.is_subtype_of_impl(db, other)
.and(db, || other.is_subtype_of_impl(db, self)) .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 /// 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` (if `self` represents the same set of possible sets of possible runtime objects as
/// `other`). /// `other`).
pub(crate) fn is_equivalent_to_impl<C: Constraints<'db>>( pub(crate) fn is_equivalent_to_impl(
&self, &self,
db: &'db dyn Db, db: &'db dyn Db,
other: &Signature<'db>, other: &Signature<'db>,
visitor: &IsEquivalentVisitor<'db, C>, visitor: &IsEquivalentVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
let mut result = C::always_satisfiable(db); let mut result = ConstraintSet::from(true);
let mut check_types = |self_type: Option<Type<'db>>, other_type: Option<Type<'db>>| { let mut check_types = |self_type: Option<Type<'db>>, other_type: Option<Type<'db>>| {
let self_type = self_type.unwrap_or(Type::unknown()); let self_type = self_type.unwrap_or(Type::unknown());
let other_type = other_type.unwrap_or(Type::unknown()); let other_type = other_type.unwrap_or(Type::unknown());
!result !result
.intersect(db, self_type.is_equivalent_to_impl(db, other_type, visitor)) .intersect(
.is_never_satisfied(db) db,
&self_type.is_equivalent_to_impl(db, other_type, visitor),
)
.is_never_satisfied()
}; };
if self.parameters.is_gradual() != other.parameters.is_gradual() { if self.parameters.is_gradual() != other.parameters.is_gradual() {
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
if self.parameters.len() != other.parameters.len() { if self.parameters.len() != other.parameters.len() {
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
if !check_types(self.return_ty, other.return_ty) { if !check_types(self.return_ty, other.return_ty) {
@ -601,7 +603,7 @@ impl<'db> Signature<'db> {
(ParameterKind::KeywordVariadic { .. }, ParameterKind::KeywordVariadic { .. }) => {} (ParameterKind::KeywordVariadic { .. }, ParameterKind::KeywordVariadic { .. }) => {}
_ => return C::unsatisfiable(db), _ => return ConstraintSet::from(false),
} }
if !check_types( if !check_types(
@ -616,13 +618,13 @@ impl<'db> Signature<'db> {
} }
/// Implementation of subtyping and assignability for signature. /// Implementation of subtyping and assignability for signature.
fn has_relation_to_impl<C: Constraints<'db>>( fn has_relation_to_impl(
&self, &self,
db: &'db dyn Db, db: &'db dyn Db,
other: &Signature<'db>, other: &Signature<'db>,
relation: TypeRelation, relation: TypeRelation,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
/// A helper struct to zip two slices of parameters together that provides control over the /// 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 /// two iterators individually. It also keeps track of the current parameter in each
/// iterator. /// 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<Type<'db>>, type2: Option<Type<'db>>| { let mut check_types = |type1: Option<Type<'db>>, type2: Option<Type<'db>>| {
let type1 = type1.unwrap_or(Type::unknown()); let type1 = type1.unwrap_or(Type::unknown());
let type2 = type2.unwrap_or(Type::unknown()); let type2 = type2.unwrap_or(Type::unknown());
!result !result
.intersect(db, type1.has_relation_to_impl(db, type2, relation, visitor)) .intersect(
.is_never_satisfied(db) db,
&type1.has_relation_to_impl(db, type2, relation, visitor),
)
.is_never_satisfied()
}; };
// Return types are covariant. // Return types are covariant.
@ -710,13 +715,13 @@ impl<'db> Signature<'db> {
.keyword_variadic() .keyword_variadic()
.is_some_and(|(_, param)| param.annotated_type().is_some_and(|ty| ty.is_object())) .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 // 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. // any other parameter list, but not a subtype or supertype of any other parameter list.
if self.parameters.is_gradual() || other.parameters.is_gradual() { 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 { let mut parameters = ParametersZip {
@ -754,7 +759,7 @@ impl<'db> Signature<'db> {
// `other`, then the non-variadic parameters in `self` must have a default // `other`, then the non-variadic parameters in `self` must have a default
// value. // value.
if default_type.is_none() { if default_type.is_none() {
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
} }
ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => { ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => {
@ -766,7 +771,7 @@ impl<'db> Signature<'db> {
EitherOrBoth::Right(_) => { EitherOrBoth::Right(_) => {
// If there are more parameters in `other` than in `self`, then `self` is not a // If there are more parameters in `other` than in `self`, then `self` is not a
// subtype of `other`. // subtype of `other`.
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
EitherOrBoth::Both(self_parameter, other_parameter) => { EitherOrBoth::Both(self_parameter, other_parameter) => {
@ -786,7 +791,7 @@ impl<'db> Signature<'db> {
}, },
) => { ) => {
if self_default.is_none() && other_default.is_some() { if self_default.is_none() && other_default.is_some() {
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
if !check_types( if !check_types(
other_parameter.annotated_type(), other_parameter.annotated_type(),
@ -807,11 +812,11 @@ impl<'db> Signature<'db> {
}, },
) => { ) => {
if self_name != other_name { if self_name != other_name {
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
// The following checks are the same as positional-only parameters. // The following checks are the same as positional-only parameters.
if self_default.is_none() && other_default.is_some() { if self_default.is_none() && other_default.is_some() {
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
if !check_types( if !check_types(
other_parameter.annotated_type(), other_parameter.annotated_type(),
@ -896,7 +901,7 @@ impl<'db> Signature<'db> {
break; 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 // previous loop. They cannot be matched against any parameter in `other` which
// only contains keyword-only and keyword-variadic parameters so the subtype // only contains keyword-only and keyword-variadic parameters so the subtype
// relation is invalid. // relation is invalid.
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
ParameterKind::Variadic { .. } => {} ParameterKind::Variadic { .. } => {}
} }
@ -957,7 +962,7 @@ impl<'db> Signature<'db> {
.. ..
} => { } => {
if self_default.is_none() && other_default.is_some() { if self_default.is_none() && other_default.is_some() {
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
if !check_types( if !check_types(
other_parameter.annotated_type(), other_parameter.annotated_type(),
@ -978,14 +983,14 @@ impl<'db> Signature<'db> {
return result; return result;
} }
} else { } else {
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
} }
ParameterKind::KeywordVariadic { .. } => { ParameterKind::KeywordVariadic { .. } => {
let Some(self_keyword_variadic_type) = self_keyword_variadic else { let Some(self_keyword_variadic_type) = self_keyword_variadic else {
// For a `self <: other` relationship, if `other` has a keyword variadic // For a `self <: other` relationship, if `other` has a keyword variadic
// parameter, `self` must also have a keyword variadic parameter. // 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) { if !check_types(other_parameter.annotated_type(), self_keyword_variadic_type) {
return result; return result;
@ -993,7 +998,7 @@ impl<'db> Signature<'db> {
} }
_ => { _ => {
// This can only occur in case of a syntax error. // 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. // optional otherwise the subtype relation is invalid.
for (_, self_parameter) in self_keywords { for (_, self_parameter) in self_keywords {
if self_parameter.default_type().is_none() { if self_parameter.default_type().is_none() {
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
} }

View file

@ -1,6 +1,6 @@
use crate::place::PlaceAndQualifiers; use crate::place::PlaceAndQualifiers;
use crate::semantic_index::definition::Definition; use crate::semantic_index::definition::Definition;
use crate::types::constraints::Constraints; use crate::types::constraints::ConstraintSet;
use crate::types::variance::VarianceInferable; use crate::types::variance::VarianceInferable;
use crate::types::{ use crate::types::{
ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassType, DynamicType, ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassType, DynamicType,
@ -129,22 +129,22 @@ impl<'db> SubclassOfType<'db> {
} }
/// Return `true` if `self` has a certain relation to `other`. /// Return `true` if `self` has a certain relation to `other`.
pub(crate) fn has_relation_to_impl<C: Constraints<'db>>( pub(crate) fn has_relation_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: SubclassOfType<'db>, other: SubclassOfType<'db>,
relation: TypeRelation, relation: TypeRelation,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
match (self.subclass_of, other.subclass_of) { match (self.subclass_of, other.subclass_of) {
(SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => { (SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => {
C::from_bool(db, relation.is_assignability()) ConstraintSet::from(relation.is_assignability())
} }
(SubclassOfInner::Dynamic(_), SubclassOfInner::Class(other_class)) => { (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(_)) => { (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`, // 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`. /// Return` true` if `self` is a disjoint type from `other`.
/// ///
/// See [`Type::is_disjoint_from`] for more details. /// See [`Type::is_disjoint_from`] for more details.
pub(crate) fn is_disjoint_from_impl<C: Constraints<'db>>( pub(crate) fn is_disjoint_from_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
_visitor: &IsDisjointVisitor<'db, C>, _visitor: &IsDisjointVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
match (self.subclass_of, other.subclass_of) { match (self.subclass_of, other.subclass_of) {
(SubclassOfInner::Dynamic(_), _) | (_, SubclassOfInner::Dynamic(_)) => { (SubclassOfInner::Dynamic(_), _) | (_, SubclassOfInner::Dynamic(_)) => {
C::unsatisfiable(db) ConstraintSet::from(false)
} }
(SubclassOfInner::Class(self_class), SubclassOfInner::Class(other_class)) => { (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))
} }
} }
} }

View file

@ -24,7 +24,7 @@ use itertools::{Either, EitherOrBoth, Itertools};
use crate::semantic_index::definition::Definition; use crate::semantic_index::definition::Definition;
use crate::types::Truthiness; use crate::types::Truthiness;
use crate::types::class::{ClassType, KnownClass}; use crate::types::class::{ClassType, KnownClass};
use crate::types::constraints::{Constraints, IteratorConstraintsExtension}; use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
use crate::types::{ use crate::types::{
ApplyTypeMappingVisitor, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor, ApplyTypeMappingVisitor, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, Type, TypeMapping, TypeRelation, IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, Type, TypeMapping, TypeRelation,
@ -253,23 +253,23 @@ impl<'db> TupleType<'db> {
.find_legacy_typevars_impl(db, binding_context, typevars, visitor); .find_legacy_typevars_impl(db, binding_context, typevars, visitor);
} }
pub(crate) fn has_relation_to_impl<C: Constraints<'db>>( pub(crate) fn has_relation_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
relation: TypeRelation, relation: TypeRelation,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
self.tuple(db) self.tuple(db)
.has_relation_to_impl(db, other.tuple(db), relation, visitor) .has_relation_to_impl(db, other.tuple(db), relation, visitor)
} }
pub(crate) fn is_equivalent_to_impl<C: Constraints<'db>>( pub(crate) fn is_equivalent_to_impl(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
visitor: &IsEquivalentVisitor<'db, C>, visitor: &IsEquivalentVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
self.tuple(db) self.tuple(db)
.is_equivalent_to_impl(db, other.tuple(db), visitor) .is_equivalent_to_impl(db, other.tuple(db), visitor)
} }
@ -411,47 +411,49 @@ impl<'db> FixedLengthTuple<Type<'db>> {
} }
} }
fn has_relation_to_impl<C: Constraints<'db>>( fn has_relation_to_impl(
&self, &self,
db: &'db dyn Db, db: &'db dyn Db,
other: &Tuple<Type<'db>>, other: &Tuple<Type<'db>>,
relation: TypeRelation, relation: TypeRelation,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
match other { match other {
Tuple::Fixed(other) => C::from_bool(db, self.0.len() == other.0.len()).and(db, || { 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.0.iter().zip(&other.0)).when_all(db, |(self_ty, other_ty)| {
self_ty.has_relation_to_impl(db, *other_ty, relation, visitor) self_ty.has_relation_to_impl(db, *other_ty, relation, visitor)
}) })
}), })
}
Tuple::Variable(other) => { Tuple::Variable(other) => {
// This tuple must have enough elements to match up with the other tuple's prefix // 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. // 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(); let mut self_iter = self.0.iter();
for other_ty in &other.prefix { for other_ty in &other.prefix {
let Some(self_ty) = self_iter.next() else { let Some(self_ty) = self_iter.next() else {
return C::unsatisfiable(db); return ConstraintSet::from(false);
}; };
let element_constraints = let element_constraints =
self_ty.has_relation_to_impl(db, *other_ty, relation, visitor); self_ty.has_relation_to_impl(db, *other_ty, relation, visitor);
if result if result
.intersect(db, element_constraints) .intersect(db, &element_constraints)
.is_never_satisfied(db) .is_never_satisfied()
{ {
return result; return result;
} }
} }
for other_ty in other.suffix.iter().rev() { for other_ty in other.suffix.iter().rev() {
let Some(self_ty) = self_iter.next_back() else { let Some(self_ty) = self_iter.next_back() else {
return C::unsatisfiable(db); return ConstraintSet::from(false);
}; };
let element_constraints = let element_constraints =
self_ty.has_relation_to_impl(db, *other_ty, relation, visitor); self_ty.has_relation_to_impl(db, *other_ty, relation, visitor);
if result if result
.intersect(db, element_constraints) .intersect(db, &element_constraints)
.is_never_satisfied(db) .is_never_satisfied()
{ {
return result; return result;
} }
@ -468,13 +470,13 @@ impl<'db> FixedLengthTuple<Type<'db>> {
} }
} }
fn is_equivalent_to_impl<C: Constraints<'db>>( fn is_equivalent_to_impl(
&self, &self,
db: &'db dyn Db, db: &'db dyn Db,
other: &Self, other: &Self,
visitor: &IsEquivalentVisitor<'db, C>, visitor: &IsEquivalentVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
C::from_bool(db, self.0.len() == other.0.len()).and(db, || { ConstraintSet::from(self.0.len() == other.0.len()).and(db, || {
(self.0.iter()) (self.0.iter())
.zip(&other.0) .zip(&other.0)
.when_all(db, |(self_ty, other_ty)| { .when_all(db, |(self_ty, other_ty)| {
@ -732,13 +734,13 @@ impl<'db> VariableLengthTuple<Type<'db>> {
} }
} }
fn has_relation_to_impl<C: Constraints<'db>>( fn has_relation_to_impl(
&self, &self,
db: &'db dyn Db, db: &'db dyn Db,
other: &Tuple<Type<'db>>, other: &Tuple<Type<'db>>,
relation: TypeRelation, relation: TypeRelation,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
match other { match other {
Tuple::Fixed(other) => { Tuple::Fixed(other) => {
// The `...` length specifier of a variable-length tuple type is interpreted // The `...` length specifier of a variable-length tuple type is interpreted
@ -753,23 +755,23 @@ impl<'db> VariableLengthTuple<Type<'db>> {
// length. // length.
if relation == TypeRelation::Subtyping || !matches!(self.variable, Type::Dynamic(_)) 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 // 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 // tuple's prefix and suffix, and each of those elements must pairwise satisfy the
// relation. // relation.
let mut result = C::always_satisfiable(db); let mut result = ConstraintSet::from(true);
let mut other_iter = other.elements().copied(); let mut other_iter = other.elements().copied();
for self_ty in self.prenormalized_prefix_elements(db, None) { for self_ty in self.prenormalized_prefix_elements(db, None) {
let Some(other_ty) = other_iter.next() else { let Some(other_ty) = other_iter.next() else {
return C::unsatisfiable(db); return ConstraintSet::from(false);
}; };
let element_constraints = let element_constraints =
self_ty.has_relation_to_impl(db, other_ty, relation, visitor); self_ty.has_relation_to_impl(db, other_ty, relation, visitor);
if result if result
.intersect(db, element_constraints) .intersect(db, &element_constraints)
.is_never_satisfied(db) .is_never_satisfied()
{ {
return result; return result;
} }
@ -777,13 +779,13 @@ impl<'db> VariableLengthTuple<Type<'db>> {
let suffix: Vec<_> = self.prenormalized_suffix_elements(db, None).collect(); let suffix: Vec<_> = self.prenormalized_suffix_elements(db, None).collect();
for self_ty in suffix.iter().rev() { for self_ty in suffix.iter().rev() {
let Some(other_ty) = other_iter.next_back() else { let Some(other_ty) = other_iter.next_back() else {
return C::unsatisfiable(db); return ConstraintSet::from(false);
}; };
let element_constraints = let element_constraints =
self_ty.has_relation_to_impl(db, other_ty, relation, visitor); self_ty.has_relation_to_impl(db, other_ty, relation, visitor);
if result if result
.intersect(db, element_constraints) .intersect(db, &element_constraints)
.is_never_satisfied(db) .is_never_satisfied()
{ {
return result; return result;
} }
@ -807,7 +809,7 @@ impl<'db> VariableLengthTuple<Type<'db>> {
// The overlapping parts of the prefixes and suffixes must satisfy the relation. // The overlapping parts of the prefixes and suffixes must satisfy the relation.
// Any remaining parts must satisfy the relation with the other tuple's // Any remaining parts must satisfy the relation with the other tuple's
// variable-length part. // 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)) let pairwise = (self.prenormalized_prefix_elements(db, self_prenormalize_variable))
.zip_longest( .zip_longest(
other.prenormalized_prefix_elements(db, other_prenormalize_variable), other.prenormalized_prefix_elements(db, other_prenormalize_variable),
@ -823,13 +825,10 @@ impl<'db> VariableLengthTuple<Type<'db>> {
EitherOrBoth::Right(_) => { EitherOrBoth::Right(_) => {
// The rhs has a required element that the lhs is not guaranteed to // The rhs has a required element that the lhs is not guaranteed to
// provide. // provide.
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
}; };
if result if result.intersect(db, &pair_constraints).is_never_satisfied() {
.intersect(db, pair_constraints)
.is_never_satisfied(db)
{
return result; return result;
} }
} }
@ -852,13 +851,10 @@ impl<'db> VariableLengthTuple<Type<'db>> {
EitherOrBoth::Right(_) => { EitherOrBoth::Right(_) => {
// The rhs has a required element that the lhs is not guaranteed to // The rhs has a required element that the lhs is not guaranteed to
// provide. // provide.
return C::unsatisfiable(db); return ConstraintSet::from(false);
} }
}; };
if result if result.intersect(db, &pair_constraints).is_never_satisfied() {
.intersect(db, pair_constraints)
.is_never_satisfied(db)
{
return result; return result;
} }
} }
@ -872,12 +868,12 @@ impl<'db> VariableLengthTuple<Type<'db>> {
} }
} }
fn is_equivalent_to_impl<C: Constraints<'db>>( fn is_equivalent_to_impl(
&self, &self,
db: &'db dyn Db, db: &'db dyn Db,
other: &Self, other: &Self,
visitor: &IsEquivalentVisitor<'db, C>, visitor: &IsEquivalentVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
self.variable self.variable
.is_equivalent_to_impl(db, other.variable, visitor) .is_equivalent_to_impl(db, other.variable, visitor)
.and(db, || { .and(db, || {
@ -887,7 +883,9 @@ impl<'db> VariableLengthTuple<Type<'db>> {
EitherOrBoth::Both(self_ty, other_ty) => { EitherOrBoth::Both(self_ty, other_ty) => {
self_ty.is_equivalent_to_impl(db, other_ty, visitor) 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, || { .and(db, || {
@ -897,7 +895,9 @@ impl<'db> VariableLengthTuple<Type<'db>> {
EitherOrBoth::Both(self_ty, other_ty) => { EitherOrBoth::Both(self_ty, other_ty) => {
self_ty.is_equivalent_to_impl(db, other_ty, visitor) 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<Type<'db>> {
} }
} }
fn has_relation_to_impl<C: Constraints<'db>>( fn has_relation_to_impl(
&self, &self,
db: &'db dyn Db, db: &'db dyn Db,
other: &Self, other: &Self,
relation: TypeRelation, relation: TypeRelation,
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
match self { match self {
Tuple::Fixed(self_tuple) => { Tuple::Fixed(self_tuple) => {
self_tuple.has_relation_to_impl(db, other, relation, visitor) self_tuple.has_relation_to_impl(db, other, relation, visitor)
@ -1093,12 +1093,12 @@ impl<'db> Tuple<Type<'db>> {
} }
} }
fn is_equivalent_to_impl<C: Constraints<'db>>( fn is_equivalent_to_impl(
&self, &self,
db: &'db dyn Db, db: &'db dyn Db,
other: &Self, other: &Self,
visitor: &IsEquivalentVisitor<'db, C>, visitor: &IsEquivalentVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
match (self, other) { match (self, other) {
(Tuple::Fixed(self_tuple), Tuple::Fixed(other_tuple)) => { (Tuple::Fixed(self_tuple), Tuple::Fixed(other_tuple)) => {
self_tuple.is_equivalent_to_impl(db, other_tuple, visitor) self_tuple.is_equivalent_to_impl(db, other_tuple, visitor)
@ -1107,35 +1107,35 @@ impl<'db> Tuple<Type<'db>> {
self_tuple.is_equivalent_to_impl(db, other_tuple, visitor) self_tuple.is_equivalent_to_impl(db, other_tuple, visitor)
} }
(Tuple::Fixed(_), Tuple::Variable(_)) | (Tuple::Variable(_), Tuple::Fixed(_)) => { (Tuple::Fixed(_), Tuple::Variable(_)) | (Tuple::Variable(_), Tuple::Fixed(_)) => {
C::unsatisfiable(db) ConstraintSet::from(false)
} }
} }
} }
pub(super) fn is_disjoint_from_impl<C: Constraints<'db>>( pub(super) fn is_disjoint_from_impl(
&self, &self,
db: &'db dyn Db, db: &'db dyn Db,
other: &Self, other: &Self,
visitor: &IsDisjointVisitor<'db, C>, visitor: &IsDisjointVisitor<'db>,
) -> C { ) -> ConstraintSet<'db> {
// Two tuples with an incompatible number of required elements must always be disjoint. // Two tuples with an incompatible number of required elements must always be disjoint.
let (self_min, self_max) = self.len().size_hint(); let (self_min, self_max) = self.len().size_hint();
let (other_min, other_max) = other.len().size_hint(); let (other_min, other_max) = other.len().size_hint();
if self_max.is_some_and(|max| max < other_min) { 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) { 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. // If any of the required elements are pairwise disjoint, the tuples are disjoint as well.
#[allow(clippy::items_after_statements)] #[allow(clippy::items_after_statements)]
fn any_disjoint<'s, 'db, C: Constraints<'db>>( fn any_disjoint<'s, 'db>(
db: &'db dyn Db, db: &'db dyn Db,
a: impl IntoIterator<Item = &'s Type<'db>>, a: impl IntoIterator<Item = &'s Type<'db>>,
b: impl IntoIterator<Item = &'s Type<'db>>, b: impl IntoIterator<Item = &'s Type<'db>>,
visitor: &IsDisjointVisitor<'db, C>, visitor: &IsDisjointVisitor<'db>,
) -> C ) -> ConstraintSet<'db>
where where
'db: 's, 'db: 's,
{ {