mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 06:11:21 +00:00
[ty] Improve assignability/subtyping between two protocol types
This commit is contained in:
parent
1cf19732b9
commit
5ccc8b2a86
13 changed files with 900 additions and 333 deletions
|
@ -1221,11 +1221,7 @@ from typing_extensions import LiteralString
|
|||
|
||||
def f(a: Foo, b: list[str], c: list[LiteralString], e):
|
||||
reveal_type(e) # revealed: Unknown
|
||||
|
||||
# TODO: we should select the second overload here and reveal `str`
|
||||
# (the incorrect result is due to missing logic in protocol subtyping/assignability)
|
||||
reveal_type(a.join(b)) # revealed: LiteralString
|
||||
|
||||
reveal_type(a.join(b)) # revealed: str
|
||||
reveal_type(a.join(c)) # revealed: LiteralString
|
||||
|
||||
# since both overloads match and they have return types that are not equivalent,
|
||||
|
|
|
@ -205,8 +205,8 @@ from typing import Protocol
|
|||
import proto_a
|
||||
import proto_b
|
||||
|
||||
# TODO should be error: [invalid-assignment] "Object of type `proto_b.Drawable` is not assignable to `proto_a.Drawable`"
|
||||
def _(drawable_b: proto_b.Drawable):
|
||||
# error: [invalid-assignment] "Object of type `proto_b.Drawable` is not assignable to `proto_a.Drawable`"
|
||||
drawable: proto_a.Drawable = drawable_b
|
||||
```
|
||||
|
||||
|
|
|
@ -247,8 +247,7 @@ class StrIterator:
|
|||
|
||||
def f(x: IntIterator | StrIterator):
|
||||
for a in x:
|
||||
# TODO: this should be `int | str` (https://github.com/astral-sh/ty/issues/1089)
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(a) # revealed: int | str
|
||||
```
|
||||
|
||||
Most real-world iterable types use `Iterator` as the return annotation of their `__iter__` methods:
|
||||
|
@ -260,14 +259,11 @@ def g(
|
|||
c: Literal["foo", b"bar"],
|
||||
):
|
||||
for x in a:
|
||||
# TODO: should be `int | str` (https://github.com/astral-sh/ty/issues/1089)
|
||||
reveal_type(x) # revealed: int
|
||||
reveal_type(x) # revealed: int | str
|
||||
for y in b:
|
||||
# TODO: should be `str | int` (https://github.com/astral-sh/ty/issues/1089)
|
||||
reveal_type(y) # revealed: str
|
||||
reveal_type(y) # revealed: str | int
|
||||
for z in c:
|
||||
# TODO: should be `LiteralString | int` (https://github.com/astral-sh/ty/issues/1089)
|
||||
reveal_type(z) # revealed: LiteralString
|
||||
reveal_type(z) # revealed: LiteralString | int
|
||||
```
|
||||
|
||||
## Union type as iterable where one union element has no `__iter__` method
|
||||
|
|
|
@ -617,11 +617,10 @@ static_assert(is_assignable_to(Foo, HasX))
|
|||
static_assert(not is_subtype_of(Foo, HasXY))
|
||||
static_assert(not is_assignable_to(Foo, HasXY))
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(not is_subtype_of(HasXIntSub, HasX)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(HasXIntSub, HasX)) # error: [static-assert-error]
|
||||
static_assert(not is_subtype_of(HasX, HasXIntSub)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(HasX, HasXIntSub)) # error: [static-assert-error]
|
||||
static_assert(not is_subtype_of(HasXIntSub, HasX))
|
||||
static_assert(not is_assignable_to(HasXIntSub, HasX))
|
||||
static_assert(not is_subtype_of(HasX, HasXIntSub))
|
||||
static_assert(not is_assignable_to(HasX, HasXIntSub))
|
||||
|
||||
class FooSub(Foo): ...
|
||||
|
||||
|
@ -2286,10 +2285,9 @@ class MethodPUnrelated(Protocol):
|
|||
|
||||
static_assert(is_subtype_of(MethodPSub, MethodPSuper))
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(not is_assignable_to(MethodPUnrelated, MethodPSuper)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(MethodPSuper, MethodPUnrelated)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(MethodPSuper, MethodPSub)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(MethodPUnrelated, MethodPSuper))
|
||||
static_assert(not is_assignable_to(MethodPSuper, MethodPUnrelated))
|
||||
static_assert(not is_assignable_to(MethodPSuper, MethodPSub))
|
||||
```
|
||||
|
||||
## Subtyping between protocols with method members and protocols with non-method members
|
||||
|
@ -2348,8 +2346,7 @@ And for the same reason, they are never assignable to attribute members (which a
|
|||
class Attribute(Protocol):
|
||||
f: Callable[[], bool]
|
||||
|
||||
# TODO: should pass
|
||||
static_assert(not is_assignable_to(Method, Attribute)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(Method, Attribute))
|
||||
```
|
||||
|
||||
Protocols with attribute members, meanwhile, cannot be assigned to protocols with method members,
|
||||
|
@ -2358,9 +2355,8 @@ this is not true for attribute members. The same principle also applies for prot
|
|||
members
|
||||
|
||||
```py
|
||||
# TODO: this should pass
|
||||
static_assert(not is_assignable_to(PropertyBool, Method)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(Attribute, Method)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(PropertyBool, Method))
|
||||
static_assert(not is_assignable_to(Attribute, Method))
|
||||
```
|
||||
|
||||
But an exception to this rule is if an attribute member is marked as `ClassVar`, as this guarantees
|
||||
|
@ -2379,9 +2375,8 @@ static_assert(is_assignable_to(ClassVarAttribute, Method))
|
|||
class ClassVarAttributeBad(Protocol):
|
||||
f: ClassVar[Callable[[], str]]
|
||||
|
||||
# TODO: these should pass:
|
||||
static_assert(not is_subtype_of(ClassVarAttributeBad, Method)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(ClassVarAttributeBad, Method)) # error: [static-assert-error]
|
||||
static_assert(not is_subtype_of(ClassVarAttributeBad, Method))
|
||||
static_assert(not is_assignable_to(ClassVarAttributeBad, Method))
|
||||
```
|
||||
|
||||
## Narrowing of protocols
|
||||
|
@ -2702,9 +2697,8 @@ class RecursiveNonFullyStatic(Protocol):
|
|||
parent: RecursiveNonFullyStatic
|
||||
x: Any
|
||||
|
||||
# TODO: these should pass, once we take into account types of members
|
||||
static_assert(not is_subtype_of(RecursiveFullyStatic, RecursiveNonFullyStatic)) # error: [static-assert-error]
|
||||
static_assert(not is_subtype_of(RecursiveNonFullyStatic, RecursiveFullyStatic)) # error: [static-assert-error]
|
||||
static_assert(not is_subtype_of(RecursiveFullyStatic, RecursiveNonFullyStatic))
|
||||
static_assert(not is_subtype_of(RecursiveNonFullyStatic, RecursiveFullyStatic))
|
||||
|
||||
static_assert(is_assignable_to(RecursiveNonFullyStatic, RecursiveNonFullyStatic))
|
||||
static_assert(is_assignable_to(RecursiveFullyStatic, RecursiveNonFullyStatic))
|
||||
|
@ -2722,9 +2716,7 @@ class RecursiveOptionalParent(Protocol):
|
|||
static_assert(is_assignable_to(RecursiveOptionalParent, RecursiveOptionalParent))
|
||||
|
||||
# Due to invariance of mutable attribute members, neither is assignable to the other
|
||||
#
|
||||
# TODO: should pass
|
||||
static_assert(not is_assignable_to(RecursiveNonFullyStatic, RecursiveOptionalParent)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(RecursiveNonFullyStatic, RecursiveOptionalParent))
|
||||
static_assert(not is_assignable_to(RecursiveOptionalParent, RecursiveNonFullyStatic))
|
||||
|
||||
class Other(Protocol):
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -27,11 +27,11 @@ use crate::types::typed_dict::typed_dict_params_from_class_def;
|
|||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, Binding, BoundSuperError, BoundSuperType, CallableType,
|
||||
DataclassParams, DeprecatedInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
|
||||
IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind,
|
||||
NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType, TypeContext,
|
||||
TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind,
|
||||
TypedDictParams, UnionBuilder, VarianceInferable, declaration_type, determine_upper_bound,
|
||||
infer_definition_types,
|
||||
IsDisjointVisitor, IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType,
|
||||
MaterializationKind, NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType,
|
||||
TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance,
|
||||
TypeVarKind, TypedDictParams, UnionBuilder, VarianceInferable, declaration_type,
|
||||
determine_upper_bound, infer_definition_types,
|
||||
};
|
||||
use crate::{
|
||||
Db, FxIndexMap, FxOrderSet, Program,
|
||||
|
@ -538,6 +538,7 @@ impl<'db> ClassType<'db> {
|
|||
other,
|
||||
TypeRelation::Subtyping,
|
||||
&HasRelationToVisitor::default(),
|
||||
&IsDisjointVisitor::default(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -546,7 +547,8 @@ impl<'db> ClassType<'db> {
|
|||
db: &'db dyn Db,
|
||||
other: Self,
|
||||
relation: TypeRelation,
|
||||
visitor: &HasRelationToVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
self.iter_mro(db).when_any(db, |base| {
|
||||
match base {
|
||||
|
@ -568,7 +570,8 @@ impl<'db> ClassType<'db> {
|
|||
db,
|
||||
other.specialization(db),
|
||||
relation,
|
||||
visitor,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -16,9 +16,9 @@ use crate::types::signatures::{Parameter, Parameters, Signature};
|
|||
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
|
||||
IsEquivalentVisitor, KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor,
|
||||
Type, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind,
|
||||
TypeVarVariance, UnionType, binding_type, declaration_type,
|
||||
IsDisjointVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType, MaterializationKind,
|
||||
NormalizedVisitor, Type, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance,
|
||||
TypeVarKind, TypeVarVariance, UnionType, binding_type, declaration_type,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
|
@ -481,7 +481,8 @@ fn is_subtype_in_invariant_position<'db>(
|
|||
derived_materialization: MaterializationKind,
|
||||
base_type: &Type<'db>,
|
||||
base_materialization: MaterializationKind,
|
||||
visitor: &HasRelationToVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
let derived_top = derived_type.top_materialization(db);
|
||||
let derived_bottom = derived_type.bottom_materialization(db);
|
||||
|
@ -501,7 +502,13 @@ fn is_subtype_in_invariant_position<'db>(
|
|||
return ConstraintSet::from(true);
|
||||
}
|
||||
|
||||
derived.has_relation_to_impl(db, base, TypeRelation::Subtyping, visitor)
|
||||
derived.has_relation_to_impl(
|
||||
db,
|
||||
base,
|
||||
TypeRelation::Subtyping,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
};
|
||||
match (derived_materialization, base_materialization) {
|
||||
// `Derived` is a subtype of `Base` if the range of materializations covered by `Derived`
|
||||
|
@ -543,6 +550,7 @@ fn is_subtype_in_invariant_position<'db>(
|
|||
/// Whether two types encountered in an invariant position
|
||||
/// have a relation (subtyping or assignability), taking into account
|
||||
/// that the two types may come from a top or bottom materialization.
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn has_relation_in_invariant_position<'db>(
|
||||
db: &'db dyn Db,
|
||||
derived_type: &Type<'db>,
|
||||
|
@ -550,7 +558,8 @@ fn has_relation_in_invariant_position<'db>(
|
|||
base_type: &Type<'db>,
|
||||
base_materialization: Option<MaterializationKind>,
|
||||
relation: TypeRelation,
|
||||
visitor: &HasRelationToVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
match (derived_materialization, base_materialization, relation) {
|
||||
// Top and bottom materializations are fully static types, so subtyping
|
||||
|
@ -561,19 +570,27 @@ fn has_relation_in_invariant_position<'db>(
|
|||
derived_mat,
|
||||
base_type,
|
||||
base_mat,
|
||||
visitor,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
// Subtyping between invariant type parameters without a top/bottom materialization involved
|
||||
// is equivalence
|
||||
(None, None, TypeRelation::Subtyping) => derived_type.when_equivalent_to(db, *base_type),
|
||||
(None, None, TypeRelation::Assignability) => derived_type
|
||||
.has_relation_to_impl(db, *base_type, TypeRelation::Assignability, visitor)
|
||||
.has_relation_to_impl(
|
||||
db,
|
||||
*base_type,
|
||||
TypeRelation::Assignability,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
.and(db, || {
|
||||
base_type.has_relation_to_impl(
|
||||
db,
|
||||
*derived_type,
|
||||
TypeRelation::Assignability,
|
||||
visitor,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
}),
|
||||
// For gradual types, A <: B (subtyping) is defined as Top[A] <: Bottom[B]
|
||||
|
@ -583,7 +600,8 @@ fn has_relation_in_invariant_position<'db>(
|
|||
MaterializationKind::Top,
|
||||
base_type,
|
||||
base_mat,
|
||||
visitor,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
(Some(derived_mat), None, TypeRelation::Subtyping) => is_subtype_in_invariant_position(
|
||||
db,
|
||||
|
@ -591,7 +609,8 @@ fn has_relation_in_invariant_position<'db>(
|
|||
derived_mat,
|
||||
base_type,
|
||||
MaterializationKind::Bottom,
|
||||
visitor,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
// And A <~ B (assignability) is Bottom[A] <: Top[B]
|
||||
(None, Some(base_mat), TypeRelation::Assignability) => is_subtype_in_invariant_position(
|
||||
|
@ -600,7 +619,8 @@ fn has_relation_in_invariant_position<'db>(
|
|||
MaterializationKind::Bottom,
|
||||
base_type,
|
||||
base_mat,
|
||||
visitor,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
(Some(derived_mat), None, TypeRelation::Assignability) => is_subtype_in_invariant_position(
|
||||
db,
|
||||
|
@ -608,7 +628,8 @@ fn has_relation_in_invariant_position<'db>(
|
|||
derived_mat,
|
||||
base_type,
|
||||
MaterializationKind::Top,
|
||||
visitor,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -819,7 +840,8 @@ impl<'db> Specialization<'db> {
|
|||
db: &'db dyn Db,
|
||||
other: Self,
|
||||
relation: TypeRelation,
|
||||
visitor: &HasRelationToVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
let generic_context = self.generic_context(db);
|
||||
if generic_context != other.generic_context(db) {
|
||||
|
@ -828,7 +850,13 @@ impl<'db> Specialization<'db> {
|
|||
|
||||
if let (Some(self_tuple), Some(other_tuple)) = (self.tuple_inner(db), other.tuple_inner(db))
|
||||
{
|
||||
return self_tuple.has_relation_to_impl(db, other_tuple, relation, visitor);
|
||||
return self_tuple.has_relation_to_impl(
|
||||
db,
|
||||
other_tuple,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
);
|
||||
}
|
||||
|
||||
let self_materialization_kind = self.materialization_kind(db);
|
||||
|
@ -853,14 +881,23 @@ impl<'db> Specialization<'db> {
|
|||
other_type,
|
||||
other_materialization_kind,
|
||||
relation,
|
||||
visitor,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
TypeVarVariance::Covariant => self_type.has_relation_to_impl(
|
||||
db,
|
||||
*other_type,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
TypeVarVariance::Contravariant => other_type.has_relation_to_impl(
|
||||
db,
|
||||
*self_type,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
TypeVarVariance::Covariant => {
|
||||
self_type.has_relation_to_impl(db, *other_type, relation, visitor)
|
||||
}
|
||||
TypeVarVariance::Contravariant => {
|
||||
other_type.has_relation_to_impl(db, *self_type, relation, visitor)
|
||||
}
|
||||
TypeVarVariance::Bivariant => ConstraintSet::from(true),
|
||||
};
|
||||
if result.intersect(db, compatible).is_never_satisfied() {
|
||||
|
|
|
@ -122,14 +122,16 @@ impl<'db> Type<'db> {
|
|||
db: &'db dyn Db,
|
||||
protocol: ProtocolInstanceType<'db>,
|
||||
relation: TypeRelation,
|
||||
visitor: &HasRelationToVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
let structurally_satisfied = if let Type::ProtocolInstance(self_protocol) = self {
|
||||
self_protocol.interface(db).extends_interface_of(
|
||||
self_protocol.interface(db).has_relation_to_impl(
|
||||
db,
|
||||
protocol.interface(db),
|
||||
relation,
|
||||
visitor,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
} else {
|
||||
protocol
|
||||
|
@ -137,7 +139,13 @@ impl<'db> Type<'db> {
|
|||
.interface(db)
|
||||
.members(db)
|
||||
.when_all(db, |member| {
|
||||
member.is_satisfied_by(db, self, relation, visitor)
|
||||
member.is_satisfied_by(
|
||||
db,
|
||||
self,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
};
|
||||
|
||||
|
@ -149,11 +157,22 @@ impl<'db> Type<'db> {
|
|||
// recognise `str` as a subtype of `Container[str]`.
|
||||
structurally_satisfied.or(db, || {
|
||||
if let Protocol::FromClass(class) = protocol.inner {
|
||||
self.has_relation_to_impl(
|
||||
let type_to_test = if let Type::ProtocolInstance(ProtocolInstanceType {
|
||||
inner: Protocol::FromClass(class),
|
||||
..
|
||||
}) = self
|
||||
{
|
||||
Type::non_tuple_instance(db, class)
|
||||
} else {
|
||||
self
|
||||
};
|
||||
|
||||
type_to_test.has_relation_to_impl(
|
||||
db,
|
||||
Type::non_tuple_instance(db, class),
|
||||
relation,
|
||||
visitor,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
} else {
|
||||
ConstraintSet::from(false)
|
||||
|
@ -342,17 +361,28 @@ impl<'db> NominalInstanceType<'db> {
|
|||
db: &'db dyn Db,
|
||||
other: Self,
|
||||
relation: TypeRelation,
|
||||
visitor: &HasRelationToVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
match (self.0, other.0) {
|
||||
(_, NominalInstanceInner::Object) => ConstraintSet::from(true),
|
||||
(
|
||||
NominalInstanceInner::ExactTuple(tuple1),
|
||||
NominalInstanceInner::ExactTuple(tuple2),
|
||||
) => tuple1.has_relation_to_impl(db, tuple2, relation, visitor),
|
||||
_ => self
|
||||
.class(db)
|
||||
.has_relation_to_impl(db, other.class(db), relation, visitor),
|
||||
) => tuple1.has_relation_to_impl(
|
||||
db,
|
||||
tuple2,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
_ => self.class(db).has_relation_to_impl(
|
||||
db,
|
||||
other.class(db),
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -381,7 +411,8 @@ impl<'db> NominalInstanceType<'db> {
|
|||
self,
|
||||
db: &'db dyn Db,
|
||||
other: Self,
|
||||
visitor: &IsDisjointVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
if self.is_object() || other.is_object() {
|
||||
return ConstraintSet::from(false);
|
||||
|
@ -389,7 +420,12 @@ impl<'db> NominalInstanceType<'db> {
|
|||
let mut result = ConstraintSet::from(false);
|
||||
if let Some(self_spec) = self.tuple_spec(db) {
|
||||
if let Some(other_spec) = other.tuple_spec(db) {
|
||||
let compatible = self_spec.is_disjoint_from_impl(db, &other_spec, visitor);
|
||||
let compatible = self_spec.is_disjoint_from_impl(
|
||||
db,
|
||||
&other_spec,
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
);
|
||||
if result.union(db, compatible).is_always_satisfied() {
|
||||
return result;
|
||||
}
|
||||
|
@ -601,6 +637,7 @@ impl<'db> ProtocolInstanceType<'db> {
|
|||
protocol,
|
||||
TypeRelation::Subtyping,
|
||||
&HasRelationToVisitor::default(),
|
||||
&IsDisjointVisitor::default(),
|
||||
)
|
||||
.is_always_satisfied()
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ use crate::{
|
|||
InstanceFallbackShadowsNonDataDescriptor, IsDisjointVisitor, KnownFunction,
|
||||
MemberLookupPolicy, NormalizedVisitor, PropertyInstanceType, Signature, Type, TypeMapping,
|
||||
TypeQualifiers, TypeRelation, TypeVarVariance, VarianceInferable,
|
||||
constraints::{ConstraintSet, IteratorConstraintsExtension},
|
||||
constraints::{ConstraintSet, IteratorConstraintsExtension, OptionConstraintsExtension},
|
||||
context::InferContext,
|
||||
diagnostic::report_undeclared_protocol_member,
|
||||
signatures::{Parameter, Parameters},
|
||||
|
@ -230,21 +230,98 @@ impl<'db> ProtocolInterface<'db> {
|
|||
.unwrap_or_else(|| Type::object().member(db, name))
|
||||
}
|
||||
|
||||
/// Return `true` if `self` extends the interface of `other`, i.e.,
|
||||
/// all members on `other` are also members of `self`.
|
||||
///
|
||||
/// TODO: this method should consider the types of the members as well as their names.
|
||||
pub(super) fn extends_interface_of(
|
||||
pub(super) fn has_relation_to_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
other: Self,
|
||||
_relation: TypeRelation,
|
||||
_visitor: &HasRelationToVisitor<'db>,
|
||||
relation: TypeRelation,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
// TODO: This could just return a bool as written, but this form is what will be needed to
|
||||
// combine the constraints when we do assignability checks on each member.
|
||||
other.inner(db).keys().when_all(db, |member_name| {
|
||||
ConstraintSet::from(self.inner(db).contains_key(member_name))
|
||||
other.members(db).when_all(db, |other_member| {
|
||||
self.member_by_name(db, other_member.name)
|
||||
.when_some_and(|our_member| match (our_member.kind, other_member.kind) {
|
||||
// Method members are always immutable;
|
||||
// they can never be subtypes of/assignable to mutable attribute members.
|
||||
(ProtocolMemberKind::Method(_), ProtocolMemberKind::Other(_)) => {
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
// A property member can only be a subtype of an attribute member
|
||||
// if the property is readable *and* writable.
|
||||
//
|
||||
// TODO: this should also consider the types of the members on both sides.
|
||||
(ProtocolMemberKind::Property(property), ProtocolMemberKind::Other(_)) => {
|
||||
ConstraintSet::from(
|
||||
property.getter(db).is_some() && property.setter(db).is_some(),
|
||||
)
|
||||
}
|
||||
|
||||
// A `@property` member can never be a subtype of a method member, as it is not necessarily
|
||||
// accessible on the meta-type, whereas a method member must be.
|
||||
(ProtocolMemberKind::Property(_), ProtocolMemberKind::Method(_)) => {
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
// But an attribute member *can* be a subtype of a method member,
|
||||
// providing it is marked `ClassVar`
|
||||
(
|
||||
ProtocolMemberKind::Other(our_type),
|
||||
ProtocolMemberKind::Method(other_type),
|
||||
) => ConstraintSet::from(
|
||||
our_member.qualifiers.contains(TypeQualifiers::CLASS_VAR),
|
||||
)
|
||||
.and(db, || {
|
||||
our_type.has_relation_to_impl(
|
||||
db,
|
||||
Type::Callable(other_type.bind_self(db)),
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
}),
|
||||
|
||||
(
|
||||
ProtocolMemberKind::Method(our_method),
|
||||
ProtocolMemberKind::Method(other_method),
|
||||
) => our_method.bind_self(db).has_relation_to_impl(
|
||||
db,
|
||||
other_method.bind_self(db),
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
|
||||
(
|
||||
ProtocolMemberKind::Other(our_type),
|
||||
ProtocolMemberKind::Other(other_type),
|
||||
) => our_type
|
||||
.has_relation_to_impl(
|
||||
db,
|
||||
other_type,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
.and(db, || {
|
||||
other_type.has_relation_to_impl(
|
||||
db,
|
||||
our_type,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
}),
|
||||
|
||||
// TODO: finish assignability/subtyping between two `@property` members,
|
||||
// and between a `@property` member and a member of a different kind.
|
||||
(
|
||||
ProtocolMemberKind::Property(_)
|
||||
| ProtocolMemberKind::Method(_)
|
||||
| ProtocolMemberKind::Other(_),
|
||||
ProtocolMemberKind::Property(_),
|
||||
) => ConstraintSet::from(true),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -518,14 +595,17 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
|||
&self,
|
||||
db: &'db dyn Db,
|
||||
other: Type<'db>,
|
||||
visitor: &IsDisjointVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
match &self.kind {
|
||||
// TODO: implement disjointness for property/method members as well as attribute members
|
||||
ProtocolMemberKind::Property(_) | ProtocolMemberKind::Method(_) => {
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
ProtocolMemberKind::Other(ty) => ty.is_disjoint_from_impl(db, other, visitor),
|
||||
ProtocolMemberKind::Other(ty) => {
|
||||
ty.is_disjoint_from_impl(db, other, disjointness_visitor, relation_visitor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -536,7 +616,8 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
|||
db: &'db dyn Db,
|
||||
other: Type<'db>,
|
||||
relation: TypeRelation,
|
||||
visitor: &HasRelationToVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
match &self.kind {
|
||||
ProtocolMemberKind::Method(method) => {
|
||||
|
@ -570,7 +651,13 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
|||
attribute_type
|
||||
};
|
||||
|
||||
attribute_type.has_relation_to_impl(db, method.bind_self(db), relation, visitor)
|
||||
attribute_type.has_relation_to_impl(
|
||||
db,
|
||||
Type::Callable(method.bind_self(db)),
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
}
|
||||
// TODO: consider the types of the attribute on `other` for property members
|
||||
ProtocolMemberKind::Property(_) => ConstraintSet::from(matches!(
|
||||
|
@ -584,9 +671,21 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
|||
return ConstraintSet::from(false);
|
||||
};
|
||||
member_type
|
||||
.has_relation_to_impl(db, attribute_type, relation, visitor)
|
||||
.has_relation_to_impl(
|
||||
db,
|
||||
attribute_type,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
.and(db, || {
|
||||
attribute_type.has_relation_to_impl(db, *member_type, relation, visitor)
|
||||
attribute_type.has_relation_to_impl(
|
||||
db,
|
||||
*member_type,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@ use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
|
|||
use crate::types::generics::{GenericContext, walk_generic_context};
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, FindLegacyTypeVarsVisitor,
|
||||
HasRelationToVisitor, IsEquivalentVisitor, KnownClass, MaterializationKind, NormalizedVisitor,
|
||||
TypeMapping, TypeRelation, VarianceInferable, todo_type,
|
||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, MaterializationKind,
|
||||
NormalizedVisitor, TypeMapping, TypeRelation, VarianceInferable, todo_type,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
use ruff_python_ast::{self as ast, name::Name};
|
||||
|
@ -136,6 +136,7 @@ impl<'db> CallableSignature<'db> {
|
|||
other,
|
||||
TypeRelation::Subtyping,
|
||||
&HasRelationToVisitor::default(),
|
||||
&IsDisjointVisitor::default(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -148,6 +149,7 @@ impl<'db> CallableSignature<'db> {
|
|||
other,
|
||||
TypeRelation::Assignability,
|
||||
&HasRelationToVisitor::default(),
|
||||
&IsDisjointVisitor::default(),
|
||||
)
|
||||
.is_always_satisfied()
|
||||
}
|
||||
|
@ -157,9 +159,17 @@ impl<'db> CallableSignature<'db> {
|
|||
db: &'db dyn Db,
|
||||
other: &Self,
|
||||
relation: TypeRelation,
|
||||
visitor: &HasRelationToVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
Self::has_relation_to_inner(db, &self.overloads, &other.overloads, relation, visitor)
|
||||
Self::has_relation_to_inner(
|
||||
db,
|
||||
&self.overloads,
|
||||
&other.overloads,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
}
|
||||
|
||||
/// Implementation of subtyping and assignability between two, possible overloaded, callable
|
||||
|
@ -169,12 +179,19 @@ impl<'db> CallableSignature<'db> {
|
|||
self_signatures: &[Signature<'db>],
|
||||
other_signatures: &[Signature<'db>],
|
||||
relation: TypeRelation,
|
||||
visitor: &HasRelationToVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
match (self_signatures, other_signatures) {
|
||||
([self_signature], [other_signature]) => {
|
||||
// Base case: both callable types contain a single signature.
|
||||
self_signature.has_relation_to_impl(db, other_signature, relation, visitor)
|
||||
self_signature.has_relation_to_impl(
|
||||
db,
|
||||
other_signature,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
}
|
||||
|
||||
// `self` is possibly overloaded while `other` is definitely not overloaded.
|
||||
|
@ -184,7 +201,8 @@ impl<'db> CallableSignature<'db> {
|
|||
std::slice::from_ref(self_signature),
|
||||
other_signatures,
|
||||
relation,
|
||||
visitor,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
}),
|
||||
|
||||
|
@ -195,7 +213,8 @@ impl<'db> CallableSignature<'db> {
|
|||
self_signatures,
|
||||
std::slice::from_ref(other_signature),
|
||||
relation,
|
||||
visitor,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
}),
|
||||
|
||||
|
@ -206,7 +225,8 @@ impl<'db> CallableSignature<'db> {
|
|||
self_signatures,
|
||||
std::slice::from_ref(other_signature),
|
||||
relation,
|
||||
visitor,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
}),
|
||||
}
|
||||
|
@ -631,7 +651,8 @@ impl<'db> Signature<'db> {
|
|||
db: &'db dyn Db,
|
||||
other: &Signature<'db>,
|
||||
relation: TypeRelation,
|
||||
visitor: &HasRelationToVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
/// A helper struct to zip two slices of parameters together that provides control over the
|
||||
/// two iterators individually. It also keeps track of the current parameter in each
|
||||
|
@ -699,7 +720,16 @@ impl<'db> Signature<'db> {
|
|||
let type1 = type1.unwrap_or(Type::unknown());
|
||||
let type2 = type2.unwrap_or(Type::unknown());
|
||||
!result
|
||||
.intersect(db, type1.has_relation_to_impl(db, type2, relation, visitor))
|
||||
.intersect(
|
||||
db,
|
||||
type1.has_relation_to_impl(
|
||||
db,
|
||||
type2,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
)
|
||||
.is_never_satisfied()
|
||||
};
|
||||
|
||||
|
|
|
@ -134,7 +134,8 @@ impl<'db> SubclassOfType<'db> {
|
|||
db: &'db dyn Db,
|
||||
other: SubclassOfType<'db>,
|
||||
relation: TypeRelation,
|
||||
visitor: &HasRelationToVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
match (self.subclass_of, other.subclass_of) {
|
||||
(SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => {
|
||||
|
@ -150,9 +151,14 @@ impl<'db> SubclassOfType<'db> {
|
|||
// 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`.
|
||||
// The first set is a subset of the second set, because `bool` is itself a subclass of `int`.
|
||||
(SubclassOfInner::Class(self_class), SubclassOfInner::Class(other_class)) => {
|
||||
self_class.has_relation_to_impl(db, other_class, relation, visitor)
|
||||
}
|
||||
(SubclassOfInner::Class(self_class), SubclassOfInner::Class(other_class)) => self_class
|
||||
.has_relation_to_impl(
|
||||
db,
|
||||
other_class,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -258,10 +258,16 @@ impl<'db> TupleType<'db> {
|
|||
db: &'db dyn Db,
|
||||
other: Self,
|
||||
relation: TypeRelation,
|
||||
visitor: &HasRelationToVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
self.tuple(db)
|
||||
.has_relation_to_impl(db, other.tuple(db), relation, visitor)
|
||||
self.tuple(db).has_relation_to_impl(
|
||||
db,
|
||||
other.tuple(db),
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn is_equivalent_to_impl(
|
||||
|
@ -416,13 +422,20 @@ impl<'db> FixedLengthTuple<Type<'db>> {
|
|||
db: &'db dyn Db,
|
||||
other: &Tuple<Type<'db>>,
|
||||
relation: TypeRelation,
|
||||
visitor: &HasRelationToVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
match other {
|
||||
Tuple::Fixed(other) => {
|
||||
ConstraintSet::from(self.0.len() == other.0.len()).and(db, || {
|
||||
(self.0.iter().zip(&other.0)).when_all(db, |(self_ty, other_ty)| {
|
||||
self_ty.has_relation_to_impl(db, *other_ty, relation, visitor)
|
||||
self_ty.has_relation_to_impl(
|
||||
db,
|
||||
*other_ty,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -436,8 +449,13 @@ impl<'db> FixedLengthTuple<Type<'db>> {
|
|||
let Some(self_ty) = self_iter.next() else {
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
let element_constraints =
|
||||
self_ty.has_relation_to_impl(db, *other_ty, relation, visitor);
|
||||
let element_constraints = self_ty.has_relation_to_impl(
|
||||
db,
|
||||
*other_ty,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
);
|
||||
if result
|
||||
.intersect(db, element_constraints)
|
||||
.is_never_satisfied()
|
||||
|
@ -449,8 +467,13 @@ impl<'db> FixedLengthTuple<Type<'db>> {
|
|||
let Some(self_ty) = self_iter.next_back() else {
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
let element_constraints =
|
||||
self_ty.has_relation_to_impl(db, *other_ty, relation, visitor);
|
||||
let element_constraints = self_ty.has_relation_to_impl(
|
||||
db,
|
||||
*other_ty,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
);
|
||||
if result
|
||||
.intersect(db, element_constraints)
|
||||
.is_never_satisfied()
|
||||
|
@ -463,7 +486,13 @@ impl<'db> FixedLengthTuple<Type<'db>> {
|
|||
// variable-length portion of the other tuple.
|
||||
result.and(db, || {
|
||||
self_iter.when_all(db, |self_ty| {
|
||||
self_ty.has_relation_to_impl(db, other.variable, relation, visitor)
|
||||
self_ty.has_relation_to_impl(
|
||||
db,
|
||||
other.variable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -743,7 +772,8 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
|||
db: &'db dyn Db,
|
||||
other: &Tuple<Type<'db>>,
|
||||
relation: TypeRelation,
|
||||
visitor: &HasRelationToVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
match other {
|
||||
Tuple::Fixed(other) => {
|
||||
|
@ -771,8 +801,13 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
|||
let Some(other_ty) = other_iter.next() else {
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
let element_constraints =
|
||||
self_ty.has_relation_to_impl(db, other_ty, relation, visitor);
|
||||
let element_constraints = self_ty.has_relation_to_impl(
|
||||
db,
|
||||
other_ty,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
);
|
||||
if result
|
||||
.intersect(db, element_constraints)
|
||||
.is_never_satisfied()
|
||||
|
@ -785,8 +820,13 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
|||
let Some(other_ty) = other_iter.next_back() else {
|
||||
return ConstraintSet::from(false);
|
||||
};
|
||||
let element_constraints =
|
||||
self_ty.has_relation_to_impl(db, other_ty, relation, visitor);
|
||||
let element_constraints = self_ty.has_relation_to_impl(
|
||||
db,
|
||||
other_ty,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
);
|
||||
if result
|
||||
.intersect(db, element_constraints)
|
||||
.is_never_satisfied()
|
||||
|
@ -820,12 +860,20 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
|||
);
|
||||
for pair in pairwise {
|
||||
let pair_constraints = match pair {
|
||||
EitherOrBoth::Both(self_ty, other_ty) => {
|
||||
self_ty.has_relation_to_impl(db, other_ty, relation, visitor)
|
||||
}
|
||||
EitherOrBoth::Left(self_ty) => {
|
||||
self_ty.has_relation_to_impl(db, other.variable, relation, visitor)
|
||||
}
|
||||
EitherOrBoth::Both(self_ty, other_ty) => self_ty.has_relation_to_impl(
|
||||
db,
|
||||
other_ty,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
EitherOrBoth::Left(self_ty) => self_ty.has_relation_to_impl(
|
||||
db,
|
||||
other.variable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
EitherOrBoth::Right(_) => {
|
||||
// The rhs has a required element that the lhs is not guaranteed to
|
||||
// provide.
|
||||
|
@ -846,12 +894,20 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
|||
let pairwise = (self_suffix.iter().rev()).zip_longest(other_suffix.iter().rev());
|
||||
for pair in pairwise {
|
||||
let pair_constraints = match pair {
|
||||
EitherOrBoth::Both(self_ty, other_ty) => {
|
||||
self_ty.has_relation_to_impl(db, *other_ty, relation, visitor)
|
||||
}
|
||||
EitherOrBoth::Left(self_ty) => {
|
||||
self_ty.has_relation_to_impl(db, other.variable, relation, visitor)
|
||||
}
|
||||
EitherOrBoth::Both(self_ty, other_ty) => self_ty.has_relation_to_impl(
|
||||
db,
|
||||
*other_ty,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
EitherOrBoth::Left(self_ty) => self_ty.has_relation_to_impl(
|
||||
db,
|
||||
other.variable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
EitherOrBoth::Right(_) => {
|
||||
// The rhs has a required element that the lhs is not guaranteed to
|
||||
// provide.
|
||||
|
@ -865,8 +921,13 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
|||
|
||||
// And lastly, the variable-length portions must satisfy the relation.
|
||||
result.and(db, || {
|
||||
self.variable
|
||||
.has_relation_to_impl(db, other.variable, relation, visitor)
|
||||
self.variable.has_relation_to_impl(
|
||||
db,
|
||||
other.variable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1088,15 +1149,24 @@ impl<'db> Tuple<Type<'db>> {
|
|||
db: &'db dyn Db,
|
||||
other: &Self,
|
||||
relation: TypeRelation,
|
||||
visitor: &HasRelationToVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
match self {
|
||||
Tuple::Fixed(self_tuple) => {
|
||||
self_tuple.has_relation_to_impl(db, other, relation, visitor)
|
||||
}
|
||||
Tuple::Variable(self_tuple) => {
|
||||
self_tuple.has_relation_to_impl(db, other, relation, visitor)
|
||||
}
|
||||
Tuple::Fixed(self_tuple) => self_tuple.has_relation_to_impl(
|
||||
db,
|
||||
other,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
Tuple::Variable(self_tuple) => self_tuple.has_relation_to_impl(
|
||||
db,
|
||||
other,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1123,7 +1193,8 @@ impl<'db> Tuple<Type<'db>> {
|
|||
&self,
|
||||
db: &'db dyn Db,
|
||||
other: &Self,
|
||||
visitor: &IsDisjointVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
// Two tuples with an incompatible number of required elements must always be disjoint.
|
||||
let (self_min, self_max) = self.len().size_hint();
|
||||
|
@ -1141,20 +1212,30 @@ impl<'db> Tuple<Type<'db>> {
|
|||
db: &'db dyn Db,
|
||||
a: impl IntoIterator<Item = &'s Type<'db>>,
|
||||
b: impl IntoIterator<Item = &'s Type<'db>>,
|
||||
visitor: &IsDisjointVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
) -> ConstraintSet<'db>
|
||||
where
|
||||
'db: 's,
|
||||
{
|
||||
(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,
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
match (self, other) {
|
||||
(Tuple::Fixed(self_tuple), Tuple::Fixed(other_tuple)) => {
|
||||
any_disjoint(db, self_tuple.elements(), other_tuple.elements(), visitor)
|
||||
}
|
||||
(Tuple::Fixed(self_tuple), Tuple::Fixed(other_tuple)) => any_disjoint(
|
||||
db,
|
||||
self_tuple.elements(),
|
||||
other_tuple.elements(),
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
),
|
||||
|
||||
// Note that we don't compare the variable-length portions; two pure homogeneous tuples
|
||||
// `tuple[A, ...]` and `tuple[B, ...]` can never be disjoint even if A and B are
|
||||
|
@ -1163,31 +1244,36 @@ impl<'db> Tuple<Type<'db>> {
|
|||
db,
|
||||
self_tuple.prefix_elements(),
|
||||
other_tuple.prefix_elements(),
|
||||
visitor,
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
)
|
||||
.or(db, || {
|
||||
any_disjoint(
|
||||
db,
|
||||
self_tuple.suffix_elements().rev(),
|
||||
other_tuple.suffix_elements().rev(),
|
||||
visitor,
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
)
|
||||
}),
|
||||
|
||||
(Tuple::Fixed(fixed), Tuple::Variable(variable))
|
||||
| (Tuple::Variable(variable), Tuple::Fixed(fixed)) => {
|
||||
any_disjoint(db, fixed.elements(), variable.prefix_elements(), visitor).or(
|
||||
| (Tuple::Variable(variable), Tuple::Fixed(fixed)) => any_disjoint(
|
||||
db,
|
||||
fixed.elements(),
|
||||
variable.prefix_elements(),
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
)
|
||||
.or(db, || {
|
||||
any_disjoint(
|
||||
db,
|
||||
|| {
|
||||
any_disjoint(
|
||||
db,
|
||||
fixed.elements().rev(),
|
||||
variable.suffix_elements().rev(),
|
||||
visitor,
|
||||
)
|
||||
},
|
||||
fixed.elements().rev(),
|
||||
variable.suffix_elements().rev(),
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
)
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue