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