[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:
Douglas Creager 2025-08-21 09:30:09 -04:00 committed by GitHub
parent 045cba382a
commit 14fe1228e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1148 additions and 602 deletions

View file

@ -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

View file

@ -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 {

View file

@ -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

View 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
}
}

View file

@ -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
} }

View file

@ -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 {

View file

@ -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(

View file

@ -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> {

View file

@ -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)
})
} }
} }
} }

View file

@ -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.

View file

@ -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))
} }
} }
} }

View file

@ -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 {