From 1a73410486ebf942ceaa5843c69d97f499f56abc Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 5 Sep 2025 18:41:36 +0100 Subject: [PATCH] wip --- .../mdtest/generics/pep695/variance.md | 9 +- .../resources/mdtest/protocols.md | 21 +- crates/ty_python_semantic/src/types.rs | 276 +++--------------- crates/ty_python_semantic/src/types/class.rs | 35 +-- .../src/types/constraints.rs | 2 +- .../ty_python_semantic/src/types/function.rs | 23 +- .../ty_python_semantic/src/types/generics.rs | 62 +--- .../ty_python_semantic/src/types/instance.rs | 57 +--- .../src/types/property_tests.rs | 8 +- .../src/types/signatures.rs | 125 +------- crates/ty_python_semantic/src/types/tuple.rs | 77 +---- 11 files changed, 86 insertions(+), 609 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/variance.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/variance.md index 48a1b922f2..267aff5723 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/variance.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/variance.md @@ -315,10 +315,11 @@ static_assert(is_equivalent_to(C[A], C[A])) static_assert(is_equivalent_to(C[B], C[B])) static_assert(is_equivalent_to(C[B], C[A])) static_assert(is_equivalent_to(C[A], C[B])) -static_assert(is_equivalent_to(C[A], C[Any])) -static_assert(is_equivalent_to(C[B], C[Any])) -static_assert(is_equivalent_to(C[Any], C[A])) -static_assert(is_equivalent_to(C[Any], C[B])) + +static_assert(not is_equivalent_to(C[A], C[Any])) +static_assert(not is_equivalent_to(C[B], C[Any])) +static_assert(not is_equivalent_to(C[Any], C[A])) +static_assert(not is_equivalent_to(C[Any], C[B])) static_assert(not is_equivalent_to(D[A], C[A])) static_assert(not is_equivalent_to(D[B], C[B])) diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index 1267bb27fd..1e879bfcd4 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -1147,10 +1147,10 @@ static_assert( ) ) -static_assert(not is_equivalent_to(GenericProto[str], GenericProto[int])) -static_assert(not is_equivalent_to(GenericProto[str], LegacyGenericProto[int])) -static_assert(not is_equivalent_to(GenericProto, GenericProto[int])) -static_assert(not is_equivalent_to(LegacyGenericProto, LegacyGenericProto[int])) +static_assert(not is_equivalent_to(GenericProto[str], GenericProto[int])) # error: [static-assert-error] +static_assert(not is_equivalent_to(GenericProto[str], LegacyGenericProto[int])) # error: [static-assert-error] +static_assert(not is_equivalent_to(GenericProto, GenericProto[int])) # error: [static-assert-error] +static_assert(not is_equivalent_to(LegacyGenericProto, LegacyGenericProto[int])) # error: [static-assert-error] ``` ## Intersections of protocols @@ -1543,8 +1543,7 @@ from ty_extensions import is_equivalent_to class HasMutableXAttr(Protocol): x: int -# TODO: should pass -static_assert(is_equivalent_to(HasMutableXAttr, HasMutableXProperty)) # error: [static-assert-error] +static_assert(is_equivalent_to(HasMutableXAttr, HasMutableXProperty)) static_assert(is_subtype_of(HasMutableXAttr, HasXProperty)) static_assert(is_assignable_to(HasMutableXAttr, HasXProperty)) @@ -1835,9 +1834,7 @@ class P4(Protocol): def z(self, value: int) -> None: ... static_assert(is_equivalent_to(P1, P2)) - -# TODO: should pass -static_assert(is_equivalent_to(P3, P4)) # error: [static-assert-error] +static_assert(is_equivalent_to(P3, P4)) ``` As with protocols that only have non-method members, this also holds true when they appear in @@ -1848,9 +1845,7 @@ class A: ... class B: ... static_assert(is_equivalent_to(A | B | P1, P2 | B | A)) - -# TODO: should pass -static_assert(is_equivalent_to(A | B | P3, P4 | B | A)) # error: [static-assert-error] +static_assert(is_equivalent_to(A | B | P3, P4 | B | A)) ``` ## Narrowing of protocols @@ -2131,8 +2126,6 @@ class Bar(Protocol): @property def x(self) -> "Bar": ... -# TODO: this should pass -# error: [static-assert-error] static_assert(is_equivalent_to(Foo, Bar)) T = TypeVar("T", bound="TypeVarRecursive") diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 0785928672..c036b62062 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -188,10 +188,6 @@ pub(crate) type HasRelationToVisitor<'db, C> = pub(crate) type IsDisjointVisitor<'db, C> = PairVisitor<'db, IsDisjoint, C>; pub(crate) struct IsDisjoint; -/// A [`PairVisitor`] that is used in `is_equivalent` methods. -pub(crate) type IsEquivalentVisitor<'db, C> = PairVisitor<'db, IsEquivalent, C>; -pub(crate) struct IsEquivalent; - /// A [`CycleDetector`] that is used in `find_legacy_typevars` methods. pub(crate) type FindLegacyTypeVarsVisitor<'db> = CycleDetector, ()>; pub(crate) struct FindLegacyTypeVars; @@ -1422,9 +1418,15 @@ impl<'db> Type<'db> { // no other type is a subtype of or assignable to `Never`. (_, Type::Never) => C::unsatisfiable(db), - (Type::Union(union), _) => union.elements(db).iter().when_all(db, |&elem_ty| { - elem_ty.has_relation_to_impl(db, target, relation, visitor) - }), + (Type::Union(union), _) => union + .elements(db) + .iter() + .when_all(db, |&elem_ty| { + elem_ty.has_relation_to_impl(db, target, relation, visitor) + }) + .or(db, || { + C::from_bool(db, self.normalized(db) == target.normalized(db)) + }), (_, Type::Union(union)) => union.elements(db).iter().when_any(db, |&elem_ty| { self.has_relation_to_impl(db, elem_ty, relation, visitor) @@ -1465,7 +1467,7 @@ impl<'db> Type<'db> { (left, Type::AlwaysTruthy) => C::from_bool(db, left.bool(db).is_always_true()), // Currently, the only supertype of `AlwaysFalsy` and `AlwaysTruthy` is the universal set (object instance). (Type::AlwaysFalsy | Type::AlwaysTruthy, _) => { - target.when_equivalent_to(db, Type::object(db)) + C::from_bool(db, target.is_equivalent_to(db, Type::object(db))) } // These clauses handle type variants that include function literals. A function @@ -1602,13 +1604,16 @@ impl<'db> Type<'db> { (Type::Callable(_), _) => C::unsatisfiable(db), - (Type::BoundSuper(_), Type::BoundSuper(_)) => self.when_equivalent_to(db, target), + (Type::BoundSuper(_), Type::BoundSuper(_)) => visitor + .visit((self, target, relation), || { + self.when_equivalent_to_impl(db, target, visitor) + }), (Type::BoundSuper(_), _) => KnownClass::Super .to_instance(db) .has_relation_to_impl(db, target, relation, 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`. + // since `type[B]` describes all possible runtiexime subclasses of the class object `B`. (Type::ClassLiteral(class), Type::SubclassOf(target_subclass_ty)) => target_subclass_ty .subclass_of() .into_class() @@ -1740,95 +1745,31 @@ impl<'db> Type<'db> { } fn when_equivalent_to>(self, db: &'db dyn Db, other: Type<'db>) -> C { - self.is_equivalent_to_impl( - db, - other, - &IsEquivalentVisitor::new(C::always_satisfiable(db)), - ) + let visitor = HasRelationToVisitor::new(C::always_satisfiable(db)); + self.when_equivalent_to_impl(db, other, &visitor) } - pub(crate) fn is_equivalent_to_impl>( + fn when_equivalent_to_impl>( self, db: &'db dyn Db, other: Type<'db>, - visitor: &IsEquivalentVisitor<'db, C>, + visitor: &HasRelationToVisitor<'db, C>, ) -> C { - if self == other { - return C::always_satisfiable(db); - } - - match (self, other) { - (Type::Dynamic(_), Type::Dynamic(_)) => C::always_satisfiable(db), - - (Type::SubclassOf(first), Type::SubclassOf(second)) => { - match (first.subclass_of(), second.subclass_of()) { - (first, second) if first == second => C::always_satisfiable(db), - (SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => { - C::always_satisfiable(db) - } - _ => C::unsatisfiable(db), - } - } - - (Type::TypeAlias(self_alias), _) => { - let self_alias_ty = self_alias.value_type(db).normalized(db); - visitor.visit((self_alias_ty, other), || { - self_alias_ty.is_equivalent_to_impl(db, other, visitor) - }) - } - - (_, Type::TypeAlias(other_alias)) => { - let other_alias_ty = other_alias.value_type(db).normalized(db); - visitor.visit((self, other_alias_ty), || { - self.is_equivalent_to_impl(db, other_alias_ty, visitor) - }) - } - - (Type::NominalInstance(first), Type::NominalInstance(second)) => { - first.is_equivalent_to_impl(db, second, visitor) - } - - (Type::Union(first), Type::Union(second)) => { - first.is_equivalent_to_impl(db, second, visitor) - } - - (Type::Intersection(first), Type::Intersection(second)) => { - first.is_equivalent_to_impl(db, second, visitor) - } - - (Type::FunctionLiteral(self_function), Type::FunctionLiteral(target_function)) => { - self_function.is_equivalent_to_impl(db, target_function, visitor) - } - (Type::BoundMethod(self_method), Type::BoundMethod(target_method)) => { - self_method.is_equivalent_to_impl(db, target_method, visitor) - } - (Type::MethodWrapper(self_method), Type::MethodWrapper(target_method)) => { - self_method.is_equivalent_to_impl(db, target_method, visitor) - } - (Type::Callable(first), Type::Callable(second)) => { - first.is_equivalent_to_impl(db, second, visitor) - } - - (Type::ProtocolInstance(first), Type::ProtocolInstance(second)) => { - first.is_equivalent_to_impl(db, second, visitor) - } - (Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n)) - | (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => { - C::from_bool(db, n.is_object(db) && protocol.normalized(db) == nominal) - } - // An instance of an enum class is equivalent to an enum literal of that class, - // if that enum has only has one member. - (Type::NominalInstance(instance), Type::EnumLiteral(literal)) - | (Type::EnumLiteral(literal), Type::NominalInstance(instance)) => { - if literal.enum_class_instance(db) != Type::NominalInstance(instance) { - return C::unsatisfiable(db); - } - - let class_literal = instance.class(db).class_literal(db).0; - C::from_bool(db, is_single_member_enum(db, class_literal)) - } - _ => C::unsatisfiable(db), - } + let self_relation = self.has_relation_to_impl(db, other, TypeRelation::Subtyping, visitor); + // println!( + // "{}, {}, {}", + // self.display(db), + // other.display(db), + // self_relation.display(db) + // ); + let other_relation = other.has_relation_to_impl(db, self, TypeRelation::Subtyping, visitor); + // println!( + // "{}, {}, {}", + // other.display(db), + // self.display(db), + // other_relation.display(db) + // ); + self_relation.and(db, || other_relation) } /// Return true if this type and `other` have no common elements. @@ -8855,21 +8796,6 @@ impl<'db> BoundMethodType<'db> { ) }) } - - fn is_equivalent_to_impl>( - self, - db: &'db dyn Db, - other: Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { - self.function(db) - .is_equivalent_to_impl(db, other.function(db), visitor) - .and(db, || { - other - .self_instance(db) - .is_equivalent_to_impl(db, self.self_instance(db), visitor) - }) - } } /// This type represents the set of all callable objects with a certain, possibly overloaded, @@ -9002,25 +8928,6 @@ impl<'db> CallableType<'db> { self.signatures(db) .has_relation_to_impl(db, other.signatures(db), relation, visitor) } - - /// Check whether this callable type is equivalent to another callable type. - /// - /// See [`Type::is_equivalent_to`] for more details. - fn is_equivalent_to_impl>( - self, - db: &'db dyn Db, - other: Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { - if self == other { - return C::always_satisfiable(db); - } - - C::from_bool(db, self.is_function_like(db) == other.is_function_like(db)).and(db, || { - self.signatures(db) - .is_equivalent_to_impl(db, other.signatures(db), visitor) - }) - } } /// Represents a specific instance of `types.MethodWrapperType` @@ -9108,44 +9015,6 @@ impl<'db> MethodWrapperKind<'db> { } } - fn is_equivalent_to_impl>( - self, - db: &'db dyn Db, - other: Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { - match (self, other) { - ( - MethodWrapperKind::FunctionTypeDunderGet(self_function), - MethodWrapperKind::FunctionTypeDunderGet(other_function), - ) => self_function.is_equivalent_to_impl(db, other_function, visitor), - - ( - MethodWrapperKind::FunctionTypeDunderCall(self_function), - MethodWrapperKind::FunctionTypeDunderCall(other_function), - ) => self_function.is_equivalent_to_impl(db, other_function, visitor), - - (MethodWrapperKind::PropertyDunderGet(_), MethodWrapperKind::PropertyDunderGet(_)) - | (MethodWrapperKind::PropertyDunderSet(_), MethodWrapperKind::PropertyDunderSet(_)) - | (MethodWrapperKind::StrStartswith(_), MethodWrapperKind::StrStartswith(_)) => { - C::from_bool(db, self == other) - } - - ( - MethodWrapperKind::FunctionTypeDunderGet(_) - | MethodWrapperKind::FunctionTypeDunderCall(_) - | MethodWrapperKind::PropertyDunderGet(_) - | MethodWrapperKind::PropertyDunderSet(_) - | MethodWrapperKind::StrStartswith(_), - MethodWrapperKind::FunctionTypeDunderGet(_) - | MethodWrapperKind::FunctionTypeDunderCall(_) - | MethodWrapperKind::PropertyDunderGet(_) - | MethodWrapperKind::PropertyDunderSet(_) - | MethodWrapperKind::StrStartswith(_), - ) => C::unsatisfiable(db), - } - } - fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self { match self { MethodWrapperKind::FunctionTypeDunderGet(function) => { @@ -9640,14 +9509,6 @@ impl<'db> UnionType<'db> { } } - /// Create a new union type with the elements normalized. - /// - /// See [`Type::normalized`] for more details. - #[must_use] - pub(crate) fn normalized(self, db: &'db dyn Db) -> Type<'db> { - self.normalized_impl(db, &NormalizedVisitor::default()) - } - pub(crate) fn normalized_impl( self, db: &'db dyn Db, @@ -9664,32 +9525,6 @@ impl<'db> UnionType<'db> { ) .build() } - - pub(crate) fn is_equivalent_to_impl>( - self, - db: &'db dyn Db, - other: Self, - _visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { - if self == other { - return C::always_satisfiable(db); - } - - let self_elements = self.elements(db); - let other_elements = other.elements(db); - - if self_elements.len() != other_elements.len() { - return C::unsatisfiable(db); - } - - let sorted_self = self.normalized(db); - - if sorted_self == Type::Union(other) { - return C::always_satisfiable(db); - } - - C::from_bool(db, sorted_self == other.normalized(db)) - } } #[salsa::interned(debug, heap_size=IntersectionType::heap_size)] @@ -9734,15 +9569,6 @@ impl<'db> IntersectionType<'db> { .build() } - /// Return a new `IntersectionType` instance with the positive and negative types sorted - /// according to a canonical ordering, and other normalizations applied to each element as applicable. - /// - /// See [`Type::normalized`] for more details. - #[must_use] - pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { - self.normalized_impl(db, &NormalizedVisitor::default()) - } - pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self { fn normalized_set<'db>( db: &'db dyn Db, @@ -9765,40 +9591,6 @@ impl<'db> IntersectionType<'db> { ) } - /// Return `true` if `self` represents exactly the same set of possible runtime objects as `other` - pub(crate) fn is_equivalent_to_impl>( - self, - db: &'db dyn Db, - other: Self, - _visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { - if self == other { - return C::always_satisfiable(db); - } - - let self_positive = self.positive(db); - let other_positive = other.positive(db); - - if self_positive.len() != other_positive.len() { - return C::unsatisfiable(db); - } - - let self_negative = self.negative(db); - let other_negative = other.negative(db); - - if self_negative.len() != other_negative.len() { - return C::unsatisfiable(db); - } - - let sorted_self = self.normalized(db); - - if sorted_self == other { - return C::always_satisfiable(db); - } - - C::from_bool(db, sorted_self == other.normalized(db)) - } - /// Returns an iterator over the positive elements of the intersection. If /// there are no positive elements, returns a single `object` type. fn positive_elements_or_object(self, db: &'db dyn Db) -> impl Iterator> { diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index ac8b245a63..463733c2e1 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -27,10 +27,10 @@ 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, TypeMapping, - TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypedDictParams, - UnionBuilder, VarianceInferable, declaration_type, infer_definition_types, + KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind, NormalizedVisitor, + PropertyInstanceType, StringLiteralType, TypeAliasType, TypeMapping, TypeRelation, + TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypedDictParams, UnionBuilder, + VarianceInferable, declaration_type, infer_definition_types, }; use crate::{ Db, FxIndexMap, FxOrderSet, Program, @@ -584,33 +584,6 @@ impl<'db> ClassType<'db> { }) } - pub(super) fn is_equivalent_to_impl>( - self, - db: &'db dyn Db, - other: ClassType<'db>, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { - if self == other { - return C::always_satisfiable(db); - } - - match (self, other) { - // A non-generic class is never equivalent to a generic class. - // Two non-generic classes are only equivalent if they are equal (handled above). - (ClassType::NonGeneric(_), _) | (_, ClassType::NonGeneric(_)) => C::unsatisfiable(db), - - (ClassType::Generic(this), ClassType::Generic(other)) => { - C::from_bool(db, this.origin(db) == other.origin(db)).and(db, || { - this.specialization(db).is_equivalent_to_impl( - db, - other.specialization(db), - visitor, - ) - }) - } - } - } - /// Return the metaclass of this class, or `type[Unknown]` if the metaclass cannot be inferred. pub(super) fn metaclass(self, db: &'db dyn Db) -> Type<'db> { let (class_literal, specialization) = self.class_literal(db); diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs index 0ee28c89e8..ba96c5e6d7 100644 --- a/crates/ty_python_semantic/src/types/constraints.rs +++ b/crates/ty_python_semantic/src/types/constraints.rs @@ -81,7 +81,7 @@ use crate::Db; use crate::types::{BoundTypeVarInstance, IntersectionType, Type, UnionType}; /// Encodes the constraints under which a type property (e.g. assignability) holds. -pub(crate) trait Constraints<'db>: Clone + Sized { +pub(crate) trait Constraints<'db>: Clone + Sized + std::fmt::Debug { /// Returns a constraint set that never holds fn unsatisfiable(db: &'db dyn Db) -> Self; diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 0a544a1e5f..f0d6648a38 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -78,9 +78,9 @@ use crate::types::signatures::{CallableSignature, Signature}; use crate::types::visitor::any_over_type; use crate::types::{ BoundMethodType, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, ClassType, - DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor, HasRelationToVisitor, - IsEquivalentVisitor, KnownClass, NormalizedVisitor, SpecialFormType, Truthiness, Type, - TypeMapping, TypeRelation, UnionBuilder, all_members, binding_type, walk_type_mapping, + DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor, HasRelationToVisitor, KnownClass, + NormalizedVisitor, SpecialFormType, Truthiness, Type, TypeMapping, TypeRelation, UnionBuilder, + all_members, binding_type, walk_type_mapping, }; use crate::{Db, FxOrderSet, ModuleName, resolve_module}; @@ -981,23 +981,6 @@ impl<'db> FunctionType<'db> { && self.signature(db).is_assignable_to(db, other.signature(db)) } - pub(crate) fn is_equivalent_to_impl>( - self, - db: &'db dyn Db, - other: Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { - if self.normalized(db) == other.normalized(db) { - return C::always_satisfiable(db); - } - if self.literal(db) != other.literal(db) { - return C::unsatisfiable(db); - } - let self_signature = self.signature(db); - let other_signature = other.signature(db); - self_signature.is_equivalent_to_impl(db, other_signature, visitor) - } - pub(crate) fn find_legacy_typevars_impl( self, db: &'db dyn Db, diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index bfb248202b..5293460e5a 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, TypeVarVariance, - UnionType, binding_type, declaration_type, + KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, Type, TypeMapping, + TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarVariance, UnionType, + binding_type, declaration_type, }; use crate::{Db, FxOrderSet}; @@ -805,7 +805,9 @@ impl<'db> Specialization<'db> { { match relation { TypeRelation::Assignability => continue, - TypeRelation::Subtyping => return C::unsatisfiable(db), + TypeRelation::Subtyping => { + return C::from_bool(db, self_type.is_dynamic() && other_type.is_dynamic()); + } } } @@ -841,58 +843,6 @@ impl<'db> Specialization<'db> { result } - pub(crate) fn is_equivalent_to_impl>( - self, - db: &'db dyn Db, - other: Specialization<'db>, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { - if self.materialization_kind(db) != other.materialization_kind(db) { - return C::unsatisfiable(db); - } - let generic_context = self.generic_context(db); - if generic_context != other.generic_context(db) { - return C::unsatisfiable(db); - } - - let mut result = C::always_satisfiable(db); - for ((bound_typevar, self_type), other_type) in (generic_context.variables(db).into_iter()) - .zip(self.types(db)) - .zip(other.types(db)) - { - // Equivalence of each type in the specialization depends on the variance of the - // corresponding typevar: - // - covariant: verify that self_type == other_type - // - contravariant: verify that other_type == self_type - // - invariant: verify that self_type == other_type - // - bivariant: skip, can't make equivalence false - let compatible = match bound_typevar.variance(db) { - TypeVarVariance::Invariant - | TypeVarVariance::Covariant - | TypeVarVariance::Contravariant => { - self_type.is_equivalent_to_impl(db, *other_type, visitor) - } - TypeVarVariance::Bivariant => C::always_satisfiable(db), - }; - if result.intersect(db, compatible).is_never_satisfied(db) { - return result; - } - } - - match (self.tuple_inner(db), other.tuple_inner(db)) { - (Some(_), None) | (None, Some(_)) => return C::unsatisfiable(db), - (None, None) => {} - (Some(self_tuple), Some(other_tuple)) => { - let compatible = self_tuple.is_equivalent_to_impl(db, other_tuple, visitor); - if result.intersect(db, compatible).is_never_satisfied(db) { - return result; - } - } - } - - result - } - pub(crate) fn find_legacy_typevars_impl( self, db: &'db dyn Db, diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index 8714c824aa..64030c41c4 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -13,8 +13,7 @@ use crate::types::protocol_class::walk_protocol_interface; use crate::types::tuple::{TupleSpec, TupleType}; use crate::types::{ ApplyTypeMappingVisitor, ClassBase, FindLegacyTypeVarsVisitor, HasRelationToVisitor, - IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, TypeMapping, TypeRelation, - VarianceInferable, + IsDisjointVisitor, NormalizedVisitor, TypeMapping, TypeRelation, VarianceInferable, }; use crate::{Db, FxOrderSet}; @@ -278,24 +277,6 @@ impl<'db> NominalInstanceType<'db> { } } - pub(super) fn is_equivalent_to_impl>( - self, - db: &'db dyn Db, - other: Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { - match (self.0, other.0) { - ( - NominalInstanceInner::ExactTuple(tuple1), - NominalInstanceInner::ExactTuple(tuple2), - ) => tuple1.is_equivalent_to_impl(db, tuple2, visitor), - (NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => { - class1.is_equivalent_to_impl(db, class2, visitor) - } - _ => C::unsatisfiable(db), - } - } - pub(super) fn is_disjoint_from_impl>( self, db: &'db dyn Db, @@ -482,13 +463,6 @@ impl<'db> ProtocolInstanceType<'db> { } } - /// Return a "normalized" version of this `Protocol` type. - /// - /// See [`Type::normalized`] for more details. - pub(super) fn normalized(self, db: &'db dyn Db) -> Type<'db> { - self.normalized_impl(db, &NormalizedVisitor::default()) - } - /// Return a "normalized" version of this `Protocol` type. /// /// See [`Type::normalized`] for more details. @@ -524,32 +498,21 @@ impl<'db> ProtocolInstanceType<'db> { self, db: &'db dyn Db, other: Self, - _relation: TypeRelation, + relation: TypeRelation, visitor: &HasRelationToVisitor<'db, C>, ) -> C { other .inner .interface(db) .is_sub_interface_of(db, self.inner.interface(db), visitor) - } - - /// Return `true` if this protocol type is equivalent to the protocol `other`. - /// - /// TODO: consider the types of the members as well as their existence - pub(super) fn is_equivalent_to_impl>( - self, - db: &'db dyn Db, - other: Self, - _visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { - if self == other { - return C::always_satisfiable(db); - } - let self_normalized = self.normalized(db); - if self_normalized == Type::ProtocolInstance(other) { - return C::always_satisfiable(db); - } - C::from_bool(db, self_normalized == other.normalized(db)) + .or(db, || { + Type::object(db).has_relation_to_impl( + db, + Type::ProtocolInstance(other), + relation, + visitor, + ) + }) } /// Return `true` if this protocol type is disjoint from the protocol `other`. diff --git a/crates/ty_python_semantic/src/types/property_tests.rs b/crates/ty_python_semantic/src/types/property_tests.rs index 77747bcaed..cea9f372ba 100644 --- a/crates/ty_python_semantic/src/types/property_tests.rs +++ b/crates/ty_python_semantic/src/types/property_tests.rs @@ -173,16 +173,16 @@ mod stable { forall types s, t. s.is_assignable_to(db, union(db, [s, t])) && t.is_assignable_to(db, union(db, [s, t])) ); - // Only `Never` is a subtype of `Any`. + // Only `Never`/`Any` are subtypes of `Any`. type_property_test!( only_never_is_subtype_of_any, db, - forall types s. !s.is_equivalent_to(db, Type::Never) => !s.is_subtype_of(db, Type::any()) + forall types s. !s.is_equivalent_to(db, Type::Never) => s.is_dynamic() || !s.is_subtype_of(db, Type::any()) ); - // Only `object` is a supertype of `Any`. + // Only `object`/`Any` are supertypes of `Any`. type_property_test!( only_object_is_supertype_of_any, db, - forall types t. !t.is_equivalent_to(db, Type::object(db)) => !Type::any().is_subtype_of(db, t) + forall types t. !t.is_equivalent_to(db, Type::object(db)) => t.is_dynamic() || !Type::any().is_subtype_of(db, t) ); // Equivalence is commutative. diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index ffd11bf77f..bc65ad5356 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, Constraints, IteratorConstraintsE 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, KnownClass, MaterializationKind, NormalizedVisitor, TypeMapping, + TypeRelation, VarianceInferable, todo_type, }; use crate::{Db, FxOrderSet}; use ruff_python_ast::{self as ast, name::Name}; @@ -197,31 +197,6 @@ impl<'db> CallableSignature<'db> { }), } } - - /// Check whether this callable type is equivalent to another callable type. - /// - /// See [`Type::is_equivalent_to`] for more details. - pub(crate) fn is_equivalent_to_impl>( - &self, - db: &'db dyn Db, - other: &Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { - match (self.overloads.as_slice(), other.overloads.as_slice()) { - ([self_signature], [other_signature]) => { - // Common case: both callable types contain a single signature, use the custom - // equivalence check instead of delegating it to the subtype check. - self_signature.is_equivalent_to_impl(db, other_signature, visitor) - } - (_, _) => { - if self == other { - return C::always_satisfiable(db); - } - self.is_subtype_of_impl::(db, other) - .and(db, || other.is_subtype_of_impl(db, self)) - } - } - } } impl<'a, 'db> IntoIterator for &'a CallableSignature<'db> { @@ -517,91 +492,6 @@ impl<'db> Signature<'db> { } } - /// Return `true` if `self` has exactly the same set of possible static materializations as - /// `other` (if `self` represents the same set of possible sets of possible runtime objects as - /// `other`). - pub(crate) fn is_equivalent_to_impl>( - &self, - db: &'db dyn Db, - other: &Signature<'db>, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { - let mut result = C::always_satisfiable(db); - let mut check_types = |self_type: Option>, other_type: Option>| { - let self_type = self_type.unwrap_or(Type::unknown()); - let other_type = other_type.unwrap_or(Type::unknown()); - !result - .intersect(db, self_type.is_equivalent_to_impl(db, other_type, visitor)) - .is_never_satisfied(db) - }; - - if self.parameters.is_gradual() != other.parameters.is_gradual() { - return C::unsatisfiable(db); - } - - if self.parameters.len() != other.parameters.len() { - return C::unsatisfiable(db); - } - - if !check_types(self.return_ty, other.return_ty) { - return result; - } - - for (self_parameter, other_parameter) in self.parameters.iter().zip(&other.parameters) { - match (self_parameter.kind(), other_parameter.kind()) { - ( - ParameterKind::PositionalOnly { - default_type: self_default, - .. - }, - ParameterKind::PositionalOnly { - default_type: other_default, - .. - }, - ) if self_default.is_some() == other_default.is_some() => {} - - ( - ParameterKind::PositionalOrKeyword { - name: self_name, - default_type: self_default, - }, - ParameterKind::PositionalOrKeyword { - name: other_name, - default_type: other_default, - }, - ) if self_default.is_some() == other_default.is_some() - && self_name == other_name => {} - - (ParameterKind::Variadic { .. }, ParameterKind::Variadic { .. }) => {} - - ( - ParameterKind::KeywordOnly { - name: self_name, - default_type: self_default, - }, - ParameterKind::KeywordOnly { - name: other_name, - default_type: other_default, - }, - ) if self_default.is_some() == other_default.is_some() - && self_name == other_name => {} - - (ParameterKind::KeywordVariadic { .. }, ParameterKind::KeywordVariadic { .. }) => {} - - _ => return C::unsatisfiable(db), - } - - if !check_types( - self_parameter.annotated_type(), - other_parameter.annotated_type(), - ) { - return result; - } - } - - result - } - /// Implementation of subtyping and assignability for signature. fn has_relation_to_impl>( &self, @@ -701,8 +591,15 @@ impl<'db> Signature<'db> { } // If either of the parameter lists is gradual (`...`), then it is assignable to and from - // any other parameter list, but not a subtype or supertype of any other parameter list. - if self.parameters.is_gradual() || other.parameters.is_gradual() { + // any other parameter list, but only a subtype or supertype of another parameter list if + // that parameter list is also gradual. + if self.parameters.is_gradual() { + return if other.parameters.is_gradual() { + C::always_satisfiable(db) + } else { + C::from_bool(db, relation.is_assignability()) + }; + } else if other.parameters.is_gradual() { return C::from_bool(db, relation.is_assignability()); } diff --git a/crates/ty_python_semantic/src/types/tuple.rs b/crates/ty_python_semantic/src/types/tuple.rs index cb947d2695..82cfab69f6 100644 --- a/crates/ty_python_semantic/src/types/tuple.rs +++ b/crates/ty_python_semantic/src/types/tuple.rs @@ -27,8 +27,7 @@ use crate::types::class::{ClassType, KnownClass}; use crate::types::constraints::{Constraints, IteratorConstraintsExtension}; use crate::types::{ ApplyTypeMappingVisitor, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor, - IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, Type, TypeMapping, TypeRelation, - UnionBuilder, UnionType, + IsDisjointVisitor, NormalizedVisitor, Type, TypeMapping, TypeRelation, UnionBuilder, UnionType, }; use crate::util::subscript::{Nth, OutOfBoundsError, PyIndex, PySlice, StepSizeZeroError}; use crate::{Db, FxOrderSet, Program}; @@ -264,16 +263,6 @@ impl<'db> TupleType<'db> { .has_relation_to_impl(db, other.tuple(db), relation, visitor) } - pub(crate) fn is_equivalent_to_impl>( - self, - db: &'db dyn Db, - other: Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { - self.tuple(db) - .is_equivalent_to_impl(db, other.tuple(db), visitor) - } - pub(crate) fn is_single_valued(self, db: &'db dyn Db) -> bool { self.tuple(db).is_single_valued(db) } @@ -468,21 +457,6 @@ impl<'db> FixedLengthTuple> { } } - fn is_equivalent_to_impl>( - &self, - db: &'db dyn Db, - other: &Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { - C::from_bool(db, self.0.len() == other.0.len()).and(db, || { - (self.0.iter()) - .zip(&other.0) - .when_all(db, |(self_ty, other_ty)| { - self_ty.is_equivalent_to_impl(db, *other_ty, visitor) - }) - }) - } - fn is_single_valued(&self, db: &'db dyn Db) -> bool { self.0.iter().all(|ty| ty.is_single_valued(db)) } @@ -871,36 +845,6 @@ impl<'db> VariableLengthTuple> { } } } - - fn is_equivalent_to_impl>( - &self, - db: &'db dyn Db, - other: &Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { - self.variable - .is_equivalent_to_impl(db, other.variable, visitor) - .and(db, || { - (self.prenormalized_prefix_elements(db, None)) - .zip_longest(other.prenormalized_prefix_elements(db, None)) - .when_all(db, |pair| match pair { - EitherOrBoth::Both(self_ty, other_ty) => { - self_ty.is_equivalent_to_impl(db, other_ty, visitor) - } - EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => C::unsatisfiable(db), - }) - }) - .and(db, || { - (self.prenormalized_suffix_elements(db, None)) - .zip_longest(other.prenormalized_suffix_elements(db, None)) - .when_all(db, |pair| match pair { - EitherOrBoth::Both(self_ty, other_ty) => { - self_ty.is_equivalent_to_impl(db, other_ty, visitor) - } - EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => C::unsatisfiable(db), - }) - }) - } } impl<'db> PyIndex<'db> for &VariableLengthTuple> { @@ -1093,25 +1037,6 @@ impl<'db> Tuple> { } } - fn is_equivalent_to_impl>( - &self, - db: &'db dyn Db, - other: &Self, - visitor: &IsEquivalentVisitor<'db, C>, - ) -> C { - match (self, other) { - (Tuple::Fixed(self_tuple), Tuple::Fixed(other_tuple)) => { - self_tuple.is_equivalent_to_impl(db, other_tuple, visitor) - } - (Tuple::Variable(self_tuple), Tuple::Variable(other_tuple)) => { - self_tuple.is_equivalent_to_impl(db, other_tuple, visitor) - } - (Tuple::Fixed(_), Tuple::Variable(_)) | (Tuple::Variable(_), Tuple::Fixed(_)) => { - C::unsatisfiable(db) - } - } - } - pub(super) fn is_disjoint_from_impl>( &self, db: &'db dyn Db,