diff --git a/crates/ruff_benchmark/benches/ty_walltime.rs b/crates/ruff_benchmark/benches/ty_walltime.rs index ef7cd9333b..783827f575 100644 --- a/crates/ruff_benchmark/benches/ty_walltime.rs +++ b/crates/ruff_benchmark/benches/ty_walltime.rs @@ -232,7 +232,7 @@ static STATIC_FRAME: std::sync::LazyLock> = std::sync::LazyLo max_dep_date: "2025-08-09", python_version: PythonVersion::PY311, }, - 600, + 620, ) }); diff --git a/crates/ty_python_semantic/resources/mdtest/call/overloads.md b/crates/ty_python_semantic/resources/mdtest/call/overloads.md index fcb23dc077..5d7d11b5f9 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/overloads.md +++ b/crates/ty_python_semantic/resources/mdtest/call/overloads.md @@ -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, diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/same_names.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/same_names.md index 6e6dfac60d..6439e67b9a 100644 --- a/crates/ty_python_semantic/resources/mdtest/diagnostics/same_names.md +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/same_names.md @@ -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 ``` diff --git a/crates/ty_python_semantic/resources/mdtest/loops/for.md b/crates/ty_python_semantic/resources/mdtest/loops/for.md index f935c11a6c..f6300f5975 100644 --- a/crates/ty_python_semantic/resources/mdtest/loops/for.md +++ b/crates/ty_python_semantic/resources/mdtest/loops/for.md @@ -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 diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index 91cef8e818..006419bd72 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -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): diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index d52b909115..06d0f962b8 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1476,7 +1476,13 @@ impl<'db> Type<'db> { target: Type<'db>, relation: TypeRelation, ) -> ConstraintSet<'db> { - self.has_relation_to_impl(db, target, relation, &HasRelationToVisitor::default()) + self.has_relation_to_impl( + db, + target, + relation, + &HasRelationToVisitor::default(), + &IsDisjointVisitor::default(), + ) } fn has_relation_to_impl( @@ -1484,7 +1490,8 @@ impl<'db> Type<'db> { db: &'db dyn Db, target: Type<'db>, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db>, + relation_visitor: &HasRelationToVisitor<'db>, + disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { // Subtyping implies assignability, so if subtyping is reflexive and the two types are // equal, it is both a subtype and assignable. Assignability is always reflexive. @@ -1514,15 +1521,29 @@ impl<'db> Type<'db> { ConstraintSet::from(relation.is_assignability()) } - (Type::TypeAlias(self_alias), _) => visitor.visit((self, target, relation), || { - self_alias - .value_type(db) - .has_relation_to_impl(db, target, relation, visitor) - }), + (Type::TypeAlias(self_alias), _) => { + relation_visitor.visit((self, target, relation), || { + self_alias.value_type(db).has_relation_to_impl( + db, + target, + relation, + relation_visitor, + disjointness_visitor, + ) + }) + } - (_, Type::TypeAlias(target_alias)) => visitor.visit((self, target, relation), || { - self.has_relation_to_impl(db, target_alias.value_type(db), relation, visitor) - }), + (_, Type::TypeAlias(target_alias)) => { + relation_visitor.visit((self, target, relation), || { + self.has_relation_to_impl( + db, + target_alias.value_type(db), + relation, + relation_visitor, + disjointness_visitor, + ) + }) + } // Pretend that instances of `dataclasses.Field` are assignable to their default type. // This allows field definitions like `name: str = field(default="")` in dataclasses @@ -1530,9 +1551,13 @@ impl<'db> Type<'db> { (Type::KnownInstance(KnownInstanceType::Field(field)), right) if relation.is_assignability() => { - field - .default_type(db) - .has_relation_to_impl(db, right, relation, visitor) + field.default_type(db).has_relation_to_impl( + db, + right, + relation, + relation_visitor, + disjointness_visitor, + ) } (Type::TypedDict(_), _) | (_, Type::TypedDict(_)) => { @@ -1582,12 +1607,23 @@ impl<'db> Type<'db> { { match bound_typevar.typevar(db).bound_or_constraints(db) { None => unreachable!(), - Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { - bound.has_relation_to_impl(db, target, relation, visitor) - } + Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound + .has_relation_to_impl( + db, + target, + relation, + relation_visitor, + disjointness_visitor, + ), Some(TypeVarBoundOrConstraints::Constraints(constraints)) => { constraints.elements(db).iter().when_all(db, |constraint| { - constraint.has_relation_to_impl(db, target, relation, visitor) + constraint.has_relation_to_impl( + db, + target, + relation, + relation_visitor, + disjointness_visitor, + ) }) } } @@ -1602,7 +1638,13 @@ impl<'db> Type<'db> { .constraints(db) .when_some_and(|constraints| { constraints.iter().when_all(db, |constraint| { - self.has_relation_to_impl(db, *constraint, relation, visitor) + self.has_relation_to_impl( + db, + *constraint, + relation, + relation_visitor, + disjointness_visitor, + ) }) }) .is_never_satisfied() => @@ -1616,7 +1658,13 @@ impl<'db> Type<'db> { .constraints(db) .when_some_and(|constraints| { constraints.iter().when_all(db, |constraint| { - self.has_relation_to_impl(db, *constraint, relation, visitor) + self.has_relation_to_impl( + db, + *constraint, + relation, + relation_visitor, + disjointness_visitor, + ) }) }) } @@ -1634,29 +1682,60 @@ impl<'db> Type<'db> { (_, Type::Never) => ConstraintSet::from(false), (Type::Union(union), _) => union.elements(db).iter().when_all(db, |&elem_ty| { - elem_ty.has_relation_to_impl(db, target, relation, visitor) + elem_ty.has_relation_to_impl( + db, + target, + relation, + relation_visitor, + disjointness_visitor, + ) }), (_, Type::Union(union)) => union.elements(db).iter().when_any(db, |&elem_ty| { - self.has_relation_to_impl(db, elem_ty, relation, visitor) + self.has_relation_to_impl( + db, + elem_ty, + relation, + relation_visitor, + disjointness_visitor, + ) }), // If both sides are intersections we need to handle the right side first // (A & B & C) is a subtype of (A & B) because the left is a subtype of both A and B, // but none of A, B, or C is a subtype of (A & B). - (_, Type::Intersection(intersection)) => (intersection.positive(db).iter()) + (_, Type::Intersection(intersection)) => intersection + .positive(db) + .iter() .when_all(db, |&pos_ty| { - self.has_relation_to_impl(db, pos_ty, relation, visitor) + self.has_relation_to_impl( + db, + pos_ty, + relation, + relation_visitor, + disjointness_visitor, + ) }) .and(db, || { - intersection - .negative(db) - .iter() - .when_all(db, |&neg_ty| self.when_disjoint_from(db, neg_ty)) + intersection.negative(db).iter().when_all(db, |&neg_ty| { + self.is_disjoint_from_impl( + db, + neg_ty, + disjointness_visitor, + relation_visitor, + ) + }) }), + (Type::Intersection(intersection), _) => { intersection.positive(db).iter().when_any(db, |&elem_ty| { - elem_ty.has_relation_to_impl(db, target, relation, visitor) + elem_ty.has_relation_to_impl( + db, + target, + relation, + relation_visitor, + disjointness_visitor, + ) }) } @@ -1671,16 +1750,27 @@ impl<'db> Type<'db> { if relation.is_assignability() && typevar.typevar(db).upper_bound(db).is_none_or(|bound| { !self - .has_relation_to_impl(db, bound, relation, visitor) + .has_relation_to_impl( + db, + bound, + relation, + relation_visitor, + disjointness_visitor, + ) .is_never_satisfied() }) => { // TODO: record the unification constraints - typevar - .typevar(db) - .upper_bound(db) - .when_none_or(|bound| self.has_relation_to_impl(db, bound, relation, visitor)) + typevar.typevar(db).upper_bound(db).when_none_or(|bound| { + self.has_relation_to_impl( + db, + bound, + relation, + relation_visitor, + disjointness_visitor, + ) + }) } // TODO: Infer specializations here @@ -1701,13 +1791,18 @@ impl<'db> Type<'db> { // applied to the signature. Different specializations of the same function literal are // only subtypes of each other if they result in the same signature. (Type::FunctionLiteral(self_function), Type::FunctionLiteral(target_function)) => { - self_function.has_relation_to_impl(db, target_function, relation, visitor) - } - (Type::BoundMethod(self_method), Type::BoundMethod(target_method)) => { - self_method.has_relation_to_impl(db, target_method, relation, visitor) + self_function.has_relation_to_impl(db, target_function, relation, relation_visitor) } + (Type::BoundMethod(self_method), Type::BoundMethod(target_method)) => self_method + .has_relation_to_impl( + db, + target_method, + relation, + relation_visitor, + disjointness_visitor, + ), (Type::KnownBoundMethod(self_method), Type::KnownBoundMethod(target_method)) => { - self_method.has_relation_to_impl(db, target_method, relation, visitor) + self_method.has_relation_to_impl(db, target_method, relation, relation_visitor) } // No literal type is a subtype of any other literal type, unless they are the same @@ -1731,19 +1826,39 @@ impl<'db> Type<'db> { | Type::EnumLiteral(_), ) => ConstraintSet::from(false), - (Type::Callable(self_callable), Type::Callable(other_callable)) => visitor + (Type::Callable(self_callable), Type::Callable(other_callable)) => relation_visitor .visit((self, target, relation), || { - self_callable.has_relation_to_impl(db, other_callable, relation, visitor) + self_callable.has_relation_to_impl( + db, + other_callable, + relation, + relation_visitor, + disjointness_visitor, + ) }), - (_, Type::Callable(_)) => visitor.visit((self, target, relation), || { + (_, Type::Callable(_)) => relation_visitor.visit((self, target, relation), || { self.into_callable(db).when_some_and(|callable| { - callable.has_relation_to_impl(db, target, relation, visitor) + callable.has_relation_to_impl( + db, + target, + relation, + relation_visitor, + disjointness_visitor, + ) }) }), (_, Type::ProtocolInstance(protocol)) => { - self.satisfies_protocol(db, protocol, relation, visitor) + relation_visitor.visit((self, target, relation), || { + self.satisfies_protocol( + db, + protocol, + relation, + relation_visitor, + disjointness_visitor, + ) + }) } // A protocol instance can never be a subtype of a nominal type, with the *sole* exception of `object`. @@ -1779,20 +1894,26 @@ impl<'db> Type<'db> { | Type::FunctionLiteral(_), _, ) => (self.literal_fallback_instance(db)).when_some_and(|instance| { - instance.has_relation_to_impl(db, target, relation, visitor) + instance.has_relation_to_impl( + db, + target, + relation, + relation_visitor, + disjointness_visitor, + ) }), // The same reasoning applies for these special callable types: (Type::BoundMethod(_), _) => KnownClass::MethodType .to_instance(db) - .has_relation_to_impl(db, target, relation, visitor), + .has_relation_to_impl(db, target, relation, relation_visitor, disjointness_visitor), (Type::KnownBoundMethod(method), _) => method .class() .to_instance(db) - .has_relation_to_impl(db, target, relation, visitor), + .has_relation_to_impl(db, target, relation, relation_visitor, disjointness_visitor), (Type::WrapperDescriptor(_), _) => KnownClass::WrapperDescriptorType .to_instance(db) - .has_relation_to_impl(db, target, relation, visitor), + .has_relation_to_impl(db, target, relation, relation_visitor, disjointness_visitor), (Type::DataclassDecorator(_) | Type::DataclassTransformer(_), _) => { // TODO: Implement subtyping using an equivalent `Callable` type. @@ -1802,34 +1923,55 @@ impl<'db> Type<'db> { // `TypeIs` is invariant. (Type::TypeIs(left), Type::TypeIs(right)) => left .return_type(db) - .has_relation_to_impl(db, right.return_type(db), relation, visitor) + .has_relation_to_impl( + db, + right.return_type(db), + relation, + relation_visitor, + disjointness_visitor, + ) .and(db, || { right.return_type(db).has_relation_to_impl( db, left.return_type(db), relation, - visitor, + relation_visitor, + disjointness_visitor, ) }), // `TypeIs[T]` is a subtype of `bool`. - (Type::TypeIs(_), _) => KnownClass::Bool - .to_instance(db) - .has_relation_to_impl(db, target, relation, visitor), + (Type::TypeIs(_), _) => KnownClass::Bool.to_instance(db).has_relation_to_impl( + db, + target, + relation, + relation_visitor, + disjointness_visitor, + ), // Function-like callables are subtypes of `FunctionType` (Type::Callable(callable), _) if callable.is_function_like(db) => { KnownClass::FunctionType .to_instance(db) - .has_relation_to_impl(db, target, relation, visitor) + .has_relation_to_impl( + db, + target, + relation, + relation_visitor, + disjointness_visitor, + ) } (Type::Callable(_), _) => ConstraintSet::from(false), (Type::BoundSuper(_), Type::BoundSuper(_)) => self.when_equivalent_to(db, target), - (Type::BoundSuper(_), _) => KnownClass::Super - .to_instance(db) - .has_relation_to_impl(db, target, relation, visitor), + (Type::BoundSuper(_), _) => KnownClass::Super.to_instance(db).has_relation_to_impl( + db, + target, + relation, + relation_visitor, + disjointness_visitor, + ), // `Literal[]` is a subtype of `type[B]` if `C` is a subclass of `B`, // since `type[B]` describes all possible runtime subclasses of the class object `B`. @@ -1841,7 +1983,8 @@ impl<'db> Type<'db> { db, subclass_of_class, relation, - visitor, + relation_visitor, + disjointness_visitor, ) }) .unwrap_or_else(|| ConstraintSet::from(relation.is_assignability())), @@ -1853,14 +1996,21 @@ impl<'db> Type<'db> { db, subclass_of_class, relation, - visitor, + relation_visitor, + disjointness_visitor, ) }) .unwrap_or_else(|| ConstraintSet::from(relation.is_assignability())), // This branch asks: given two types `type[T]` and `type[S]`, is `type[T]` a subtype of `type[S]`? (Type::SubclassOf(self_subclass_ty), Type::SubclassOf(target_subclass_ty)) => { - self_subclass_ty.has_relation_to_impl(db, target_subclass_ty, relation, visitor) + self_subclass_ty.has_relation_to_impl( + db, + target_subclass_ty, + relation, + relation_visitor, + disjointness_visitor, + ) } // `Literal[str]` is a subtype of `type` because the `str` class object is an instance of its metaclass `type`. @@ -1868,23 +2018,30 @@ impl<'db> Type<'db> { // is an instance of its metaclass `abc.ABCMeta`. (Type::ClassLiteral(class), _) => class .metaclass_instance_type(db) - .has_relation_to_impl(db, target, relation, visitor), + .has_relation_to_impl(db, target, relation, relation_visitor, disjointness_visitor), (Type::GenericAlias(alias), _) => ClassType::from(alias) .metaclass_instance_type(db) - .has_relation_to_impl(db, target, relation, visitor), + .has_relation_to_impl(db, target, relation, relation_visitor, disjointness_visitor), // `type[Any]` is a subtype of `type[object]`, and is assignable to any `type[...]` (Type::SubclassOf(subclass_of_ty), other) if subclass_of_ty.is_dynamic() => { KnownClass::Type .to_instance(db) - .has_relation_to_impl(db, other, relation, visitor) + .has_relation_to_impl( + db, + other, + relation, + relation_visitor, + disjointness_visitor, + ) .or(db, || { ConstraintSet::from(relation.is_assignability()).and(db, || { other.has_relation_to_impl( db, KnownClass::Type.to_instance(db), relation, - visitor, + relation_visitor, + disjointness_visitor, ) }) }) @@ -1894,7 +2051,13 @@ impl<'db> Type<'db> { (other, Type::SubclassOf(subclass_of_ty)) if subclass_of_ty.is_dynamic() && relation.is_assignability() => { - other.has_relation_to_impl(db, KnownClass::Type.to_instance(db), relation, visitor) + other.has_relation_to_impl( + db, + KnownClass::Type.to_instance(db), + relation, + relation_visitor, + disjointness_visitor, + ) } // `type[str]` (== `SubclassOf("str")` in ty) describes all possible runtime subclasses @@ -1909,35 +2072,50 @@ impl<'db> Type<'db> { .into_class() .map(|class| class.metaclass_instance_type(db)) .unwrap_or_else(|| KnownClass::Type.to_instance(db)) - .has_relation_to_impl(db, target, relation, visitor), + .has_relation_to_impl(db, target, relation, relation_visitor, disjointness_visitor), // For example: `Type::SpecialForm(SpecialFormType::Type)` is a subtype of `Type::NominalInstance(_SpecialForm)`, // because `Type::SpecialForm(SpecialFormType::Type)` is a set with exactly one runtime value in it // (the symbol `typing.Type`), and that symbol is known to be an instance of `typing._SpecialForm` at runtime. - (Type::SpecialForm(left), right) => left - .instance_fallback(db) - .has_relation_to_impl(db, right, relation, visitor), + (Type::SpecialForm(left), right) => left.instance_fallback(db).has_relation_to_impl( + db, + right, + relation, + relation_visitor, + disjointness_visitor, + ), - (Type::KnownInstance(left), right) => left - .instance_fallback(db) - .has_relation_to_impl(db, right, relation, visitor), + (Type::KnownInstance(left), right) => left.instance_fallback(db).has_relation_to_impl( + db, + right, + relation, + relation_visitor, + disjointness_visitor, + ), // `bool` is a subtype of `int`, because `bool` subclasses `int`, // which means that all instances of `bool` are also instances of `int` (Type::NominalInstance(self_instance), Type::NominalInstance(target_instance)) => { - visitor.visit((self, target, relation), || { - self_instance.has_relation_to_impl(db, target_instance, relation, visitor) + relation_visitor.visit((self, target, relation), || { + self_instance.has_relation_to_impl( + db, + target_instance, + relation, + relation_visitor, + disjointness_visitor, + ) }) } (Type::PropertyInstance(_), _) => KnownClass::Property .to_instance(db) - .has_relation_to_impl(db, target, relation, visitor), + .has_relation_to_impl(db, target, relation, relation_visitor, disjointness_visitor), (_, Type::PropertyInstance(_)) => self.has_relation_to_impl( db, KnownClass::Property.to_instance(db), relation, - visitor, + relation_visitor, + disjointness_visitor, ), // Other than the special cases enumerated above, `Instance` types and typevars are @@ -2070,20 +2248,27 @@ impl<'db> Type<'db> { } fn when_disjoint_from(self, db: &'db dyn Db, other: Type<'db>) -> ConstraintSet<'db> { - self.is_disjoint_from_impl(db, other, &IsDisjointVisitor::default()) + self.is_disjoint_from_impl( + db, + other, + &IsDisjointVisitor::default(), + &HasRelationToVisitor::default(), + ) } pub(crate) fn is_disjoint_from_impl( self, db: &'db dyn Db, other: Type<'db>, - visitor: &IsDisjointVisitor<'db>, + disjointness_visitor: &IsDisjointVisitor<'db>, + relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { fn any_protocol_members_absent_or_disjoint<'db>( db: &'db dyn Db, protocol: ProtocolInstanceType<'db>, other: Type<'db>, - visitor: &IsDisjointVisitor<'db>, + disjointness_visitor: &IsDisjointVisitor<'db>, + relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { protocol.interface(db).members(db).when_any(db, |member| { other @@ -2091,7 +2276,12 @@ impl<'db> Type<'db> { .place .ignore_possibly_unbound() .when_none_or(|attribute_type| { - member.has_disjoint_type_from(db, attribute_type, visitor) + member.has_disjoint_type_from( + db, + attribute_type, + disjointness_visitor, + relation_visitor, + ) }) }) } @@ -2103,15 +2293,25 @@ impl<'db> Type<'db> { (Type::TypeAlias(alias), _) => { let self_alias_ty = alias.value_type(db); - visitor.visit((self_alias_ty, other), || { - self_alias_ty.is_disjoint_from_impl(db, other, visitor) + disjointness_visitor.visit((self_alias_ty, other), || { + self_alias_ty.is_disjoint_from_impl( + db, + other, + disjointness_visitor, + relation_visitor, + ) }) } (_, Type::TypeAlias(alias)) => { let other_alias_ty = alias.value_type(db); - visitor.visit((self, other_alias_ty), || { - self.is_disjoint_from_impl(db, other_alias_ty, visitor) + disjointness_visitor.visit((self, other_alias_ty), || { + self.is_disjoint_from_impl( + db, + other_alias_ty, + disjointness_visitor, + relation_visitor, + ) }) } @@ -2144,12 +2344,16 @@ impl<'db> Type<'db> { | (other, Type::NonInferableTypeVar(bound_typevar)) => { match bound_typevar.typevar(db).bound_or_constraints(db) { None => ConstraintSet::from(false), - Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { - bound.is_disjoint_from_impl(db, other, visitor) - } + Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound + .is_disjoint_from_impl(db, other, disjointness_visitor, relation_visitor), Some(TypeVarBoundOrConstraints::Constraints(constraints)) => { constraints.elements(db).iter().when_all(db, |constraint| { - constraint.is_disjoint_from_impl(db, other, visitor) + constraint.is_disjoint_from_impl( + db, + other, + disjointness_visitor, + relation_visitor, + ) }) } } @@ -2158,40 +2362,68 @@ impl<'db> Type<'db> { // TODO: Infer specializations here (Type::TypeVar(_), _) | (_, Type::TypeVar(_)) => ConstraintSet::from(false), - (Type::Union(union), other) | (other, Type::Union(union)) => union - .elements(db) - .iter() - .when_all(db, |e| e.is_disjoint_from_impl(db, other, visitor)), + (Type::Union(union), other) | (other, Type::Union(union)) => { + union.elements(db).iter().when_all(db, |e| { + e.is_disjoint_from_impl(db, other, disjointness_visitor, relation_visitor) + }) + } // If we have two intersections, we test the positive elements of each one against the other intersection // Negative elements need a positive element on the other side in order to be disjoint. // This is similar to what would happen if we tried to build a new intersection that combines the two (Type::Intersection(self_intersection), Type::Intersection(other_intersection)) => { - self_intersection - .positive(db) - .iter() - .when_any(db, |p| p.is_disjoint_from_impl(db, other, visitor)) - .or(db, || { - other_intersection - .positive(db) - .iter() - .when_any(db, |p| p.is_disjoint_from_impl(db, self, visitor)) - }) + disjointness_visitor.visit((self, other), || { + self_intersection + .positive(db) + .iter() + .when_any(db, |p| { + p.is_disjoint_from_impl( + db, + other, + disjointness_visitor, + relation_visitor, + ) + }) + .or(db, || { + other_intersection.positive(db).iter().when_any(db, |p| { + p.is_disjoint_from_impl( + db, + self, + disjointness_visitor, + relation_visitor, + ) + }) + }) + }) } (Type::Intersection(intersection), other) | (other, Type::Intersection(intersection)) => { - intersection - .positive(db) - .iter() - .when_any(db, |p| p.is_disjoint_from_impl(db, other, visitor)) - // A & B & Not[C] is disjoint from C - .or(db, || { - intersection - .negative(db) - .iter() - .when_any(db, |&neg_ty| other.when_subtype_of(db, neg_ty)) - }) + disjointness_visitor.visit((self, other), || { + intersection + .positive(db) + .iter() + .when_any(db, |p| { + p.is_disjoint_from_impl( + db, + other, + disjointness_visitor, + relation_visitor, + ) + }) + // A & B & Not[C] is disjoint from C + .or(db, || { + intersection.negative(db).iter().when_any(db, |&neg_ty| { + other.has_relation_to_impl( + db, + neg_ty, + TypeRelation::Subtyping, + relation_visitor, + disjointness_visitor, + ) + }) + }) + }) } // any single-valued type is disjoint from another single-valued type @@ -2266,32 +2498,36 @@ impl<'db> Type<'db> { ConstraintSet::from(ty.bool(db).is_always_true()) } - (Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => { - left.is_disjoint_from_impl(db, right, visitor) - } + (Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => disjointness_visitor + .visit((self, other), || { + left.is_disjoint_from_impl(db, right, disjointness_visitor) + }), (Type::ProtocolInstance(protocol), Type::SpecialForm(special_form)) | (Type::SpecialForm(special_form), Type::ProtocolInstance(protocol)) => { - visitor.visit((self, other), || { + disjointness_visitor.visit((self, other), || { any_protocol_members_absent_or_disjoint( db, protocol, special_form.instance_fallback(db), - visitor, + disjointness_visitor, + relation_visitor, ) }) } (Type::ProtocolInstance(protocol), Type::KnownInstance(known_instance)) - | (Type::KnownInstance(known_instance), Type::ProtocolInstance(protocol)) => visitor - .visit((self, other), || { + | (Type::KnownInstance(known_instance), Type::ProtocolInstance(protocol)) => { + disjointness_visitor.visit((self, other), || { any_protocol_members_absent_or_disjoint( db, protocol, known_instance.instance_fallback(db), - visitor, + disjointness_visitor, + relation_visitor, ) - }), + }) + } // The absence of a protocol member on one of these types guarantees // that the type will be disjoint from the protocol, @@ -2345,8 +2581,14 @@ impl<'db> Type<'db> { | Type::GenericAlias(..) | Type::IntLiteral(..) | Type::EnumLiteral(..)), - ) => visitor.visit((self, other), || { - any_protocol_members_absent_or_disjoint(db, protocol, ty, visitor) + ) => disjointness_visitor.visit((self, other), || { + any_protocol_members_absent_or_disjoint( + db, + protocol, + ty, + disjointness_visitor, + relation_visitor, + ) }), // This is the same as the branch above -- @@ -2356,22 +2598,33 @@ impl<'db> Type<'db> { | (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) if n.class(db).is_final(db) => { - visitor.visit((self, other), || { - any_protocol_members_absent_or_disjoint(db, protocol, nominal, visitor) + disjointness_visitor.visit((self, other), || { + any_protocol_members_absent_or_disjoint( + db, + protocol, + nominal, + disjointness_visitor, + relation_visitor, + ) }) } (Type::ProtocolInstance(protocol), other) - | (other, Type::ProtocolInstance(protocol)) => visitor.visit((self, other), || { - protocol.interface(db).members(db).when_any(db, |member| { - match other.member(db, member.name()).place { - Place::Type(attribute_type, _) => { - member.has_disjoint_type_from(db, attribute_type, visitor) + | (other, Type::ProtocolInstance(protocol)) => { + disjointness_visitor.visit((self, other), || { + protocol.interface(db).members(db).when_any(db, |member| { + match other.member(db, member.name()).place { + Place::Type(attribute_type, _) => member.has_disjoint_type_from( + db, + attribute_type, + disjointness_visitor, + relation_visitor, + ), + Place::Unbound => ConstraintSet::from(false), } - Place::Unbound => ConstraintSet::from(false), - } + }) }) - }), + } (Type::SubclassOf(subclass_of_ty), Type::ClassLiteral(class_b)) | (Type::ClassLiteral(class_b), Type::SubclassOf(subclass_of_ty)) => { @@ -2394,7 +2647,7 @@ impl<'db> Type<'db> { } (Type::SubclassOf(left), Type::SubclassOf(right)) => { - left.is_disjoint_from_impl(db, right, visitor) + left.is_disjoint_from_impl(db, right, disjointness_visitor) } // for `type[Any]`/`type[Unknown]`/`type[Todo]`, we know the type cannot be any larger than `type`, @@ -2403,10 +2656,10 @@ impl<'db> Type<'db> { | (other, Type::SubclassOf(subclass_of_ty)) => match subclass_of_ty.subclass_of() { SubclassOfInner::Dynamic(_) => KnownClass::Type .to_instance(db) - .is_disjoint_from_impl(db, other, visitor), + .is_disjoint_from_impl(db, other, disjointness_visitor, relation_visitor), SubclassOfInner::Class(class) => class .metaclass_instance_type(db) - .is_disjoint_from_impl(db, other, visitor), + .is_disjoint_from_impl(db, other, disjointness_visitor, relation_visitor), }, (Type::SpecialForm(special_form), Type::NominalInstance(instance)) @@ -2470,7 +2723,13 @@ impl<'db> Type<'db> { | (instance @ Type::NominalInstance(_), Type::EnumLiteral(enum_literal)) => { enum_literal .enum_class_instance(db) - .when_subtype_of(db, instance) + .has_relation_to_impl( + db, + instance, + TypeRelation::Subtyping, + relation_visitor, + disjointness_visitor, + ) .negate(db) } (Type::EnumLiteral(..), _) | (_, Type::EnumLiteral(..)) => ConstraintSet::from(true), @@ -2487,7 +2746,13 @@ impl<'db> Type<'db> { | (instance @ Type::NominalInstance(_), Type::GenericAlias(alias)) => { ClassType::from(alias) .metaclass_instance_type(db) - .when_subtype_of(db, instance) + .has_relation_to_impl( + db, + instance, + TypeRelation::Subtyping, + relation_visitor, + disjointness_visitor, + ) .negate(db) } @@ -2502,19 +2767,21 @@ impl<'db> Type<'db> { (Type::BoundMethod(_), other) | (other, Type::BoundMethod(_)) => KnownClass::MethodType .to_instance(db) - .is_disjoint_from_impl(db, other, visitor), + .is_disjoint_from_impl(db, other, disjointness_visitor, relation_visitor), (Type::KnownBoundMethod(method), other) | (other, Type::KnownBoundMethod(method)) => { - method - .class() - .to_instance(db) - .is_disjoint_from_impl(db, other, visitor) + method.class().to_instance(db).is_disjoint_from_impl( + db, + other, + disjointness_visitor, + relation_visitor, + ) } (Type::WrapperDescriptor(_), other) | (other, Type::WrapperDescriptor(_)) => { KnownClass::WrapperDescriptorType .to_instance(db) - .is_disjoint_from_impl(db, other, visitor) + .is_disjoint_from_impl(db, other, disjointness_visitor, relation_visitor) } (Type::Callable(_) | Type::FunctionLiteral(_), Type::Callable(_)) @@ -2559,7 +2826,13 @@ impl<'db> Type<'db> { .ignore_possibly_unbound() .when_none_or(|dunder_call| { dunder_call - .when_assignable_to(db, CallableType::unknown(db)) + .has_relation_to_impl( + db, + CallableType::unknown(db), + TypeRelation::Assignability, + relation_visitor, + disjointness_visitor, + ) .negate(db) }), @@ -2578,18 +2851,26 @@ impl<'db> Type<'db> { (Type::ModuleLiteral(..), other @ Type::NominalInstance(..)) | (other @ Type::NominalInstance(..), Type::ModuleLiteral(..)) => { // Modules *can* actually be instances of `ModuleType` subclasses - other.is_disjoint_from_impl(db, KnownClass::ModuleType.to_instance(db), visitor) + other.is_disjoint_from_impl( + db, + KnownClass::ModuleType.to_instance(db), + disjointness_visitor, + relation_visitor, + ) } - (Type::NominalInstance(left), Type::NominalInstance(right)) => visitor + (Type::NominalInstance(left), Type::NominalInstance(right)) => disjointness_visitor .visit((self, other), || { - left.is_disjoint_from_impl(db, right, visitor) + left.is_disjoint_from_impl(db, right, disjointness_visitor, relation_visitor) }), (Type::PropertyInstance(_), other) | (other, Type::PropertyInstance(_)) => { - KnownClass::Property - .to_instance(db) - .is_disjoint_from_impl(db, other, visitor) + KnownClass::Property.to_instance(db).is_disjoint_from_impl( + db, + other, + disjointness_visitor, + relation_visitor, + ) } (Type::BoundSuper(_), Type::BoundSuper(_)) => { @@ -2597,7 +2878,7 @@ impl<'db> Type<'db> { } (Type::BoundSuper(_), other) | (other, Type::BoundSuper(_)) => KnownClass::Super .to_instance(db) - .is_disjoint_from_impl(db, other, visitor), + .is_disjoint_from_impl(db, other, disjointness_visitor, relation_visitor), } } @@ -3162,7 +3443,7 @@ impl<'db> Type<'db> { Some((self, AttributeKind::NormalOrNonDataDescriptor)) } else { Some(( - callable.bind_self(db), + Type::Callable(callable.bind_self(db)), AttributeKind::NormalOrNonDataDescriptor, )) }; @@ -9191,20 +9472,22 @@ impl<'db> BoundMethodType<'db> { db: &'db dyn Db, other: Self, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db>, + relation_visitor: &HasRelationToVisitor<'db>, + disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { // A bound method is a typically a subtype of itself. However, we must explicitly verify // the subtyping of the underlying function signatures (since they might be specialized // differently), and of the bound self parameter (taking care that parameters, including a // bound self parameter, are contravariant.) self.function(db) - .has_relation_to_impl(db, other.function(db), relation, visitor) + .has_relation_to_impl(db, other.function(db), relation, relation_visitor) .and(db, || { other.self_instance(db).has_relation_to_impl( db, self.self_instance(db), relation, - visitor, + relation_visitor, + disjointness_visitor, ) }) } @@ -9286,12 +9569,8 @@ impl<'db> CallableType<'db> { Self::single(db, Signature::unknown()) } - pub(crate) fn bind_self(self, db: &'db dyn Db) -> Type<'db> { - Type::Callable(CallableType::new( - db, - self.signatures(db).bind_self(db, None), - false, - )) + pub(crate) fn bind_self(self, db: &'db dyn Db) -> CallableType<'db> { + CallableType::new(db, self.signatures(db).bind_self(db, None), false) } /// Create a callable type which represents a fully-static "bottom" callable. @@ -9346,13 +9625,19 @@ impl<'db> CallableType<'db> { db: &'db dyn Db, other: Self, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db>, + relation_visitor: &HasRelationToVisitor<'db>, + disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { if other.is_function_like(db) && !self.is_function_like(db) { return ConstraintSet::from(false); } - self.signatures(db) - .has_relation_to_impl(db, other.signatures(db), relation, visitor) + self.signatures(db).has_relation_to_impl( + db, + other.signatures(db), + relation, + relation_visitor, + disjointness_visitor, + ) } /// Check whether this callable type is equivalent to another callable type. diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 0afb14480a..da27e229c9 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -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, ) }) } diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index c781d4a69e..4397358983 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -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, 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() { diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index 87649cb6f4..1683b5d9ed 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -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() } diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 2b0daa9c18..57e8275120 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -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, + ) }) } } diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 3d3868b06c..526b07550c 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -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() }; diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index 1b0db0c808..f7d0a78768 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -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, + ), } } diff --git a/crates/ty_python_semantic/src/types/tuple.rs b/crates/ty_python_semantic/src/types/tuple.rs index c6d669612a..4eba3bed90 100644 --- a/crates/ty_python_semantic/src/types/tuple.rs +++ b/crates/ty_python_semantic/src/types/tuple.rs @@ -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> { db: &'db dyn Db, other: &Tuple>, 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> { 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> { 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> { // 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> { db: &'db dyn Db, other: &Tuple>, 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> { 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> { 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> { ); 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> { 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> { // 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> { 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> { &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> { db: &'db dyn Db, a: impl IntoIterator>, b: impl IntoIterator>, - 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> { 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, ) - } + }), } }