mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +00:00
[ty] Perform assignability etc checks using new Constraints
trait (#19838)
"Why would you do this? This looks like you just replaced `bool` with an overly complex trait" Yes that's correct! This should be a no-op refactoring. It replaces all of the logic in our assignability, subtyping, equivalence, and disjointness methods to work over an arbitrary `Constraints` trait instead of only working on `bool`. The methods that `Constraints` provides looks very much like what we get from `bool`. But soon we will add a new impl of this trait, and some new methods, that let us express "fuzzy" constraints that aren't always true or false. (In particular, a constraint will express the upper and lower bounds of the allowed specializations of a typevar.) Even once we have that, most of the operations that we perform on constraint sets will be the usual boolean operations, just on sets. (`false` becomes empty/never; `true` becomes universe/always; `or` becomes union; `and` becomes intersection; `not` becomes negation.) So it's helpful to have this separate PR to refactor how we invoke those operations without introducing the new functionality yet. Note that we also have translations of `Option::is_some_and` and `is_none_or`, and of `Iterator::any` and `all`, and that the `and`, `or`, `when_any`, and `when_all` methods are meant to short-circuit, just like the corresponding boolean operations. For constraint sets, that depends on being able to implement the `is_always` and `is_never` trait methods. --------- Co-authored-by: Carl Meyer <carl@astral.sh> Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
045cba382a
commit
14fe1228e7
13 changed files with 1148 additions and 602 deletions
|
@ -327,6 +327,17 @@ def union[T: Base, U: (Base, Unrelated)](t: T, u: U) -> None:
|
||||||
static_assert(is_subtype_of(U, U | None))
|
static_assert(is_subtype_of(U, U | None))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
A bound or constrained typevar in a union with a dynamic type is assignable to the typevar:
|
||||||
|
|
||||||
|
```py
|
||||||
|
def union_with_dynamic[T: Base, U: (Base, Unrelated)](t: T, u: U) -> None:
|
||||||
|
static_assert(is_assignable_to(T | Any, T))
|
||||||
|
static_assert(is_assignable_to(U | Any, U))
|
||||||
|
|
||||||
|
static_assert(not is_subtype_of(T | Any, T))
|
||||||
|
static_assert(not is_subtype_of(U | Any, U))
|
||||||
|
```
|
||||||
|
|
||||||
And an intersection of a typevar with another type is always a subtype of the TypeVar:
|
And an intersection of a typevar with another type is always a subtype of the TypeVar:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -18,6 +18,7 @@ use crate::semantic_index::{
|
||||||
BindingWithConstraints, DeclarationWithConstraint, SemanticIndex, attribute_declarations,
|
BindingWithConstraints, DeclarationWithConstraint, SemanticIndex, attribute_declarations,
|
||||||
attribute_scopes,
|
attribute_scopes,
|
||||||
};
|
};
|
||||||
|
use crate::types::constraints::{Constraints, IteratorConstraintsExtension};
|
||||||
use crate::types::context::InferContext;
|
use crate::types::context::InferContext;
|
||||||
use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE};
|
use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE};
|
||||||
use crate::types::enums::enum_metadata;
|
use crate::types::enums::enum_metadata;
|
||||||
|
@ -28,10 +29,10 @@ use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signatu
|
||||||
use crate::types::tuple::{TupleSpec, TupleType};
|
use crate::types::tuple::{TupleSpec, TupleType};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ApplyTypeMappingVisitor, BareTypeAliasType, Binding, BoundSuperError, BoundSuperType,
|
ApplyTypeMappingVisitor, BareTypeAliasType, Binding, BoundSuperError, BoundSuperType,
|
||||||
CallableType, DataclassParams, DeprecatedInstance, HasRelationToVisitor, KnownInstanceType,
|
CallableType, DataclassParams, DeprecatedInstance, HasRelationToVisitor, IsEquivalentVisitor,
|
||||||
NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType, TypeMapping,
|
KnownInstanceType, NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType,
|
||||||
TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, VarianceInferable,
|
TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind,
|
||||||
declaration_type, infer_definition_types, todo_type,
|
VarianceInferable, declaration_type, infer_definition_types, todo_type,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
Db, FxIndexMap, FxOrderSet, Program,
|
Db, FxIndexMap, FxOrderSet, Program,
|
||||||
|
@ -49,7 +50,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
types::{
|
types::{
|
||||||
CallArguments, CallError, CallErrorKind, MetaclassCandidate, UnionBuilder, UnionType,
|
CallArguments, CallError, CallErrorKind, MetaclassCandidate, UnionBuilder, UnionType,
|
||||||
cyclic::PairVisitor, definition_expression_type,
|
definition_expression_type,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
|
@ -536,64 +537,88 @@ impl<'db> ClassType<'db> {
|
||||||
|
|
||||||
/// Return `true` if `other` is present in this class's MRO.
|
/// Return `true` if `other` is present in this class's MRO.
|
||||||
pub(super) fn is_subclass_of(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
|
pub(super) fn is_subclass_of(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
|
||||||
self.has_relation_to_impl(db, other, TypeRelation::Subtyping, &PairVisitor::new(true))
|
self.when_subclass_of(db, other)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn has_relation_to_impl(
|
pub(super) fn when_subclass_of<C: Constraints<'db>>(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
other: ClassType<'db>,
|
||||||
|
) -> C {
|
||||||
|
self.has_relation_to_impl(
|
||||||
|
db,
|
||||||
|
other,
|
||||||
|
TypeRelation::Subtyping,
|
||||||
|
&HasRelationToVisitor::new(C::always_satisfiable(db)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn has_relation_to_impl<C: Constraints<'db>>(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
other: Self,
|
other: Self,
|
||||||
relation: TypeRelation,
|
relation: TypeRelation,
|
||||||
visitor: &HasRelationToVisitor<'db>,
|
visitor: &HasRelationToVisitor<'db, C>,
|
||||||
) -> bool {
|
) -> C {
|
||||||
self.iter_mro(db).any(|base| {
|
self.iter_mro(db).when_any(db, |base| {
|
||||||
match base {
|
match base {
|
||||||
ClassBase::Dynamic(_) => match relation {
|
ClassBase::Dynamic(_) => match relation {
|
||||||
TypeRelation::Subtyping => other.is_object(db),
|
TypeRelation::Subtyping => C::from_bool(db, other.is_object(db)),
|
||||||
TypeRelation::Assignability => !other.is_final(db),
|
TypeRelation::Assignability => C::from_bool(db, !other.is_final(db)),
|
||||||
},
|
},
|
||||||
|
|
||||||
// Protocol and Generic are not represented by a ClassType.
|
// Protocol and Generic are not represented by a ClassType.
|
||||||
ClassBase::Protocol | ClassBase::Generic => false,
|
ClassBase::Protocol | ClassBase::Generic => C::unsatisfiable(db),
|
||||||
|
|
||||||
ClassBase::Class(base) => match (base, other) {
|
ClassBase::Class(base) => match (base, other) {
|
||||||
(ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other,
|
(ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => {
|
||||||
|
C::from_bool(db, base == other)
|
||||||
|
}
|
||||||
(ClassType::Generic(base), ClassType::Generic(other)) => {
|
(ClassType::Generic(base), ClassType::Generic(other)) => {
|
||||||
base.origin(db) == other.origin(db)
|
C::from_bool(db, base.origin(db) == other.origin(db)).and(db, || {
|
||||||
&& base.specialization(db).has_relation_to_impl(
|
base.specialization(db).has_relation_to_impl(
|
||||||
db,
|
db,
|
||||||
other.specialization(db),
|
other.specialization(db),
|
||||||
relation,
|
relation,
|
||||||
visitor,
|
visitor,
|
||||||
)
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
(ClassType::Generic(_), ClassType::NonGeneric(_))
|
(ClassType::Generic(_), ClassType::NonGeneric(_))
|
||||||
| (ClassType::NonGeneric(_), ClassType::Generic(_)) => false,
|
| (ClassType::NonGeneric(_), ClassType::Generic(_)) => C::unsatisfiable(db),
|
||||||
},
|
},
|
||||||
|
|
||||||
ClassBase::TypedDict => {
|
ClassBase::TypedDict => {
|
||||||
// TODO: Implement subclassing and assignability for TypedDicts.
|
// TODO: Implement subclassing and assignability for TypedDicts.
|
||||||
true
|
C::always_satisfiable(db)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
|
pub(super) fn is_equivalent_to_impl<C: Constraints<'db>>(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
other: ClassType<'db>,
|
||||||
|
visitor: &IsEquivalentVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
if self == other {
|
if self == other {
|
||||||
return true;
|
return C::always_satisfiable(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
// A non-generic class is never equivalent to a generic class.
|
// A non-generic class is never equivalent to a generic class.
|
||||||
// Two non-generic classes are only equivalent if they are equal (handled above).
|
// Two non-generic classes are only equivalent if they are equal (handled above).
|
||||||
(ClassType::NonGeneric(_), _) | (_, ClassType::NonGeneric(_)) => false,
|
(ClassType::NonGeneric(_), _) | (_, ClassType::NonGeneric(_)) => C::unsatisfiable(db),
|
||||||
|
|
||||||
(ClassType::Generic(this), ClassType::Generic(other)) => {
|
(ClassType::Generic(this), ClassType::Generic(other)) => {
|
||||||
this.origin(db) == other.origin(db)
|
C::from_bool(db, this.origin(db) == other.origin(db)).and(db, || {
|
||||||
&& this
|
this.specialization(db).is_equivalent_to_impl(
|
||||||
.specialization(db)
|
db,
|
||||||
.is_equivalent_to(db, other.specialization(db))
|
other.specialization(db),
|
||||||
|
visitor,
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1613,6 +1638,15 @@ impl<'db> ClassLiteral<'db> {
|
||||||
.contains(&ClassBase::Class(other))
|
.contains(&ClassBase::Class(other))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn when_subclass_of<C: Constraints<'db>>(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
specialization: Option<Specialization<'db>>,
|
||||||
|
other: ClassType<'db>,
|
||||||
|
) -> C {
|
||||||
|
C::from_bool(db, self.is_subclass_of(db, specialization, other))
|
||||||
|
}
|
||||||
|
|
||||||
/// Return `true` if this class constitutes a typed dict specification (inherits from
|
/// Return `true` if this class constitutes a typed dict specification (inherits from
|
||||||
/// `typing.TypedDict`, either directly or indirectly).
|
/// `typing.TypedDict`, either directly or indirectly).
|
||||||
#[salsa::tracked(
|
#[salsa::tracked(
|
||||||
|
@ -4186,6 +4220,14 @@ impl KnownClass {
|
||||||
.is_ok_and(|class| class.is_subclass_of(db, None, other))
|
.is_ok_and(|class| class.is_subclass_of(db, None, other))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn when_subclass_of<'db, C: Constraints<'db>>(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
other: ClassType<'db>,
|
||||||
|
) -> C {
|
||||||
|
C::from_bool(db, self.is_subclass_of(db, other))
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the module in which we should look up the definition for this class
|
/// Return the module in which we should look up the definition for this class
|
||||||
fn canonical_module(self, db: &dyn Db) -> KnownModule {
|
fn canonical_module(self, db: &dyn Db) -> KnownModule {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -3,8 +3,7 @@ use crate::types::generics::Specialization;
|
||||||
use crate::types::tuple::TupleType;
|
use crate::types::tuple::TupleType;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ApplyTypeMappingVisitor, ClassLiteral, ClassType, DynamicType, KnownClass, KnownInstanceType,
|
ApplyTypeMappingVisitor, ClassLiteral, ClassType, DynamicType, KnownClass, KnownInstanceType,
|
||||||
MroError, MroIterator, NormalizedVisitor, SpecialFormType, Type, TypeMapping, TypeTransformer,
|
MroError, MroIterator, NormalizedVisitor, SpecialFormType, Type, TypeMapping, todo_type,
|
||||||
todo_type,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Enumeration of the possible kinds of types we allow in class bases.
|
/// Enumeration of the possible kinds of types we allow in class bases.
|
||||||
|
@ -292,7 +291,7 @@ impl<'db> ClassBase<'db> {
|
||||||
self.apply_type_mapping_impl(
|
self.apply_type_mapping_impl(
|
||||||
db,
|
db,
|
||||||
&TypeMapping::Specialization(specialization),
|
&TypeMapping::Specialization(specialization),
|
||||||
&TypeTransformer::default(),
|
&ApplyTypeMappingVisitor::default(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
self
|
self
|
||||||
|
|
184
crates/ty_python_semantic/src/types/constraints.rs
Normal file
184
crates/ty_python_semantic/src/types/constraints.rs
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
//! Constraints under which type properties hold
|
||||||
|
//!
|
||||||
|
//! For "concrete" types (which contain no type variables), type properties like assignability have
|
||||||
|
//! simple answers: one type is either assignable to another type, or it isn't. (The _rules_ for
|
||||||
|
//! comparing two particular concrete types can be rather complex, but the _answer_ is a simple
|
||||||
|
//! "yes" or "no".)
|
||||||
|
//!
|
||||||
|
//! These properties are more complex when type variables are involved, because there are (usually)
|
||||||
|
//! many different concrete types that a typevar can be specialized to, and the type property might
|
||||||
|
//! hold for some specializations, but not for others. That means that for types that include
|
||||||
|
//! typevars, "Is this type assignable to another?" no longer makes sense as a question. The better
|
||||||
|
//! question is: "Under what constraints is this type assignable to another?".
|
||||||
|
//!
|
||||||
|
//! This module provides the machinery for representing the "under what constraints" part of that
|
||||||
|
//! question. An individual constraint restricts the specialization of a single typevar to be within a
|
||||||
|
//! particular lower and upper bound. You can then build up more complex constraint sets using
|
||||||
|
//! union, intersection, and negation operations (just like types themselves).
|
||||||
|
//!
|
||||||
|
//! NOTE: This module is currently in a transitional state: we've added a trait that our constraint
|
||||||
|
//! set implementations will conform to, and updated all of our type property implementations to
|
||||||
|
//! work on any impl of that trait. But the only impl we have right now is `bool`, which means that
|
||||||
|
//! we are still not tracking the full detail as promised in the description above. (`bool` is a
|
||||||
|
//! perfectly fine impl, but it can generate false positives when you have to break down a
|
||||||
|
//! particular assignability check into subchecks: each subcheck might say "yes", but technically
|
||||||
|
//! under conflicting constraints, which a single `bool` can't track.) Soon we will add a proper
|
||||||
|
//! constraint set implementation, and the `bool` impl of the trait (and possibly the trait itself)
|
||||||
|
//! will go away.
|
||||||
|
|
||||||
|
use crate::Db;
|
||||||
|
|
||||||
|
/// Encodes the constraints under which a type property (e.g. assignability) holds.
|
||||||
|
pub(crate) trait Constraints<'db>: Clone + Sized {
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> Constraints<'db> for bool {
|
||||||
|
fn unsatisfiable(_db: &'db dyn Db) -> Self {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn always_satisfiable(_db: &'db dyn Db) -> Self {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_never_satisfied(&self, _db: &'db dyn Db) -> bool {
|
||||||
|
!*self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_always_satisfied(&self, _db: &'db dyn Db) -> bool {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn union(&mut self, _db: &'db dyn Db, other: Self) -> &Self {
|
||||||
|
*self = *self || other;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect(&mut self, _db: &'db dyn Db, other: Self) -> &Self {
|
||||||
|
*self = *self && other;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn negate(self, _db: &'db dyn Db) -> Self {
|
||||||
|
!self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
match self {
|
||||||
|
Some(value) => f(value),
|
||||||
|
None => C::always_satisfiable(db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn when_some_and<'db, C: Constraints<'db>>(self, db: &'db dyn Db, f: impl FnOnce(T) -> C) -> C {
|
||||||
|
match self {
|
||||||
|
Some(value) => f(value),
|
||||||
|
None => C::unsatisfiable(db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An extension trait for building constraint sets from an [`Iterator`].
|
||||||
|
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
|
||||||
|
/// 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;
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
for child in self {
|
||||||
|
if result.union(db, f(child)).is_always_satisfied(db) {
|
||||||
|
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);
|
||||||
|
for child in self {
|
||||||
|
if result.intersect(db, f(child)).is_never_satisfied(db) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,7 +41,7 @@ impl<Tag> Default for TypeTransformer<'_, Tag> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type PairVisitor<'db, Tag> = CycleDetector<Tag, (Type<'db>, Type<'db>), bool>;
|
pub(crate) type PairVisitor<'db, Tag, C> = CycleDetector<Tag, (Type<'db>, Type<'db>), C>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct CycleDetector<Tag, T, R> {
|
pub(crate) struct CycleDetector<Tag, T, R> {
|
||||||
|
@ -63,7 +63,7 @@ pub(crate) struct CycleDetector<Tag, T, R> {
|
||||||
_tag: PhantomData<Tag>,
|
_tag: PhantomData<Tag>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Tag, T: Hash + Eq + Copy, R: Copy> CycleDetector<Tag, T, R> {
|
impl<Tag, T: Hash + Eq + Clone, R: Clone> CycleDetector<Tag, T, R> {
|
||||||
pub(crate) fn new(fallback: R) -> Self {
|
pub(crate) fn new(fallback: R) -> Self {
|
||||||
CycleDetector {
|
CycleDetector {
|
||||||
seen: RefCell::new(FxIndexSet::default()),
|
seen: RefCell::new(FxIndexSet::default()),
|
||||||
|
@ -75,17 +75,17 @@ impl<Tag, T: Hash + Eq + Copy, R: Copy> CycleDetector<Tag, T, R> {
|
||||||
|
|
||||||
pub(crate) fn visit(&self, item: T, func: impl FnOnce() -> R) -> R {
|
pub(crate) fn visit(&self, item: T, func: impl FnOnce() -> R) -> R {
|
||||||
if let Some(val) = self.cache.borrow().get(&item) {
|
if let Some(val) = self.cache.borrow().get(&item) {
|
||||||
return *val;
|
return val.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
// We hit a cycle
|
// We hit a cycle
|
||||||
if !self.seen.borrow_mut().insert(item) {
|
if !self.seen.borrow_mut().insert(item.clone()) {
|
||||||
return self.fallback;
|
return self.fallback.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret = func();
|
let ret = func();
|
||||||
self.seen.borrow_mut().pop();
|
self.seen.borrow_mut().pop();
|
||||||
self.cache.borrow_mut().insert(item, ret);
|
self.cache.borrow_mut().insert(item, ret.clone());
|
||||||
|
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,7 @@ use crate::semantic_index::definition::Definition;
|
||||||
use crate::semantic_index::scope::ScopeId;
|
use crate::semantic_index::scope::ScopeId;
|
||||||
use crate::semantic_index::semantic_index;
|
use crate::semantic_index::semantic_index;
|
||||||
use crate::types::call::{Binding, CallArguments};
|
use crate::types::call::{Binding, CallArguments};
|
||||||
|
use crate::types::constraints::Constraints;
|
||||||
use crate::types::context::InferContext;
|
use crate::types::context::InferContext;
|
||||||
use crate::types::diagnostic::{
|
use crate::types::diagnostic::{
|
||||||
REDUNDANT_CAST, STATIC_ASSERT_ERROR, TYPE_ASSERTION_FAILURE,
|
REDUNDANT_CAST, STATIC_ASSERT_ERROR, TYPE_ASSERTION_FAILURE,
|
||||||
|
@ -77,8 +78,9 @@ use crate::types::signatures::{CallableSignature, Signature};
|
||||||
use crate::types::visitor::any_over_type;
|
use crate::types::visitor::any_over_type;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
BoundMethodType, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, ClassType,
|
BoundMethodType, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, ClassType,
|
||||||
DeprecatedInstance, DynamicType, KnownClass, NormalizedVisitor, Truthiness, Type, TypeMapping,
|
DeprecatedInstance, DynamicType, HasRelationToVisitor, IsEquivalentVisitor, KnownClass,
|
||||||
TypeRelation, TypeTransformer, UnionBuilder, all_members, walk_type_mapping,
|
NormalizedVisitor, Truthiness, Type, TypeMapping, TypeRelation, UnionBuilder, all_members,
|
||||||
|
walk_type_mapping,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
|
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
|
||||||
|
|
||||||
|
@ -858,15 +860,16 @@ impl<'db> FunctionType<'db> {
|
||||||
BoundMethodType::new(db, self, self_instance)
|
BoundMethodType::new(db, self, self_instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn has_relation_to(
|
pub(crate) fn has_relation_to_impl<C: Constraints<'db>>(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
other: Self,
|
other: Self,
|
||||||
relation: TypeRelation,
|
relation: TypeRelation,
|
||||||
) -> bool {
|
_visitor: &HasRelationToVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
match relation {
|
match relation {
|
||||||
TypeRelation::Subtyping => self.is_subtype_of(db, other),
|
TypeRelation::Subtyping => C::from_bool(db, self.is_subtype_of(db, other)),
|
||||||
TypeRelation::Assignability => self.is_assignable_to(db, other),
|
TypeRelation::Assignability => C::from_bool(db, self.is_assignable_to(db, other)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -895,16 +898,21 @@ impl<'db> FunctionType<'db> {
|
||||||
&& self.signature(db).is_assignable_to(db, other.signature(db))
|
&& self.signature(db).is_assignable_to(db, other.signature(db))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
pub(crate) fn is_equivalent_to_impl<C: Constraints<'db>>(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
other: Self,
|
||||||
|
visitor: &IsEquivalentVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
if self.normalized(db) == other.normalized(db) {
|
if self.normalized(db) == other.normalized(db) {
|
||||||
return true;
|
return C::always_satisfiable(db);
|
||||||
}
|
}
|
||||||
if self.literal(db) != other.literal(db) {
|
if self.literal(db) != other.literal(db) {
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
}
|
}
|
||||||
let self_signature = self.signature(db);
|
let self_signature = self.signature(db);
|
||||||
let other_signature = other.signature(db);
|
let other_signature = other.signature(db);
|
||||||
self_signature.is_equivalent_to(db, other_signature)
|
self_signature.is_equivalent_to_impl(db, other_signature, visitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn find_legacy_typevars(
|
pub(crate) fn find_legacy_typevars(
|
||||||
|
@ -920,7 +928,7 @@ impl<'db> FunctionType<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
|
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
|
||||||
self.normalized_impl(db, &TypeTransformer::default())
|
self.normalized_impl(db, &NormalizedVisitor::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||||
|
|
|
@ -9,13 +9,14 @@ use crate::semantic_index::definition::Definition;
|
||||||
use crate::semantic_index::scope::{FileScopeId, NodeWithScopeKind};
|
use crate::semantic_index::scope::{FileScopeId, NodeWithScopeKind};
|
||||||
use crate::types::class::ClassType;
|
use crate::types::class::ClassType;
|
||||||
use crate::types::class_base::ClassBase;
|
use crate::types::class_base::ClassBase;
|
||||||
|
use crate::types::constraints::Constraints;
|
||||||
use crate::types::infer::infer_definition_types;
|
use crate::types::infer::infer_definition_types;
|
||||||
use crate::types::instance::{Protocol, ProtocolInstanceType};
|
use crate::types::instance::{Protocol, ProtocolInstanceType};
|
||||||
use crate::types::signatures::{Parameter, Parameters, Signature};
|
use crate::types::signatures::{Parameter, Parameters, Signature};
|
||||||
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
|
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, HasRelationToVisitor, KnownClass,
|
ApplyTypeMappingVisitor, BoundTypeVarInstance, HasRelationToVisitor, IsEquivalentVisitor,
|
||||||
KnownInstanceType, NormalizedVisitor, Type, TypeMapping, TypeRelation, TypeTransformer,
|
KnownClass, KnownInstanceType, NormalizedVisitor, Type, TypeMapping, TypeRelation,
|
||||||
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarVariance, UnionType, binding_type,
|
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarVariance, UnionType, binding_type,
|
||||||
declaration_type,
|
declaration_type,
|
||||||
};
|
};
|
||||||
|
@ -471,7 +472,7 @@ impl<'db> Specialization<'db> {
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
type_mapping: &TypeMapping<'a, 'db>,
|
type_mapping: &TypeMapping<'a, 'db>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.apply_type_mapping_impl(db, type_mapping, &TypeTransformer::default())
|
self.apply_type_mapping_impl(db, type_mapping, &ApplyTypeMappingVisitor::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn apply_type_mapping_impl<'a>(
|
pub(crate) fn apply_type_mapping_impl<'a>(
|
||||||
|
@ -560,16 +561,16 @@ impl<'db> Specialization<'db> {
|
||||||
Specialization::new(db, self.generic_context(db), types, tuple_inner)
|
Specialization::new(db, self.generic_context(db), types, tuple_inner)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn has_relation_to_impl(
|
pub(crate) fn has_relation_to_impl<C: Constraints<'db>>(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
other: Self,
|
other: Self,
|
||||||
relation: TypeRelation,
|
relation: TypeRelation,
|
||||||
visitor: &HasRelationToVisitor<'db>,
|
visitor: &HasRelationToVisitor<'db, C>,
|
||||||
) -> bool {
|
) -> C {
|
||||||
let generic_context = self.generic_context(db);
|
let generic_context = self.generic_context(db);
|
||||||
if generic_context != other.generic_context(db) {
|
if generic_context != other.generic_context(db) {
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (Some(self_tuple), Some(other_tuple)) = (self.tuple_inner(db), other.tuple_inner(db))
|
if let (Some(self_tuple), Some(other_tuple)) = (self.tuple_inner(db), other.tuple_inner(db))
|
||||||
|
@ -577,6 +578,7 @@ impl<'db> Specialization<'db> {
|
||||||
return self_tuple.has_relation_to_impl(db, other_tuple, relation, visitor);
|
return self_tuple.has_relation_to_impl(db, other_tuple, relation, visitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut result = C::always_satisfiable(db);
|
||||||
for ((bound_typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
|
for ((bound_typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
|
||||||
.zip(self.types(db))
|
.zip(self.types(db))
|
||||||
.zip(other.types(db))
|
.zip(other.types(db))
|
||||||
|
@ -584,7 +586,7 @@ impl<'db> Specialization<'db> {
|
||||||
if self_type.is_dynamic() || other_type.is_dynamic() {
|
if self_type.is_dynamic() || other_type.is_dynamic() {
|
||||||
match relation {
|
match relation {
|
||||||
TypeRelation::Assignability => continue,
|
TypeRelation::Assignability => continue,
|
||||||
TypeRelation::Subtyping => return false,
|
TypeRelation::Subtyping => return C::unsatisfiable(db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -596,11 +598,12 @@ impl<'db> Specialization<'db> {
|
||||||
// - bivariant: skip, can't make subtyping/assignability false
|
// - bivariant: skip, can't make subtyping/assignability false
|
||||||
let compatible = match bound_typevar.variance(db) {
|
let compatible = match bound_typevar.variance(db) {
|
||||||
TypeVarVariance::Invariant => match relation {
|
TypeVarVariance::Invariant => match relation {
|
||||||
TypeRelation::Subtyping => self_type.is_equivalent_to(db, *other_type),
|
TypeRelation::Subtyping => self_type.when_equivalent_to(db, *other_type),
|
||||||
TypeRelation::Assignability => {
|
TypeRelation::Assignability => C::from_bool(
|
||||||
|
db,
|
||||||
self_type.is_assignable_to(db, *other_type)
|
self_type.is_assignable_to(db, *other_type)
|
||||||
&& other_type.is_assignable_to(db, *self_type)
|
&& other_type.is_assignable_to(db, *self_type),
|
||||||
}
|
),
|
||||||
},
|
},
|
||||||
TypeVarVariance::Covariant => {
|
TypeVarVariance::Covariant => {
|
||||||
self_type.has_relation_to_impl(db, *other_type, relation, visitor)
|
self_type.has_relation_to_impl(db, *other_type, relation, visitor)
|
||||||
|
@ -608,22 +611,28 @@ impl<'db> Specialization<'db> {
|
||||||
TypeVarVariance::Contravariant => {
|
TypeVarVariance::Contravariant => {
|
||||||
other_type.has_relation_to_impl(db, *self_type, relation, visitor)
|
other_type.has_relation_to_impl(db, *self_type, relation, visitor)
|
||||||
}
|
}
|
||||||
TypeVarVariance::Bivariant => true,
|
TypeVarVariance::Bivariant => C::always_satisfiable(db),
|
||||||
};
|
};
|
||||||
if !compatible {
|
if result.intersect(db, compatible).is_never_satisfied(db) {
|
||||||
return false;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Specialization<'db>) -> bool {
|
pub(crate) fn is_equivalent_to_impl<C: Constraints<'db>>(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
other: Specialization<'db>,
|
||||||
|
visitor: &IsEquivalentVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
let generic_context = self.generic_context(db);
|
let generic_context = self.generic_context(db);
|
||||||
if generic_context != other.generic_context(db) {
|
if generic_context != other.generic_context(db) {
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut result = C::always_satisfiable(db);
|
||||||
for ((bound_typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
|
for ((bound_typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
|
||||||
.zip(self.types(db))
|
.zip(self.types(db))
|
||||||
.zip(other.types(db))
|
.zip(other.types(db))
|
||||||
|
@ -637,25 +646,28 @@ impl<'db> Specialization<'db> {
|
||||||
let compatible = match bound_typevar.variance(db) {
|
let compatible = match bound_typevar.variance(db) {
|
||||||
TypeVarVariance::Invariant
|
TypeVarVariance::Invariant
|
||||||
| TypeVarVariance::Covariant
|
| TypeVarVariance::Covariant
|
||||||
| TypeVarVariance::Contravariant => self_type.is_equivalent_to(db, *other_type),
|
| TypeVarVariance::Contravariant => {
|
||||||
TypeVarVariance::Bivariant => true,
|
self_type.is_equivalent_to_impl(db, *other_type, visitor)
|
||||||
|
}
|
||||||
|
TypeVarVariance::Bivariant => C::always_satisfiable(db),
|
||||||
};
|
};
|
||||||
if !compatible {
|
if result.intersect(db, compatible).is_never_satisfied(db) {
|
||||||
return false;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match (self.tuple_inner(db), other.tuple_inner(db)) {
|
match (self.tuple_inner(db), other.tuple_inner(db)) {
|
||||||
(Some(_), None) | (None, Some(_)) => return false,
|
(Some(_), None) | (None, Some(_)) => return C::unsatisfiable(db),
|
||||||
(None, None) => {}
|
(None, None) => {}
|
||||||
(Some(self_tuple), Some(other_tuple)) => {
|
(Some(self_tuple), Some(other_tuple)) => {
|
||||||
if !self_tuple.is_equivalent_to(db, other_tuple) {
|
let compatible = self_tuple.is_equivalent_to_impl(db, other_tuple, visitor);
|
||||||
return false;
|
if result.intersect(db, compatible).is_never_satisfied(db) {
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn find_legacy_typevars(
|
pub(crate) fn find_legacy_typevars(
|
||||||
|
|
|
@ -7,12 +7,13 @@ use super::protocol_class::ProtocolInterface;
|
||||||
use super::{BoundTypeVarInstance, ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance};
|
use super::{BoundTypeVarInstance, ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance};
|
||||||
use crate::place::PlaceAndQualifiers;
|
use crate::place::PlaceAndQualifiers;
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
|
use crate::types::constraints::{Constraints, IteratorConstraintsExtension};
|
||||||
use crate::types::enums::is_single_member_enum;
|
use crate::types::enums::is_single_member_enum;
|
||||||
use crate::types::protocol_class::walk_protocol_interface;
|
use crate::types::protocol_class::walk_protocol_interface;
|
||||||
use crate::types::tuple::{TupleSpec, TupleType};
|
use crate::types::tuple::{TupleSpec, TupleType};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ApplyTypeMappingVisitor, ClassBase, DynamicType, HasRelationToVisitor, IsDisjointVisitor,
|
ApplyTypeMappingVisitor, ClassBase, DynamicType, HasRelationToVisitor, IsDisjointVisitor,
|
||||||
NormalizedVisitor, TypeMapping, TypeRelation, TypeTransformer, VarianceInferable,
|
IsEquivalentVisitor, NormalizedVisitor, TypeMapping, TypeRelation, VarianceInferable,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet};
|
use crate::{Db, FxOrderSet};
|
||||||
|
|
||||||
|
@ -92,23 +93,26 @@ impl<'db> Type<'db> {
|
||||||
SynthesizedProtocolType::new(
|
SynthesizedProtocolType::new(
|
||||||
db,
|
db,
|
||||||
ProtocolInterface::with_property_members(db, members),
|
ProtocolInterface::with_property_members(db, members),
|
||||||
&TypeTransformer::default(),
|
&NormalizedVisitor::default(),
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if `self` conforms to the interface described by `protocol`.
|
/// Return `true` if `self` conforms to the interface described by `protocol`.
|
||||||
pub(super) fn satisfies_protocol(
|
pub(super) fn satisfies_protocol<C: Constraints<'db>>(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
protocol: ProtocolInstanceType<'db>,
|
protocol: ProtocolInstanceType<'db>,
|
||||||
relation: TypeRelation,
|
relation: TypeRelation,
|
||||||
) -> bool {
|
visitor: &HasRelationToVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
protocol
|
protocol
|
||||||
.inner
|
.inner
|
||||||
.interface(db)
|
.interface(db)
|
||||||
.members(db)
|
.members(db)
|
||||||
.all(|member| member.is_satisfied_by(db, self, relation))
|
.when_all(db, |member| {
|
||||||
|
member.is_satisfied_by(db, self, relation, visitor)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,13 +268,13 @@ impl<'db> NominalInstanceType<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn has_relation_to_impl(
|
pub(super) fn has_relation_to_impl<C: Constraints<'db>>(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
other: Self,
|
other: Self,
|
||||||
relation: TypeRelation,
|
relation: TypeRelation,
|
||||||
visitor: &HasRelationToVisitor<'db>,
|
visitor: &HasRelationToVisitor<'db, C>,
|
||||||
) -> bool {
|
) -> C {
|
||||||
match (self.0, other.0) {
|
match (self.0, other.0) {
|
||||||
(
|
(
|
||||||
NominalInstanceInner::ExactTuple(tuple1),
|
NominalInstanceInner::ExactTuple(tuple1),
|
||||||
|
@ -282,35 +286,45 @@ impl<'db> NominalInstanceType<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
pub(super) fn is_equivalent_to_impl<C: Constraints<'db>>(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
other: Self,
|
||||||
|
visitor: &IsEquivalentVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
match (self.0, other.0) {
|
match (self.0, other.0) {
|
||||||
(
|
(
|
||||||
NominalInstanceInner::ExactTuple(tuple1),
|
NominalInstanceInner::ExactTuple(tuple1),
|
||||||
NominalInstanceInner::ExactTuple(tuple2),
|
NominalInstanceInner::ExactTuple(tuple2),
|
||||||
) => tuple1.is_equivalent_to(db, tuple2),
|
) => tuple1.is_equivalent_to_impl(db, tuple2, visitor),
|
||||||
(NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => {
|
(NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => {
|
||||||
class1.is_equivalent_to(db, class2)
|
class1.is_equivalent_to_impl(db, class2, visitor)
|
||||||
}
|
}
|
||||||
_ => false,
|
_ => C::unsatisfiable(db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn is_disjoint_from_impl(
|
pub(super) fn is_disjoint_from_impl<C: Constraints<'db>>(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
other: Self,
|
other: Self,
|
||||||
visitor: &IsDisjointVisitor<'db>,
|
visitor: &IsDisjointVisitor<'db, C>,
|
||||||
) -> bool {
|
) -> C {
|
||||||
|
let mut result = C::unsatisfiable(db);
|
||||||
if let Some(self_spec) = self.tuple_spec(db) {
|
if let Some(self_spec) = self.tuple_spec(db) {
|
||||||
if let Some(other_spec) = other.tuple_spec(db) {
|
if let Some(other_spec) = other.tuple_spec(db) {
|
||||||
if self_spec.is_disjoint_from_impl(db, &other_spec, visitor) {
|
let compatible = self_spec.is_disjoint_from_impl(db, &other_spec, visitor);
|
||||||
return true;
|
if result.union(db, compatible).is_always_satisfied(db) {
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
!self
|
result.or(db, || {
|
||||||
.class(db)
|
C::from_bool(
|
||||||
.could_coexist_in_mro_with(db, other.class(db))
|
db,
|
||||||
|
!(self.class(db)).could_coexist_in_mro_with(db, other.class(db)),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn is_singleton(self, db: &'db dyn Db) -> bool {
|
pub(super) fn is_singleton(self, db: &'db dyn Db) -> bool {
|
||||||
|
@ -479,7 +493,7 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||||
///
|
///
|
||||||
/// See [`Type::normalized`] for more details.
|
/// See [`Type::normalized`] for more details.
|
||||||
pub(super) fn normalized(self, db: &'db dyn Db) -> Type<'db> {
|
pub(super) fn normalized(self, db: &'db dyn Db) -> Type<'db> {
|
||||||
self.normalized_impl(db, &TypeTransformer::default())
|
self.normalized_impl(db, &NormalizedVisitor::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a "normalized" version of this `Protocol` type.
|
/// Return a "normalized" version of this `Protocol` type.
|
||||||
|
@ -491,7 +505,12 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||||
visitor: &NormalizedVisitor<'db>,
|
visitor: &NormalizedVisitor<'db>,
|
||||||
) -> Type<'db> {
|
) -> Type<'db> {
|
||||||
let object = Type::object(db);
|
let object = Type::object(db);
|
||||||
if object.satisfies_protocol(db, self, TypeRelation::Subtyping) {
|
if object.satisfies_protocol(
|
||||||
|
db,
|
||||||
|
self,
|
||||||
|
TypeRelation::Subtyping,
|
||||||
|
&HasRelationToVisitor::new(true),
|
||||||
|
) {
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
match self.inner {
|
match self.inner {
|
||||||
|
@ -505,30 +524,36 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||||
/// Return `true` if this protocol type has the given type relation to the protocol `other`.
|
/// Return `true` if this protocol type has the given type relation to the protocol `other`.
|
||||||
///
|
///
|
||||||
/// TODO: consider the types of the members as well as their existence
|
/// TODO: consider the types of the members as well as their existence
|
||||||
pub(super) fn has_relation_to(
|
pub(super) fn has_relation_to_impl<C: Constraints<'db>>(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
other: Self,
|
other: Self,
|
||||||
_relation: TypeRelation,
|
_relation: TypeRelation,
|
||||||
) -> bool {
|
visitor: &HasRelationToVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
other
|
other
|
||||||
.inner
|
.inner
|
||||||
.interface(db)
|
.interface(db)
|
||||||
.is_sub_interface_of(db, self.inner.interface(db))
|
.is_sub_interface_of(db, self.inner.interface(db), visitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if this protocol type is equivalent to the protocol `other`.
|
/// Return `true` if this protocol type is equivalent to the protocol `other`.
|
||||||
///
|
///
|
||||||
/// TODO: consider the types of the members as well as their existence
|
/// TODO: consider the types of the members as well as their existence
|
||||||
pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
pub(super) fn is_equivalent_to_impl<C: Constraints<'db>>(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
other: Self,
|
||||||
|
_visitor: &IsEquivalentVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
if self == other {
|
if self == other {
|
||||||
return true;
|
return C::always_satisfiable(db);
|
||||||
}
|
}
|
||||||
let self_normalized = self.normalized(db);
|
let self_normalized = self.normalized(db);
|
||||||
if self_normalized == Type::ProtocolInstance(other) {
|
if self_normalized == Type::ProtocolInstance(other) {
|
||||||
return true;
|
return C::always_satisfiable(db);
|
||||||
}
|
}
|
||||||
self_normalized == other.normalized(db)
|
C::from_bool(db, self_normalized == other.normalized(db))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if this protocol type is disjoint from the protocol `other`.
|
/// Return `true` if this protocol type is disjoint from the protocol `other`.
|
||||||
|
@ -536,13 +561,13 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||||
/// TODO: a protocol `X` is disjoint from a protocol `Y` if `X` and `Y`
|
/// TODO: a protocol `X` is disjoint from a protocol `Y` if `X` and `Y`
|
||||||
/// have a member with the same name but disjoint types
|
/// have a member with the same name but disjoint types
|
||||||
#[expect(clippy::unused_self)]
|
#[expect(clippy::unused_self)]
|
||||||
pub(super) fn is_disjoint_from_impl(
|
pub(super) fn is_disjoint_from_impl<C: Constraints<'db>>(
|
||||||
self,
|
self,
|
||||||
_db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
_other: Self,
|
_other: Self,
|
||||||
_visitor: &IsDisjointVisitor<'db>,
|
_visitor: &IsDisjointVisitor<'db, C>,
|
||||||
) -> bool {
|
) -> C {
|
||||||
false
|
C::unsatisfiable(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
|
pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
|
||||||
|
|
|
@ -16,9 +16,10 @@ use crate::{
|
||||||
place::{Boundness, Place, PlaceAndQualifiers, place_from_bindings, place_from_declarations},
|
place::{Boundness, Place, PlaceAndQualifiers, place_from_bindings, place_from_declarations},
|
||||||
semantic_index::{definition::Definition, use_def_map},
|
semantic_index::{definition::Definition, use_def_map},
|
||||||
types::{
|
types::{
|
||||||
BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, IsDisjointVisitor,
|
BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, HasRelationToVisitor,
|
||||||
KnownFunction, NormalizedVisitor, PropertyInstanceType, Signature, Type, TypeMapping,
|
IsDisjointVisitor, KnownFunction, NormalizedVisitor, PropertyInstanceType, Signature, Type,
|
||||||
TypeQualifiers, TypeRelation, TypeTransformer, VarianceInferable,
|
TypeMapping, TypeQualifiers, TypeRelation, VarianceInferable,
|
||||||
|
constraints::{Constraints, IteratorConstraintsExtension},
|
||||||
signatures::{Parameter, Parameters},
|
signatures::{Parameter, Parameters},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -219,10 +220,17 @@ impl<'db> ProtocolInterface<'db> {
|
||||||
/// Return `true` if if all members on `self` are also members of `other`.
|
/// Return `true` if if all members on `self` are also members of `other`.
|
||||||
///
|
///
|
||||||
/// TODO: this method should consider the types of the members as well as their names.
|
/// TODO: this method should consider the types of the members as well as their names.
|
||||||
pub(super) fn is_sub_interface_of(self, db: &'db dyn Db, other: Self) -> bool {
|
pub(super) fn is_sub_interface_of<C: Constraints<'db>>(
|
||||||
self.inner(db)
|
self,
|
||||||
.keys()
|
db: &'db dyn Db,
|
||||||
.all(|member_name| other.inner(db).contains_key(member_name))
|
other: Self,
|
||||||
|
_visitor: &HasRelationToVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
|
// 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.
|
||||||
|
self.inner(db).keys().when_all(db, |member_name| {
|
||||||
|
C::from_bool(db, other.inner(db).contains_key(member_name))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
pub(super) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||||
|
@ -318,7 +326,7 @@ pub(super) struct ProtocolMemberData<'db> {
|
||||||
|
|
||||||
impl<'db> ProtocolMemberData<'db> {
|
impl<'db> ProtocolMemberData<'db> {
|
||||||
fn normalized(&self, db: &'db dyn Db) -> Self {
|
fn normalized(&self, db: &'db dyn Db) -> Self {
|
||||||
self.normalized_impl(db, &TypeTransformer::default())
|
self.normalized_impl(db, &NormalizedVisitor::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalized_impl(&self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
fn normalized_impl(&self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||||
|
@ -504,46 +512,56 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn has_disjoint_type_from(
|
pub(super) fn has_disjoint_type_from<C: Constraints<'db>>(
|
||||||
&self,
|
&self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
other: Type<'db>,
|
other: Type<'db>,
|
||||||
visitor: &IsDisjointVisitor<'db>,
|
visitor: &IsDisjointVisitor<'db, C>,
|
||||||
) -> bool {
|
) -> C {
|
||||||
match &self.kind {
|
match &self.kind {
|
||||||
// TODO: implement disjointness for property/method members as well as attribute members
|
// TODO: implement disjointness for property/method members as well as attribute members
|
||||||
ProtocolMemberKind::Property(_) | ProtocolMemberKind::Method(_) => false,
|
ProtocolMemberKind::Property(_) | ProtocolMemberKind::Method(_) => C::unsatisfiable(db),
|
||||||
ProtocolMemberKind::Other(ty) => ty.is_disjoint_from_impl(db, other, visitor),
|
ProtocolMemberKind::Other(ty) => ty.is_disjoint_from_impl(db, other, visitor),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if `other` contains an attribute/method/property that satisfies
|
/// Return `true` if `other` contains an attribute/method/property that satisfies
|
||||||
/// the part of the interface defined by this protocol member.
|
/// the part of the interface defined by this protocol member.
|
||||||
pub(super) fn is_satisfied_by(
|
pub(super) fn is_satisfied_by<C: Constraints<'db>>(
|
||||||
&self,
|
&self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
other: Type<'db>,
|
other: Type<'db>,
|
||||||
relation: TypeRelation,
|
relation: TypeRelation,
|
||||||
) -> bool {
|
visitor: &HasRelationToVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
match &self.kind {
|
match &self.kind {
|
||||||
// TODO: consider the types of the attribute on `other` for method members
|
// TODO: consider the types of the attribute on `other` for method members
|
||||||
ProtocolMemberKind::Method(_) => matches!(
|
ProtocolMemberKind::Method(_) => C::from_bool(
|
||||||
other.to_meta_type(db).member(db, self.name).place,
|
db,
|
||||||
Place::Type(_, Boundness::Bound)
|
matches!(
|
||||||
|
other.to_meta_type(db).member(db, self.name).place,
|
||||||
|
Place::Type(_, Boundness::Bound)
|
||||||
|
),
|
||||||
),
|
),
|
||||||
// TODO: consider the types of the attribute on `other` for property members
|
// TODO: consider the types of the attribute on `other` for property members
|
||||||
ProtocolMemberKind::Property(_) => matches!(
|
ProtocolMemberKind::Property(_) => C::from_bool(
|
||||||
other.member(db, self.name).place,
|
db,
|
||||||
Place::Type(_, Boundness::Bound)
|
matches!(
|
||||||
|
other.member(db, self.name).place,
|
||||||
|
Place::Type(_, Boundness::Bound)
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ProtocolMemberKind::Other(member_type) => {
|
ProtocolMemberKind::Other(member_type) => {
|
||||||
let Place::Type(attribute_type, Boundness::Bound) =
|
let Place::Type(attribute_type, Boundness::Bound) =
|
||||||
other.member(db, self.name).place
|
other.member(db, self.name).place
|
||||||
else {
|
else {
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
};
|
};
|
||||||
member_type.has_relation_to(db, attribute_type, relation)
|
member_type
|
||||||
&& attribute_type.has_relation_to(db, *member_type, relation)
|
.has_relation_to_impl(db, attribute_type, relation, visitor)
|
||||||
|
.and(db, || {
|
||||||
|
attribute_type.has_relation_to_impl(db, *member_type, relation, visitor)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,11 @@ use smallvec::{SmallVec, smallvec_inline};
|
||||||
|
|
||||||
use super::{DynamicType, Type, TypeVarVariance, definition_expression_type};
|
use super::{DynamicType, Type, TypeVarVariance, definition_expression_type};
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
|
use crate::types::constraints::{Constraints, IteratorConstraintsExtension};
|
||||||
use crate::types::generics::{GenericContext, walk_generic_context};
|
use crate::types::generics::{GenericContext, walk_generic_context};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
BindingContext, BoundTypeVarInstance, KnownClass, NormalizedVisitor, TypeMapping, TypeRelation,
|
BindingContext, BoundTypeVarInstance, HasRelationToVisitor, IsEquivalentVisitor, KnownClass,
|
||||||
VarianceInferable, todo_type,
|
NormalizedVisitor, TypeMapping, TypeRelation, VarianceInferable, todo_type,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet};
|
use crate::{Db, FxOrderSet};
|
||||||
use ruff_python_ast::{self as ast, name::Name};
|
use ruff_python_ast::{self as ast, name::Name};
|
||||||
|
@ -112,27 +113,19 @@ impl<'db> CallableSignature<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn has_relation_to(
|
|
||||||
&self,
|
|
||||||
db: &'db dyn Db,
|
|
||||||
other: &Self,
|
|
||||||
relation: TypeRelation,
|
|
||||||
) -> bool {
|
|
||||||
match relation {
|
|
||||||
TypeRelation::Subtyping => self.is_subtype_of(db, other),
|
|
||||||
TypeRelation::Assignability => self.is_assignable_to(db, other),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check whether this callable type is a subtype of another callable type.
|
/// Check whether this callable type is a subtype of another callable type.
|
||||||
///
|
///
|
||||||
/// See [`Type::is_subtype_of`] for more details.
|
/// See [`Type::is_subtype_of`] for more details.
|
||||||
pub(crate) fn is_subtype_of(&self, db: &'db dyn Db, other: &Self) -> bool {
|
pub(crate) fn is_subtype_of(&self, db: &'db dyn Db, other: &Self) -> bool {
|
||||||
Self::has_relation_to_impl(
|
self.is_subtype_of_impl(db, other)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_subtype_of_impl<C: Constraints<'db>>(&self, db: &'db dyn Db, other: &Self) -> C {
|
||||||
|
self.has_relation_to_impl(
|
||||||
db,
|
db,
|
||||||
&self.overloads,
|
other,
|
||||||
&other.overloads,
|
|
||||||
TypeRelation::Subtyping,
|
TypeRelation::Subtyping,
|
||||||
|
&HasRelationToVisitor::new(C::always_satisfiable(db)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,55 +133,69 @@ impl<'db> CallableSignature<'db> {
|
||||||
///
|
///
|
||||||
/// See [`Type::is_assignable_to`] for more details.
|
/// See [`Type::is_assignable_to`] for more details.
|
||||||
pub(crate) fn is_assignable_to(&self, db: &'db dyn Db, other: &Self) -> bool {
|
pub(crate) fn is_assignable_to(&self, db: &'db dyn Db, other: &Self) -> bool {
|
||||||
Self::has_relation_to_impl(
|
self.has_relation_to_impl(
|
||||||
db,
|
db,
|
||||||
&self.overloads,
|
other,
|
||||||
&other.overloads,
|
|
||||||
TypeRelation::Assignability,
|
TypeRelation::Assignability,
|
||||||
|
&HasRelationToVisitor::new(true),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn has_relation_to_impl<C: Constraints<'db>>(
|
||||||
|
&self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
other: &Self,
|
||||||
|
relation: TypeRelation,
|
||||||
|
visitor: &HasRelationToVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
|
Self::has_relation_to_inner(db, &self.overloads, &other.overloads, relation, visitor)
|
||||||
|
}
|
||||||
|
|
||||||
/// Implementation of subtyping and assignability between two, possible overloaded, callable
|
/// Implementation of subtyping and assignability between two, possible overloaded, callable
|
||||||
/// types.
|
/// types.
|
||||||
fn has_relation_to_impl(
|
fn has_relation_to_inner<C: Constraints<'db>>(
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
self_signatures: &[Signature<'db>],
|
self_signatures: &[Signature<'db>],
|
||||||
other_signatures: &[Signature<'db>],
|
other_signatures: &[Signature<'db>],
|
||||||
relation: TypeRelation,
|
relation: TypeRelation,
|
||||||
) -> bool {
|
visitor: &HasRelationToVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
match (self_signatures, other_signatures) {
|
match (self_signatures, other_signatures) {
|
||||||
([self_signature], [other_signature]) => {
|
([self_signature], [other_signature]) => {
|
||||||
// Base case: both callable types contain a single signature.
|
// Base case: both callable types contain a single signature.
|
||||||
self_signature.has_relation_to(db, other_signature, relation)
|
self_signature.has_relation_to_impl(db, other_signature, relation, visitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// `self` is possibly overloaded while `other` is definitely not overloaded.
|
// `self` is possibly overloaded while `other` is definitely not overloaded.
|
||||||
(_, [_]) => self_signatures.iter().any(|self_signature| {
|
(_, [_]) => self_signatures.iter().when_any(db, |self_signature| {
|
||||||
Self::has_relation_to_impl(
|
Self::has_relation_to_inner(
|
||||||
db,
|
db,
|
||||||
std::slice::from_ref(self_signature),
|
std::slice::from_ref(self_signature),
|
||||||
other_signatures,
|
other_signatures,
|
||||||
relation,
|
relation,
|
||||||
|
visitor,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// `self` is definitely not overloaded while `other` is possibly overloaded.
|
// `self` is definitely not overloaded while `other` is possibly overloaded.
|
||||||
([_], _) => other_signatures.iter().all(|other_signature| {
|
([_], _) => other_signatures.iter().when_all(db, |other_signature| {
|
||||||
Self::has_relation_to_impl(
|
Self::has_relation_to_inner(
|
||||||
db,
|
db,
|
||||||
self_signatures,
|
self_signatures,
|
||||||
std::slice::from_ref(other_signature),
|
std::slice::from_ref(other_signature),
|
||||||
relation,
|
relation,
|
||||||
|
visitor,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// `self` is definitely overloaded while `other` is possibly overloaded.
|
// `self` is definitely overloaded while `other` is possibly overloaded.
|
||||||
(_, _) => other_signatures.iter().all(|other_signature| {
|
(_, _) => other_signatures.iter().when_all(db, |other_signature| {
|
||||||
Self::has_relation_to_impl(
|
Self::has_relation_to_inner(
|
||||||
db,
|
db,
|
||||||
self_signatures,
|
self_signatures,
|
||||||
std::slice::from_ref(other_signature),
|
std::slice::from_ref(other_signature),
|
||||||
relation,
|
relation,
|
||||||
|
visitor,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
@ -197,18 +204,24 @@ impl<'db> CallableSignature<'db> {
|
||||||
/// Check whether this callable type is equivalent to another callable type.
|
/// Check whether this callable type is equivalent to another callable type.
|
||||||
///
|
///
|
||||||
/// See [`Type::is_equivalent_to`] for more details.
|
/// See [`Type::is_equivalent_to`] for more details.
|
||||||
pub(crate) fn is_equivalent_to(&self, db: &'db dyn Db, other: &Self) -> bool {
|
pub(crate) fn is_equivalent_to_impl<C: Constraints<'db>>(
|
||||||
|
&self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
other: &Self,
|
||||||
|
visitor: &IsEquivalentVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
match (self.overloads.as_slice(), other.overloads.as_slice()) {
|
match (self.overloads.as_slice(), other.overloads.as_slice()) {
|
||||||
([self_signature], [other_signature]) => {
|
([self_signature], [other_signature]) => {
|
||||||
// Common case: both callable types contain a single signature, use the custom
|
// Common case: both callable types contain a single signature, use the custom
|
||||||
// equivalence check instead of delegating it to the subtype check.
|
// equivalence check instead of delegating it to the subtype check.
|
||||||
self_signature.is_equivalent_to(db, other_signature)
|
self_signature.is_equivalent_to_impl(db, other_signature, visitor)
|
||||||
}
|
}
|
||||||
(_, _) => {
|
(_, _) => {
|
||||||
if self == other {
|
if self == other {
|
||||||
return true;
|
return C::always_satisfiable(db);
|
||||||
}
|
}
|
||||||
self.is_subtype_of(db, other) && other.is_subtype_of(db, self)
|
self.is_subtype_of_impl::<C>(db, other)
|
||||||
|
.and(db, || other.is_subtype_of_impl(db, self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -498,23 +511,31 @@ impl<'db> Signature<'db> {
|
||||||
/// Return `true` if `self` has exactly the same set of possible static materializations as
|
/// Return `true` if `self` has exactly the same set of possible static materializations as
|
||||||
/// `other` (if `self` represents the same set of possible sets of possible runtime objects as
|
/// `other` (if `self` represents the same set of possible sets of possible runtime objects as
|
||||||
/// `other`).
|
/// `other`).
|
||||||
pub(crate) fn is_equivalent_to(&self, db: &'db dyn Db, other: &Signature<'db>) -> bool {
|
pub(crate) fn is_equivalent_to_impl<C: Constraints<'db>>(
|
||||||
let check_types = |self_type: Option<Type<'db>>, other_type: Option<Type<'db>>| {
|
&self,
|
||||||
self_type
|
db: &'db dyn Db,
|
||||||
.unwrap_or(Type::unknown())
|
other: &Signature<'db>,
|
||||||
.is_equivalent_to(db, other_type.unwrap_or(Type::unknown()))
|
visitor: &IsEquivalentVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
|
let mut result = C::always_satisfiable(db);
|
||||||
|
let mut check_types = |self_type: Option<Type<'db>>, other_type: Option<Type<'db>>| {
|
||||||
|
let self_type = self_type.unwrap_or(Type::unknown());
|
||||||
|
let 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)
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.parameters.is_gradual() != other.parameters.is_gradual() {
|
if self.parameters.is_gradual() != other.parameters.is_gradual() {
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.parameters.len() != other.parameters.len() {
|
if self.parameters.len() != other.parameters.len() {
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !check_types(self.return_ty, other.return_ty) {
|
if !check_types(self.return_ty, other.return_ty) {
|
||||||
return false;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (self_parameter, other_parameter) in self.parameters.iter().zip(&other.parameters) {
|
for (self_parameter, other_parameter) in self.parameters.iter().zip(&other.parameters) {
|
||||||
|
@ -558,27 +579,28 @@ impl<'db> Signature<'db> {
|
||||||
|
|
||||||
(ParameterKind::KeywordVariadic { .. }, ParameterKind::KeywordVariadic { .. }) => {}
|
(ParameterKind::KeywordVariadic { .. }, ParameterKind::KeywordVariadic { .. }) => {}
|
||||||
|
|
||||||
_ => return false,
|
_ => return C::unsatisfiable(db),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !check_types(
|
if !check_types(
|
||||||
self_parameter.annotated_type(),
|
self_parameter.annotated_type(),
|
||||||
other_parameter.annotated_type(),
|
other_parameter.annotated_type(),
|
||||||
) {
|
) {
|
||||||
return false;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implementation of subtyping and assignability for signature.
|
/// Implementation of subtyping and assignability for signature.
|
||||||
fn has_relation_to(
|
fn has_relation_to_impl<C: Constraints<'db>>(
|
||||||
&self,
|
&self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
other: &Signature<'db>,
|
other: &Signature<'db>,
|
||||||
relation: TypeRelation,
|
relation: TypeRelation,
|
||||||
) -> bool {
|
visitor: &HasRelationToVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
/// A helper struct to zip two slices of parameters together that provides control over the
|
/// A helper struct to zip two slices of parameters together that provides control over the
|
||||||
/// two iterators individually. It also keeps track of the current parameter in each
|
/// two iterators individually. It also keeps track of the current parameter in each
|
||||||
/// iterator.
|
/// iterator.
|
||||||
|
@ -640,17 +662,18 @@ impl<'db> Signature<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let check_types = |type1: Option<Type<'db>>, type2: Option<Type<'db>>| {
|
let mut result = C::always_satisfiable(db);
|
||||||
type1.unwrap_or(Type::unknown()).has_relation_to(
|
let mut check_types = |type1: Option<Type<'db>>, type2: Option<Type<'db>>| {
|
||||||
db,
|
let type1 = type1.unwrap_or(Type::unknown());
|
||||||
type2.unwrap_or(Type::unknown()),
|
let type2 = type2.unwrap_or(Type::unknown());
|
||||||
relation,
|
!result
|
||||||
)
|
.intersect(db, type1.has_relation_to_impl(db, type2, relation, visitor))
|
||||||
|
.is_never_satisfied(db)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Return types are covariant.
|
// Return types are covariant.
|
||||||
if !check_types(self.return_ty, other.return_ty) {
|
if !check_types(self.return_ty, other.return_ty) {
|
||||||
return false;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A gradual parameter list is a supertype of the "bottom" parameter list (*args: object,
|
// A gradual parameter list is a supertype of the "bottom" parameter list (*args: object,
|
||||||
|
@ -665,13 +688,13 @@ impl<'db> Signature<'db> {
|
||||||
.keyword_variadic()
|
.keyword_variadic()
|
||||||
.is_some_and(|(_, param)| param.annotated_type().is_some_and(|ty| ty.is_object(db)))
|
.is_some_and(|(_, param)| param.annotated_type().is_some_and(|ty| ty.is_object(db)))
|
||||||
{
|
{
|
||||||
return true;
|
return C::always_satisfiable(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If either of the parameter lists is gradual (`...`), then it is assignable to and from
|
// If either of the parameter lists is gradual (`...`), then it is assignable to and from
|
||||||
// any other parameter list, but not a subtype or supertype of any other parameter list.
|
// any other parameter list, but not a subtype or supertype of any other parameter list.
|
||||||
if self.parameters.is_gradual() || other.parameters.is_gradual() {
|
if self.parameters.is_gradual() || other.parameters.is_gradual() {
|
||||||
return relation.is_assignability();
|
return C::from_bool(db, relation.is_assignability());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut parameters = ParametersZip {
|
let mut parameters = ParametersZip {
|
||||||
|
@ -689,7 +712,7 @@ impl<'db> Signature<'db> {
|
||||||
let Some(next_parameter) = parameters.next() else {
|
let Some(next_parameter) = parameters.next() else {
|
||||||
// All parameters have been checked or both the parameter lists were empty. In
|
// All parameters have been checked or both the parameter lists were empty. In
|
||||||
// either case, `self` is a subtype of `other`.
|
// either case, `self` is a subtype of `other`.
|
||||||
return true;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
match next_parameter {
|
match next_parameter {
|
||||||
|
@ -709,7 +732,7 @@ impl<'db> Signature<'db> {
|
||||||
// `other`, then the non-variadic parameters in `self` must have a default
|
// `other`, then the non-variadic parameters in `self` must have a default
|
||||||
// value.
|
// value.
|
||||||
if default_type.is_none() {
|
if default_type.is_none() {
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => {
|
ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => {
|
||||||
|
@ -721,7 +744,7 @@ impl<'db> Signature<'db> {
|
||||||
EitherOrBoth::Right(_) => {
|
EitherOrBoth::Right(_) => {
|
||||||
// If there are more parameters in `other` than in `self`, then `self` is not a
|
// If there are more parameters in `other` than in `self`, then `self` is not a
|
||||||
// subtype of `other`.
|
// subtype of `other`.
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
EitherOrBoth::Both(self_parameter, other_parameter) => {
|
EitherOrBoth::Both(self_parameter, other_parameter) => {
|
||||||
|
@ -741,13 +764,13 @@ impl<'db> Signature<'db> {
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
if self_default.is_none() && other_default.is_some() {
|
if self_default.is_none() && other_default.is_some() {
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
}
|
}
|
||||||
if !check_types(
|
if !check_types(
|
||||||
other_parameter.annotated_type(),
|
other_parameter.annotated_type(),
|
||||||
self_parameter.annotated_type(),
|
self_parameter.annotated_type(),
|
||||||
) {
|
) {
|
||||||
return false;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -762,17 +785,17 @@ impl<'db> Signature<'db> {
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
if self_name != other_name {
|
if self_name != other_name {
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
}
|
}
|
||||||
// The following checks are the same as positional-only parameters.
|
// The following checks are the same as positional-only parameters.
|
||||||
if self_default.is_none() && other_default.is_some() {
|
if self_default.is_none() && other_default.is_some() {
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
}
|
}
|
||||||
if !check_types(
|
if !check_types(
|
||||||
other_parameter.annotated_type(),
|
other_parameter.annotated_type(),
|
||||||
self_parameter.annotated_type(),
|
self_parameter.annotated_type(),
|
||||||
) {
|
) {
|
||||||
return false;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -785,7 +808,7 @@ impl<'db> Signature<'db> {
|
||||||
other_parameter.annotated_type(),
|
other_parameter.annotated_type(),
|
||||||
self_parameter.annotated_type(),
|
self_parameter.annotated_type(),
|
||||||
) {
|
) {
|
||||||
return false;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(
|
if matches!(
|
||||||
|
@ -825,7 +848,7 @@ impl<'db> Signature<'db> {
|
||||||
other_parameter.annotated_type(),
|
other_parameter.annotated_type(),
|
||||||
self_parameter.annotated_type(),
|
self_parameter.annotated_type(),
|
||||||
) {
|
) {
|
||||||
return false;
|
return result;
|
||||||
}
|
}
|
||||||
parameters.next_other();
|
parameters.next_other();
|
||||||
}
|
}
|
||||||
|
@ -836,7 +859,7 @@ impl<'db> Signature<'db> {
|
||||||
other_parameter.annotated_type(),
|
other_parameter.annotated_type(),
|
||||||
self_parameter.annotated_type(),
|
self_parameter.annotated_type(),
|
||||||
) {
|
) {
|
||||||
return false;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -851,7 +874,7 @@ impl<'db> Signature<'db> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => return false,
|
_ => return C::unsatisfiable(db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -885,7 +908,7 @@ impl<'db> Signature<'db> {
|
||||||
// previous loop. They cannot be matched against any parameter in `other` which
|
// previous loop. They cannot be matched against any parameter in `other` which
|
||||||
// only contains keyword-only and keyword-variadic parameters so the subtype
|
// only contains keyword-only and keyword-variadic parameters so the subtype
|
||||||
// relation is invalid.
|
// relation is invalid.
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
}
|
}
|
||||||
ParameterKind::Variadic { .. } => {}
|
ParameterKind::Variadic { .. } => {}
|
||||||
}
|
}
|
||||||
|
@ -912,13 +935,13 @@ impl<'db> Signature<'db> {
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
if self_default.is_none() && other_default.is_some() {
|
if self_default.is_none() && other_default.is_some() {
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
}
|
}
|
||||||
if !check_types(
|
if !check_types(
|
||||||
other_parameter.annotated_type(),
|
other_parameter.annotated_type(),
|
||||||
self_parameter.annotated_type(),
|
self_parameter.annotated_type(),
|
||||||
) {
|
) {
|
||||||
return false;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => unreachable!(
|
_ => unreachable!(
|
||||||
|
@ -930,25 +953,25 @@ impl<'db> Signature<'db> {
|
||||||
other_parameter.annotated_type(),
|
other_parameter.annotated_type(),
|
||||||
self_keyword_variadic_type,
|
self_keyword_variadic_type,
|
||||||
) {
|
) {
|
||||||
return false;
|
return result;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ParameterKind::KeywordVariadic { .. } => {
|
ParameterKind::KeywordVariadic { .. } => {
|
||||||
let Some(self_keyword_variadic_type) = self_keyword_variadic else {
|
let Some(self_keyword_variadic_type) = self_keyword_variadic else {
|
||||||
// For a `self <: other` relationship, if `other` has a keyword variadic
|
// For a `self <: other` relationship, if `other` has a keyword variadic
|
||||||
// parameter, `self` must also have a keyword variadic parameter.
|
// parameter, `self` must also have a keyword variadic parameter.
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
};
|
};
|
||||||
if !check_types(other_parameter.annotated_type(), self_keyword_variadic_type) {
|
if !check_types(other_parameter.annotated_type(), self_keyword_variadic_type) {
|
||||||
return false;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// This can only occur in case of a syntax error.
|
// This can only occur in case of a syntax error.
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -957,11 +980,11 @@ impl<'db> Signature<'db> {
|
||||||
// optional otherwise the subtype relation is invalid.
|
// optional otherwise the subtype relation is invalid.
|
||||||
for (_, self_parameter) in self_keywords {
|
for (_, self_parameter) in self_keywords {
|
||||||
if self_parameter.default_type().is_none() {
|
if self_parameter.default_type().is_none() {
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new signature with the given definition.
|
/// Create a new signature with the given definition.
|
||||||
|
|
|
@ -2,11 +2,12 @@ use ruff_python_ast::name::Name;
|
||||||
|
|
||||||
use crate::place::PlaceAndQualifiers;
|
use crate::place::PlaceAndQualifiers;
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
|
use crate::types::constraints::Constraints;
|
||||||
use crate::types::variance::VarianceInferable;
|
use crate::types::variance::VarianceInferable;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, ClassType, DynamicType,
|
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, ClassType, DynamicType,
|
||||||
HasRelationToVisitor, KnownClass, MemberLookupPolicy, NormalizedVisitor, Type, TypeMapping,
|
HasRelationToVisitor, IsDisjointVisitor, KnownClass, MemberLookupPolicy, NormalizedVisitor,
|
||||||
TypeRelation, TypeVarInstance,
|
Type, TypeMapping, TypeRelation, TypeVarInstance,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet};
|
use crate::{Db, FxOrderSet};
|
||||||
|
|
||||||
|
@ -159,21 +160,23 @@ impl<'db> SubclassOfType<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if `self` has a certain relation to `other`.
|
/// Return `true` if `self` has a certain relation to `other`.
|
||||||
pub(crate) fn has_relation_to_impl(
|
pub(crate) fn has_relation_to_impl<C: Constraints<'db>>(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
other: SubclassOfType<'db>,
|
other: SubclassOfType<'db>,
|
||||||
relation: TypeRelation,
|
relation: TypeRelation,
|
||||||
visitor: &HasRelationToVisitor<'db>,
|
visitor: &HasRelationToVisitor<'db, C>,
|
||||||
) -> bool {
|
) -> C {
|
||||||
match (self.subclass_of, other.subclass_of) {
|
match (self.subclass_of, other.subclass_of) {
|
||||||
(SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => {
|
(SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => {
|
||||||
relation.is_assignability()
|
C::from_bool(db, relation.is_assignability())
|
||||||
}
|
}
|
||||||
(SubclassOfInner::Dynamic(_), SubclassOfInner::Class(other_class)) => {
|
(SubclassOfInner::Dynamic(_), SubclassOfInner::Class(other_class)) => {
|
||||||
other_class.is_object(db) || relation.is_assignability()
|
C::from_bool(db, other_class.is_object(db) || relation.is_assignability())
|
||||||
|
}
|
||||||
|
(SubclassOfInner::Class(_), SubclassOfInner::Dynamic(_)) => {
|
||||||
|
C::from_bool(db, relation.is_assignability())
|
||||||
}
|
}
|
||||||
(SubclassOfInner::Class(_), SubclassOfInner::Dynamic(_)) => relation.is_assignability(),
|
|
||||||
|
|
||||||
// For example, `type[bool]` describes all possible runtime subclasses of the class `bool`,
|
// For example, `type[bool]` describes all possible runtime subclasses of the class `bool`,
|
||||||
// and `type[int]` describes all possible runtime subclasses of the class `int`.
|
// and `type[int]` describes all possible runtime subclasses of the class `int`.
|
||||||
|
@ -187,11 +190,18 @@ impl<'db> SubclassOfType<'db> {
|
||||||
/// Return` true` if `self` is a disjoint type from `other`.
|
/// Return` true` if `self` is a disjoint type from `other`.
|
||||||
///
|
///
|
||||||
/// See [`Type::is_disjoint_from`] for more details.
|
/// See [`Type::is_disjoint_from`] for more details.
|
||||||
pub(crate) fn is_disjoint_from_impl(self, db: &'db dyn Db, other: Self) -> bool {
|
pub(crate) fn is_disjoint_from_impl<C: Constraints<'db>>(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
other: Self,
|
||||||
|
_visitor: &IsDisjointVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
match (self.subclass_of, other.subclass_of) {
|
match (self.subclass_of, other.subclass_of) {
|
||||||
(SubclassOfInner::Dynamic(_), _) | (_, SubclassOfInner::Dynamic(_)) => false,
|
(SubclassOfInner::Dynamic(_), _) | (_, SubclassOfInner::Dynamic(_)) => {
|
||||||
|
C::unsatisfiable(db)
|
||||||
|
}
|
||||||
(SubclassOfInner::Class(self_class), SubclassOfInner::Class(other_class)) => {
|
(SubclassOfInner::Class(self_class), SubclassOfInner::Class(other_class)) => {
|
||||||
!self_class.could_coexist_in_mro_with(db, other_class)
|
C::from_bool(db, !self_class.could_coexist_in_mro_with(db, other_class))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,11 @@ use itertools::{Either, EitherOrBoth, Itertools};
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
use crate::types::Truthiness;
|
use crate::types::Truthiness;
|
||||||
use crate::types::class::{ClassType, KnownClass};
|
use crate::types::class::{ClassType, KnownClass};
|
||||||
|
use crate::types::constraints::{Constraints, IteratorConstraintsExtension};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, HasRelationToVisitor, IsDisjointVisitor,
|
ApplyTypeMappingVisitor, BoundTypeVarInstance, HasRelationToVisitor, IsDisjointVisitor,
|
||||||
NormalizedVisitor, Type, TypeMapping, TypeRelation, TypeVarVariance, UnionBuilder, UnionType,
|
IsEquivalentVisitor, NormalizedVisitor, Type, TypeMapping, TypeRelation, TypeVarVariance,
|
||||||
|
UnionBuilder, UnionType,
|
||||||
};
|
};
|
||||||
use crate::util::subscript::{Nth, OutOfBoundsError, PyIndex, PySlice, StepSizeZeroError};
|
use crate::util::subscript::{Nth, OutOfBoundsError, PyIndex, PySlice, StepSizeZeroError};
|
||||||
use crate::{Db, FxOrderSet, Program};
|
use crate::{Db, FxOrderSet, Program};
|
||||||
|
@ -254,19 +256,25 @@ impl<'db> TupleType<'db> {
|
||||||
.find_legacy_typevars(db, binding_context, typevars);
|
.find_legacy_typevars(db, binding_context, typevars);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn has_relation_to_impl(
|
pub(crate) fn has_relation_to_impl<C: Constraints<'db>>(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
other: Self,
|
other: Self,
|
||||||
relation: TypeRelation,
|
relation: TypeRelation,
|
||||||
visitor: &HasRelationToVisitor<'db>,
|
visitor: &HasRelationToVisitor<'db, C>,
|
||||||
) -> bool {
|
) -> C {
|
||||||
self.tuple(db)
|
self.tuple(db)
|
||||||
.has_relation_to_impl(db, other.tuple(db), relation, visitor)
|
.has_relation_to_impl(db, other.tuple(db), relation, visitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
pub(crate) fn is_equivalent_to_impl<C: Constraints<'db>>(
|
||||||
self.tuple(db).is_equivalent_to(db, other.tuple(db))
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
other: Self,
|
||||||
|
visitor: &IsEquivalentVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
|
self.tuple(db)
|
||||||
|
.is_equivalent_to_impl(db, other.tuple(db), visitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_single_valued(self, db: &'db dyn Db) -> bool {
|
pub(crate) fn is_single_valued(self, db: &'db dyn Db) -> bool {
|
||||||
|
@ -409,56 +417,76 @@ impl<'db> FixedLengthTuple<Type<'db>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_relation_to_impl(
|
fn has_relation_to_impl<C: Constraints<'db>>(
|
||||||
&self,
|
&self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
other: &Tuple<Type<'db>>,
|
other: &Tuple<Type<'db>>,
|
||||||
relation: TypeRelation,
|
relation: TypeRelation,
|
||||||
visitor: &HasRelationToVisitor<'db>,
|
visitor: &HasRelationToVisitor<'db, C>,
|
||||||
) -> bool {
|
) -> C {
|
||||||
match other {
|
match other {
|
||||||
Tuple::Fixed(other) => {
|
Tuple::Fixed(other) => C::from_bool(db, self.0.len() == other.0.len()).and(db, || {
|
||||||
self.0.len() == other.0.len()
|
(self.0.iter().zip(&other.0)).when_all(db, |(self_ty, other_ty)| {
|
||||||
&& (self.0.iter()).zip(&other.0).all(|(self_ty, other_ty)| {
|
self_ty.has_relation_to_impl(db, *other_ty, relation, visitor)
|
||||||
self_ty.has_relation_to_impl(db, *other_ty, relation, visitor)
|
})
|
||||||
})
|
}),
|
||||||
}
|
|
||||||
|
|
||||||
Tuple::Variable(other) => {
|
Tuple::Variable(other) => {
|
||||||
// This tuple must have enough elements to match up with the other tuple's prefix
|
// This tuple must have enough elements to match up with the other tuple's prefix
|
||||||
// and suffix, and each of those elements must pairwise satisfy the relation.
|
// and suffix, and each of those elements must pairwise satisfy the relation.
|
||||||
|
let mut result = C::always_satisfiable(db);
|
||||||
let mut self_iter = self.0.iter();
|
let mut self_iter = self.0.iter();
|
||||||
for other_ty in &other.prefix {
|
for other_ty in &other.prefix {
|
||||||
let Some(self_ty) = self_iter.next() else {
|
let Some(self_ty) = self_iter.next() else {
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
};
|
};
|
||||||
if !self_ty.has_relation_to_impl(db, *other_ty, relation, visitor) {
|
let element_constraints =
|
||||||
return false;
|
self_ty.has_relation_to_impl(db, *other_ty, relation, visitor);
|
||||||
|
if result
|
||||||
|
.intersect(db, element_constraints)
|
||||||
|
.is_never_satisfied(db)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for other_ty in other.suffix.iter().rev() {
|
for other_ty in other.suffix.iter().rev() {
|
||||||
let Some(self_ty) = self_iter.next_back() else {
|
let Some(self_ty) = self_iter.next_back() else {
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
};
|
};
|
||||||
if !self_ty.has_relation_to_impl(db, *other_ty, relation, visitor) {
|
let element_constraints =
|
||||||
return false;
|
self_ty.has_relation_to_impl(db, *other_ty, relation, visitor);
|
||||||
|
if result
|
||||||
|
.intersect(db, element_constraints)
|
||||||
|
.is_never_satisfied(db)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In addition, any remaining elements in this tuple must satisfy the
|
// In addition, any remaining elements in this tuple must satisfy the
|
||||||
// variable-length portion of the other tuple.
|
// variable-length portion of the other tuple.
|
||||||
self_iter.all(|self_ty| {
|
result.and(db, || {
|
||||||
self_ty.has_relation_to_impl(db, other.variable, relation, visitor)
|
self_iter.when_all(db, |self_ty| {
|
||||||
|
self_ty.has_relation_to_impl(db, other.variable, relation, visitor)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_equivalent_to(&self, db: &'db dyn Db, other: &Self) -> bool {
|
fn is_equivalent_to_impl<C: Constraints<'db>>(
|
||||||
self.0.len() == other.0.len()
|
&self,
|
||||||
&& (self.0.iter())
|
db: &'db dyn Db,
|
||||||
|
other: &Self,
|
||||||
|
visitor: &IsEquivalentVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
|
C::from_bool(db, self.0.len() == other.0.len()).and(db, || {
|
||||||
|
(self.0.iter())
|
||||||
.zip(&other.0)
|
.zip(&other.0)
|
||||||
.all(|(self_ty, other_ty)| self_ty.is_equivalent_to(db, *other_ty))
|
.when_all(db, |(self_ty, other_ty)| {
|
||||||
|
self_ty.is_equivalent_to_impl(db, *other_ty, visitor)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_single_valued(&self, db: &'db dyn Db) -> bool {
|
fn is_single_valued(&self, db: &'db dyn Db) -> bool {
|
||||||
|
@ -717,13 +745,13 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_relation_to_impl(
|
fn has_relation_to_impl<C: Constraints<'db>>(
|
||||||
&self,
|
&self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
other: &Tuple<Type<'db>>,
|
other: &Tuple<Type<'db>>,
|
||||||
relation: TypeRelation,
|
relation: TypeRelation,
|
||||||
visitor: &HasRelationToVisitor<'db>,
|
visitor: &HasRelationToVisitor<'db, C>,
|
||||||
) -> bool {
|
) -> C {
|
||||||
match other {
|
match other {
|
||||||
Tuple::Fixed(other) => {
|
Tuple::Fixed(other) => {
|
||||||
// The `...` length specifier of a variable-length tuple type is interpreted
|
// The `...` length specifier of a variable-length tuple type is interpreted
|
||||||
|
@ -738,32 +766,43 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||||
// length.
|
// length.
|
||||||
if relation == TypeRelation::Subtyping || !matches!(self.variable, Type::Dynamic(_))
|
if relation == TypeRelation::Subtyping || !matches!(self.variable, Type::Dynamic(_))
|
||||||
{
|
{
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
// In addition, the other tuple must have enough elements to match up with this
|
// In addition, the other tuple must have enough elements to match up with this
|
||||||
// tuple's prefix and suffix, and each of those elements must pairwise satisfy the
|
// tuple's prefix and suffix, and each of those elements must pairwise satisfy the
|
||||||
// relation.
|
// relation.
|
||||||
|
let mut result = C::always_satisfiable(db);
|
||||||
let mut other_iter = other.elements().copied();
|
let mut other_iter = other.elements().copied();
|
||||||
for self_ty in self.prenormalized_prefix_elements(db, None) {
|
for self_ty in self.prenormalized_prefix_elements(db, None) {
|
||||||
let Some(other_ty) = other_iter.next() else {
|
let Some(other_ty) = other_iter.next() else {
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
};
|
};
|
||||||
if !self_ty.has_relation_to_impl(db, other_ty, relation, visitor) {
|
let element_constraints =
|
||||||
return false;
|
self_ty.has_relation_to_impl(db, other_ty, relation, visitor);
|
||||||
|
if result
|
||||||
|
.intersect(db, element_constraints)
|
||||||
|
.is_never_satisfied(db)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let suffix: Vec<_> = self.prenormalized_suffix_elements(db, None).collect();
|
let suffix: Vec<_> = self.prenormalized_suffix_elements(db, None).collect();
|
||||||
for self_ty in suffix.iter().rev() {
|
for self_ty in suffix.iter().rev() {
|
||||||
let Some(other_ty) = other_iter.next_back() else {
|
let Some(other_ty) = other_iter.next_back() else {
|
||||||
return false;
|
return C::unsatisfiable(db);
|
||||||
};
|
};
|
||||||
if !self_ty.has_relation_to_impl(db, other_ty, relation, visitor) {
|
let element_constraints =
|
||||||
return false;
|
self_ty.has_relation_to_impl(db, other_ty, relation, visitor);
|
||||||
|
if result
|
||||||
|
.intersect(db, element_constraints)
|
||||||
|
.is_never_satisfied(db)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
Tuple::Variable(other) => {
|
Tuple::Variable(other) => {
|
||||||
|
@ -781,12 +820,13 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||||
// The overlapping parts of the prefixes and suffixes must satisfy the relation.
|
// The overlapping parts of the prefixes and suffixes must satisfy the relation.
|
||||||
// Any remaining parts must satisfy the relation with the other tuple's
|
// Any remaining parts must satisfy the relation with the other tuple's
|
||||||
// variable-length part.
|
// variable-length part.
|
||||||
if !self
|
let mut result = C::always_satisfiable(db);
|
||||||
.prenormalized_prefix_elements(db, self_prenormalize_variable)
|
let pairwise = (self.prenormalized_prefix_elements(db, self_prenormalize_variable))
|
||||||
.zip_longest(
|
.zip_longest(
|
||||||
other.prenormalized_prefix_elements(db, other_prenormalize_variable),
|
other.prenormalized_prefix_elements(db, other_prenormalize_variable),
|
||||||
)
|
);
|
||||||
.all(|pair| match pair {
|
for pair in pairwise {
|
||||||
|
let pair_constraints = match pair {
|
||||||
EitherOrBoth::Both(self_ty, other_ty) => {
|
EitherOrBoth::Both(self_ty, other_ty) => {
|
||||||
self_ty.has_relation_to_impl(db, other_ty, relation, visitor)
|
self_ty.has_relation_to_impl(db, other_ty, relation, visitor)
|
||||||
}
|
}
|
||||||
|
@ -796,11 +836,15 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||||
EitherOrBoth::Right(_) => {
|
EitherOrBoth::Right(_) => {
|
||||||
// The rhs has a required element that the lhs is not guaranteed to
|
// The rhs has a required element that the lhs is not guaranteed to
|
||||||
// provide.
|
// provide.
|
||||||
false
|
return C::unsatisfiable(db);
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
{
|
if result
|
||||||
return false;
|
.intersect(db, pair_constraints)
|
||||||
|
.is_never_satisfied(db)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let self_suffix: Vec<_> = self
|
let self_suffix: Vec<_> = self
|
||||||
|
@ -809,9 +853,9 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||||
let other_suffix: Vec<_> = other
|
let other_suffix: Vec<_> = other
|
||||||
.prenormalized_suffix_elements(db, other_prenormalize_variable)
|
.prenormalized_suffix_elements(db, other_prenormalize_variable)
|
||||||
.collect();
|
.collect();
|
||||||
if !(self_suffix.iter().rev())
|
let pairwise = (self_suffix.iter().rev()).zip_longest(other_suffix.iter().rev());
|
||||||
.zip_longest(other_suffix.iter().rev())
|
for pair in pairwise {
|
||||||
.all(|pair| match pair {
|
let pair_constraints = match pair {
|
||||||
EitherOrBoth::Both(self_ty, other_ty) => {
|
EitherOrBoth::Both(self_ty, other_ty) => {
|
||||||
self_ty.has_relation_to_impl(db, *other_ty, relation, visitor)
|
self_ty.has_relation_to_impl(db, *other_ty, relation, visitor)
|
||||||
}
|
}
|
||||||
|
@ -821,34 +865,54 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||||
EitherOrBoth::Right(_) => {
|
EitherOrBoth::Right(_) => {
|
||||||
// The rhs has a required element that the lhs is not guaranteed to
|
// The rhs has a required element that the lhs is not guaranteed to
|
||||||
// provide.
|
// provide.
|
||||||
false
|
return C::unsatisfiable(db);
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
{
|
if result
|
||||||
return false;
|
.intersect(db, pair_constraints)
|
||||||
|
.is_never_satisfied(db)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// And lastly, the variable-length portions must satisfy the relation.
|
// And lastly, the variable-length portions must satisfy the relation.
|
||||||
self.variable
|
result.and(db, || {
|
||||||
.has_relation_to_impl(db, other.variable, relation, visitor)
|
self.variable
|
||||||
|
.has_relation_to_impl(db, other.variable, relation, visitor)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_equivalent_to(&self, db: &'db dyn Db, other: &Self) -> bool {
|
fn is_equivalent_to_impl<C: Constraints<'db>>(
|
||||||
self.variable.is_equivalent_to(db, other.variable)
|
&self,
|
||||||
&& (self.prenormalized_prefix_elements(db, None))
|
db: &'db dyn Db,
|
||||||
.zip_longest(other.prenormalized_prefix_elements(db, None))
|
other: &Self,
|
||||||
.all(|pair| match pair {
|
visitor: &IsEquivalentVisitor<'db, C>,
|
||||||
EitherOrBoth::Both(self_ty, other_ty) => self_ty.is_equivalent_to(db, other_ty),
|
) -> C {
|
||||||
EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => false,
|
self.variable
|
||||||
})
|
.is_equivalent_to_impl(db, other.variable, visitor)
|
||||||
&& (self.prenormalized_suffix_elements(db, None))
|
.and(db, || {
|
||||||
.zip_longest(other.prenormalized_suffix_elements(db, None))
|
(self.prenormalized_prefix_elements(db, None))
|
||||||
.all(|pair| match pair {
|
.zip_longest(other.prenormalized_prefix_elements(db, None))
|
||||||
EitherOrBoth::Both(self_ty, other_ty) => self_ty.is_equivalent_to(db, other_ty),
|
.when_all(db, |pair| match pair {
|
||||||
EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => false,
|
EitherOrBoth::Both(self_ty, other_ty) => {
|
||||||
})
|
self_ty.is_equivalent_to_impl(db, other_ty, visitor)
|
||||||
|
}
|
||||||
|
EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => C::unsatisfiable(db),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.and(db, || {
|
||||||
|
(self.prenormalized_suffix_elements(db, None))
|
||||||
|
.zip_longest(other.prenormalized_suffix_elements(db, None))
|
||||||
|
.when_all(db, |pair| match pair {
|
||||||
|
EitherOrBoth::Both(self_ty, other_ty) => {
|
||||||
|
self_ty.is_equivalent_to_impl(db, other_ty, visitor)
|
||||||
|
}
|
||||||
|
EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => C::unsatisfiable(db),
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1027,13 +1091,13 @@ impl<'db> Tuple<Type<'db>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_relation_to_impl(
|
fn has_relation_to_impl<C: Constraints<'db>>(
|
||||||
&self,
|
&self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
other: &Self,
|
other: &Self,
|
||||||
relation: TypeRelation,
|
relation: TypeRelation,
|
||||||
visitor: &HasRelationToVisitor<'db>,
|
visitor: &HasRelationToVisitor<'db, C>,
|
||||||
) -> bool {
|
) -> C {
|
||||||
match self {
|
match self {
|
||||||
Tuple::Fixed(self_tuple) => {
|
Tuple::Fixed(self_tuple) => {
|
||||||
self_tuple.has_relation_to_impl(db, other, relation, visitor)
|
self_tuple.has_relation_to_impl(db, other, relation, visitor)
|
||||||
|
@ -1044,96 +1108,95 @@ impl<'db> Tuple<Type<'db>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_equivalent_to(&self, db: &'db dyn Db, other: &Self) -> bool {
|
fn is_equivalent_to_impl<C: Constraints<'db>>(
|
||||||
match (self, other) {
|
|
||||||
(Tuple::Fixed(self_tuple), Tuple::Fixed(other_tuple)) => {
|
|
||||||
self_tuple.is_equivalent_to(db, other_tuple)
|
|
||||||
}
|
|
||||||
(Tuple::Variable(self_tuple), Tuple::Variable(other_tuple)) => {
|
|
||||||
self_tuple.is_equivalent_to(db, other_tuple)
|
|
||||||
}
|
|
||||||
(Tuple::Fixed(_), Tuple::Variable(_)) | (Tuple::Variable(_), Tuple::Fixed(_)) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn is_disjoint_from_impl(
|
|
||||||
&self,
|
&self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
other: &Self,
|
other: &Self,
|
||||||
visitor: &IsDisjointVisitor<'db>,
|
visitor: &IsEquivalentVisitor<'db, C>,
|
||||||
) -> bool {
|
) -> C {
|
||||||
|
match (self, other) {
|
||||||
|
(Tuple::Fixed(self_tuple), Tuple::Fixed(other_tuple)) => {
|
||||||
|
self_tuple.is_equivalent_to_impl(db, other_tuple, visitor)
|
||||||
|
}
|
||||||
|
(Tuple::Variable(self_tuple), Tuple::Variable(other_tuple)) => {
|
||||||
|
self_tuple.is_equivalent_to_impl(db, other_tuple, visitor)
|
||||||
|
}
|
||||||
|
(Tuple::Fixed(_), Tuple::Variable(_)) | (Tuple::Variable(_), Tuple::Fixed(_)) => {
|
||||||
|
C::unsatisfiable(db)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn is_disjoint_from_impl<C: Constraints<'db>>(
|
||||||
|
&self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
other: &Self,
|
||||||
|
visitor: &IsDisjointVisitor<'db, C>,
|
||||||
|
) -> C {
|
||||||
// Two tuples with an incompatible number of required elements must always be disjoint.
|
// Two tuples with an incompatible number of required elements must always be disjoint.
|
||||||
let (self_min, self_max) = self.len().size_hint();
|
let (self_min, self_max) = self.len().size_hint();
|
||||||
let (other_min, other_max) = other.len().size_hint();
|
let (other_min, other_max) = other.len().size_hint();
|
||||||
if self_max.is_some_and(|max| max < other_min) {
|
if self_max.is_some_and(|max| max < other_min) {
|
||||||
return true;
|
return C::always_satisfiable(db);
|
||||||
}
|
}
|
||||||
if other_max.is_some_and(|max| max < self_min) {
|
if other_max.is_some_and(|max| max < self_min) {
|
||||||
return true;
|
return C::always_satisfiable(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If any of the required elements are pairwise disjoint, the tuples are disjoint as well.
|
// If any of the required elements are pairwise disjoint, the tuples are disjoint as well.
|
||||||
#[allow(clippy::items_after_statements)]
|
#[allow(clippy::items_after_statements)]
|
||||||
fn any_disjoint<'s, 'db>(
|
fn any_disjoint<'s, 'db, C: Constraints<'db>>(
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
a: impl IntoIterator<Item = &'s Type<'db>>,
|
a: impl IntoIterator<Item = &'s Type<'db>>,
|
||||||
b: impl IntoIterator<Item = &'s Type<'db>>,
|
b: impl IntoIterator<Item = &'s Type<'db>>,
|
||||||
visitor: &IsDisjointVisitor<'db>,
|
visitor: &IsDisjointVisitor<'db, C>,
|
||||||
) -> bool
|
) -> C
|
||||||
where
|
where
|
||||||
'db: 's,
|
'db: 's,
|
||||||
{
|
{
|
||||||
a.into_iter().zip(b).any(|(self_element, other_element)| {
|
(a.into_iter().zip(b)).when_any(db, |(self_element, other_element)| {
|
||||||
self_element.is_disjoint_from_impl(db, *other_element, visitor)
|
self_element.is_disjoint_from_impl(db, *other_element, visitor)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Tuple::Fixed(self_tuple), Tuple::Fixed(other_tuple)) => {
|
(Tuple::Fixed(self_tuple), Tuple::Fixed(other_tuple)) => {
|
||||||
if any_disjoint(db, self_tuple.elements(), other_tuple.elements(), visitor) {
|
any_disjoint(db, self_tuple.elements(), other_tuple.elements(), visitor)
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(Tuple::Variable(self_tuple), Tuple::Variable(other_tuple)) => {
|
// Note that we don't compare the variable-length portions; two pure homogeneous tuples
|
||||||
if any_disjoint(
|
// `tuple[A, ...]` and `tuple[B, ...]` can never be disjoint even if A and B are
|
||||||
db,
|
// disjoint, because `tuple[()]` would be assignable to both.
|
||||||
self_tuple.prefix_elements(),
|
(Tuple::Variable(self_tuple), Tuple::Variable(other_tuple)) => any_disjoint(
|
||||||
other_tuple.prefix_elements(),
|
db,
|
||||||
visitor,
|
self_tuple.prefix_elements(),
|
||||||
) {
|
other_tuple.prefix_elements(),
|
||||||
return true;
|
visitor,
|
||||||
}
|
)
|
||||||
if any_disjoint(
|
.or(db, || {
|
||||||
|
any_disjoint(
|
||||||
db,
|
db,
|
||||||
self_tuple.suffix_elements().rev(),
|
self_tuple.suffix_elements().rev(),
|
||||||
other_tuple.suffix_elements().rev(),
|
other_tuple.suffix_elements().rev(),
|
||||||
visitor,
|
visitor,
|
||||||
) {
|
)
|
||||||
return true;
|
}),
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(Tuple::Fixed(fixed), Tuple::Variable(variable))
|
(Tuple::Fixed(fixed), Tuple::Variable(variable))
|
||||||
| (Tuple::Variable(variable), Tuple::Fixed(fixed)) => {
|
| (Tuple::Variable(variable), Tuple::Fixed(fixed)) => {
|
||||||
if any_disjoint(db, fixed.elements(), variable.prefix_elements(), visitor) {
|
any_disjoint(db, fixed.elements(), variable.prefix_elements(), visitor).or(
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if any_disjoint(
|
|
||||||
db,
|
db,
|
||||||
fixed.elements().rev(),
|
|| {
|
||||||
variable.suffix_elements().rev(),
|
any_disjoint(
|
||||||
visitor,
|
db,
|
||||||
) {
|
fixed.elements().rev(),
|
||||||
return true;
|
variable.suffix_elements().rev(),
|
||||||
}
|
visitor,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Two pure homogeneous tuples `tuple[A, ...]` and `tuple[B, ...]` can never be
|
|
||||||
// disjoint even if A and B are disjoint, because `tuple[()]` would be assignable to
|
|
||||||
// both.
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_single_valued(&self, db: &'db dyn Db) -> bool {
|
pub(crate) fn is_single_valued(&self, db: &'db dyn Db) -> bool {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue