diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index af54dae8d6..0f3cac11c1 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1,6 +1,7 @@ use infer::enclosing_class_symbol; use itertools::Either; +use std::cmp::Ordering; use std::slice::Iter; use std::str::FromStr; @@ -7973,6 +7974,10 @@ impl<'db> StringLiteralType<'db> { .chars() .map(|c| StringLiteralType::new(db, c.to_string().as_str())) } + + pub(crate) fn ordering(self, db: &'db dyn Db, other: Self) -> Ordering { + self.value(db).cmp(other.value(db)) + } } #[salsa::interned(debug)] @@ -7985,6 +7990,10 @@ impl<'db> BytesLiteralType<'db> { pub(crate) fn python_len(self, db: &'db dyn Db) -> usize { self.value(db).len() } + + pub(crate) fn ordering(self, db: &'db dyn Db, other: Self) -> Ordering { + self.value(db).cmp(other.value(db)) + } } #[salsa::interned(debug)] diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 8a6836253b..4c7620ea12 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use std::hash::BuildHasherDefault; use std::sync::{LazyLock, Mutex}; @@ -187,6 +188,15 @@ impl<'db> GenericAlias<'db> { ) { self.specialization(db).find_legacy_typevars(db, typevars); } + + pub(super) fn ordering(self, db: &'db dyn Db, other: Self) -> Ordering { + self.origin(db) + .ordering(db, other.origin(db)) + .then_with(|| { + self.specialization(db) + .ordering(db, other.specialization(db)) + }) + } } impl<'db> From> for Type<'db> { @@ -197,9 +207,7 @@ impl<'db> From> for Type<'db> { /// Represents a class type, which might be a non-generic class, or a specialization of a generic /// class. -#[derive( - Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, salsa::Supertype, salsa::Update, -)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, salsa::Supertype, salsa::Update)] pub enum ClassType<'db> { NonGeneric(ClassLiteral<'db>), Generic(GenericAlias<'db>), @@ -470,6 +478,15 @@ impl<'db> ClassType<'db> { .own_instance_member(db, name) .map_type(|ty| ty.apply_optional_specialization(db, specialization)) } + + pub fn ordering(self, db: &'db dyn Db, other: Self) -> Ordering { + match (self, other) { + (ClassType::NonGeneric(_), ClassType::Generic(_)) => Ordering::Less, + (ClassType::Generic(_), ClassType::NonGeneric(_)) => Ordering::Greater, + (ClassType::NonGeneric(this), ClassType::NonGeneric(other)) => this.ordering(db, other), + (ClassType::Generic(this), ClassType::Generic(other)) => this.ordering(db, other), + } + } } impl<'db> From> for ClassType<'db> { @@ -506,6 +523,14 @@ pub struct ClassLiteral<'db> { pub(crate) dataclass_transformer_params: Option, } +impl<'db> ClassLiteral<'db> { + pub fn ordering(self, db: &'db dyn Db, other: Self) -> Ordering { + self.name(db) + .cmp(other.name(db)) + .then_with(|| self.body_scope(db).cmp(&other.body_scope(db))) + } +} + #[expect(clippy::trivially_copy_pass_by_ref, clippy::ref_option)] fn pep695_generic_context_cycle_recover<'db>( _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 a6360e5732..a178afe40c 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -1,3 +1,5 @@ +use std::cmp::Ordering; + use ruff_python_ast as ast; use rustc_hash::FxHashMap; @@ -7,8 +9,9 @@ use crate::types::class_base::ClassBase; use crate::types::instance::{NominalInstanceType, Protocol, ProtocolInstanceType}; use crate::types::signatures::{Parameter, Parameters, Signature}; use crate::types::{ - declaration_type, todo_type, KnownInstanceType, Type, TypeVarBoundOrConstraints, - TypeVarInstance, TypeVarVariance, UnionType, + declaration_type, todo_type, type_ordering::union_or_intersection_elements_ordering, + KnownInstanceType, Type, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarVariance, + UnionType, }; use crate::{Db, FxOrderSet}; @@ -229,9 +232,15 @@ impl<'db> GenericContext<'db> { Specialization::new(db, self, expanded.into_boxed_slice()) } + + pub(crate) fn ordering(self, db: &'db dyn Db, other: Self) -> Ordering { + self.variables(db) + .cmp(other.variables(db)) + .then_with(|| self.origin(db).cmp(&other.origin(db))) + } } -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] pub enum GenericContextOrigin { LegacyBase(LegacyGenericBase), Inherited, @@ -256,7 +265,7 @@ impl std::fmt::Display for GenericContextOrigin { } } -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] pub enum LegacyGenericBase { Generic, Protocol, @@ -518,6 +527,20 @@ impl<'db> Specialization<'db> { ty.find_legacy_typevars(db, typevars); } } + + pub(crate) fn ordering(self, db: &'db dyn Db, other: Self) -> Ordering { + self.generic_context(db) + .ordering(db, other.generic_context(db)) + .then_with(|| { + self.types(db) + .iter() + .zip(other.types(db)) + .map(|(self_type, other_type)| { + union_or_intersection_elements_ordering(db, self_type, other_type) + }) + .fold(Ordering::Equal, Ordering::then) + }) + } } /// A mapping between type variables and types. diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index 99312849da..6c60b4902f 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -1,5 +1,6 @@ //! Instance types: both nominal and structural. +use std::cmp::Ordering; use std::marker::PhantomData; use super::protocol_class::ProtocolInterface; @@ -157,7 +158,7 @@ impl<'db> From> for Type<'db> { /// A `ProtocolInstanceType` represents the set of all possible runtime objects /// that conform to the interface described by a certain protocol. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, salsa::Update)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update)] pub struct ProtocolInstanceType<'db> { pub(super) inner: Protocol<'db>, @@ -330,11 +331,15 @@ impl<'db> ProtocolInstanceType<'db> { } } } + + pub(super) fn ordering(self, db: &'db dyn Db, other: Self) -> Ordering { + self.inner.ordering(db, other.inner) + } } /// An enumeration of the two kinds of protocol types: those that originate from a class /// definition in source code, and those that are synthesized from a set of members. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update, PartialOrd, Ord)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update)] pub(super) enum Protocol<'db> { FromClass(ClassType<'db>), Synthesized(SynthesizedProtocolType<'db>), @@ -353,9 +358,20 @@ impl<'db> Protocol<'db> { Self::Synthesized(synthesized) => synthesized.interface(), } } + + pub(super) fn ordering(self, db: &'db dyn Db, other: Self) -> Ordering { + match (self, other) { + (Self::FromClass(_), Self::Synthesized(_)) => Ordering::Greater, + (Self::Synthesized(_), Self::FromClass(_)) => Ordering::Less, + (Self::FromClass(this), Self::FromClass(other)) => this.ordering(db, other), + (Self::Synthesized(this), Self::Synthesized(other)) => this.ordering(db, other), + } + } } mod synthesized_protocol { + use std::cmp::Ordering; + use crate::types::generics::TypeMapping; use crate::types::protocol_class::ProtocolInterface; use crate::types::TypeVarInstance; @@ -370,7 +386,7 @@ mod synthesized_protocol { /// /// The constructor method of this type maintains the invariant that a synthesized protocol type /// is always constructed from a *normalized* protocol interface. - #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update, PartialOrd, Ord)] + #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update)] pub(in crate::types) struct SynthesizedProtocolType<'db>(ProtocolInterface<'db>); impl<'db> SynthesizedProtocolType<'db> { @@ -397,5 +413,9 @@ mod synthesized_protocol { pub(in crate::types) fn interface(self) -> ProtocolInterface<'db> { self.0 } + + pub(super) fn ordering(self, db: &'db dyn Db, other: Self) -> Ordering { + self.0.ordering(db, other.0) + } } } diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index c29be0bd3e..e7d549b007 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, ops::Deref}; +use std::{cmp::Ordering, collections::BTreeMap, ops::Deref}; use itertools::{Either, Itertools}; @@ -65,7 +65,7 @@ pub(super) struct ProtocolInterfaceMembers<'db> { } /// The interface of a protocol: the members of that protocol, and the types of those members. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update, PartialOrd, Ord)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update)] pub(super) enum ProtocolInterface<'db> { Members(ProtocolInterfaceMembers<'db>), SelfReference, @@ -205,6 +205,17 @@ impl<'db> ProtocolInterface<'db> { Self::SelfReference => {} } } + + pub(super) fn ordering(self, db: &'db dyn Db, other: Self) -> Ordering { + match (self, other) { + (Self::Members(this), Self::Members(other)) => { + this.inner(db).keys().cmp(other.inner(db).keys()) + } + (Self::SelfReference, Self::Members(_)) => Ordering::Less, + (Self::Members(_), Self::SelfReference) => Ordering::Greater, + (Self::SelfReference, Self::SelfReference) => Ordering::Equal, + } + } } #[derive(Debug, PartialEq, Eq, Clone, Hash, salsa::Update)] diff --git a/crates/ty_python_semantic/src/types/type_ordering.rs b/crates/ty_python_semantic/src/types/type_ordering.rs index 52dd6ee28a..0da0558fd3 100644 --- a/crates/ty_python_semantic/src/types/type_ordering.rs +++ b/crates/ty_python_semantic/src/types/type_ordering.rs @@ -56,23 +56,32 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (Type::IntLiteral(_), _) => Ordering::Less, (_, Type::IntLiteral(_)) => Ordering::Greater, - (Type::StringLiteral(left), Type::StringLiteral(right)) => left.cmp(right), + (Type::StringLiteral(left), Type::StringLiteral(right)) => left.ordering(db, *right), (Type::StringLiteral(_), _) => Ordering::Less, (_, Type::StringLiteral(_)) => Ordering::Greater, - (Type::BytesLiteral(left), Type::BytesLiteral(right)) => left.cmp(right), + (Type::BytesLiteral(left), Type::BytesLiteral(right)) => left.ordering(db, *right), (Type::BytesLiteral(_), _) => Ordering::Less, (_, Type::BytesLiteral(_)) => Ordering::Greater, - (Type::FunctionLiteral(left), Type::FunctionLiteral(right)) => left.cmp(right), + (Type::FunctionLiteral(left), Type::FunctionLiteral(right)) => { + // TODO: This compares by salsa ID, which can lead to non-deterministic ordering + left.cmp(right) + } (Type::FunctionLiteral(_), _) => Ordering::Less, (_, Type::FunctionLiteral(_)) => Ordering::Greater, - (Type::BoundMethod(left), Type::BoundMethod(right)) => left.cmp(right), + (Type::BoundMethod(left), Type::BoundMethod(right)) => { + // TODO: This compares by salsa ID, which can lead to non-deterministic ordering + left.cmp(right) + } (Type::BoundMethod(_), _) => Ordering::Less, (_, Type::BoundMethod(_)) => Ordering::Greater, - (Type::MethodWrapper(left), Type::MethodWrapper(right)) => left.cmp(right), + (Type::MethodWrapper(left), Type::MethodWrapper(right)) => { + // TODO: This compares by salsa ID, which can lead to non-deterministic ordering + left.cmp(right) + } (Type::MethodWrapper(_), _) => Ordering::Less, (_, Type::MethodWrapper(_)) => Ordering::Greater, @@ -92,29 +101,40 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (Type::DataclassTransformer(_), _) => Ordering::Less, (_, Type::DataclassTransformer(_)) => Ordering::Greater, - (Type::Callable(left), Type::Callable(right)) => left.cmp(right), + (Type::Callable(left), Type::Callable(right)) => { + // TODO: This compares by salsa ID, which can lead to non-deterministic ordering + left.cmp(right) + } (Type::Callable(_), _) => Ordering::Less, (_, Type::Callable(_)) => Ordering::Greater, - (Type::Tuple(left), Type::Tuple(right)) => left.cmp(right), + (Type::Tuple(left), Type::Tuple(right)) => { + // TODO: This compares by salsa ID, which can lead to non-deterministic ordering + left.cmp(right) + } (Type::Tuple(_), _) => Ordering::Less, (_, Type::Tuple(_)) => Ordering::Greater, - (Type::ModuleLiteral(left), Type::ModuleLiteral(right)) => left.cmp(right), + (Type::ModuleLiteral(left), Type::ModuleLiteral(right)) => { + // TODO: This compares by salsa ID, which can lead to non-deterministic ordering + left.cmp(right) + } (Type::ModuleLiteral(_), _) => Ordering::Less, (_, Type::ModuleLiteral(_)) => Ordering::Greater, - (Type::ClassLiteral(left), Type::ClassLiteral(right)) => left.cmp(right), + (Type::ClassLiteral(left), Type::ClassLiteral(right)) => left.ordering(db, *right), (Type::ClassLiteral(_), _) => Ordering::Less, (_, Type::ClassLiteral(_)) => Ordering::Greater, - (Type::GenericAlias(left), Type::GenericAlias(right)) => left.cmp(right), + (Type::GenericAlias(left), Type::GenericAlias(right)) => left.ordering(db, *right), (Type::GenericAlias(_), _) => Ordering::Less, (_, Type::GenericAlias(_)) => Ordering::Greater, (Type::SubclassOf(left), Type::SubclassOf(right)) => { match (left.subclass_of(), right.subclass_of()) { - (SubclassOfInner::Class(left), SubclassOfInner::Class(right)) => left.cmp(&right), + (SubclassOfInner::Class(left), SubclassOfInner::Class(right)) => { + left.ordering(db, right) + } (SubclassOfInner::Class(_), _) => Ordering::Less, (_, SubclassOfInner::Class(_)) => Ordering::Greater, (SubclassOfInner::Dynamic(left), SubclassOfInner::Dynamic(right)) => { @@ -126,17 +146,22 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (Type::SubclassOf(_), _) => Ordering::Less, (_, Type::SubclassOf(_)) => Ordering::Greater, - (Type::NominalInstance(left), Type::NominalInstance(right)) => left.class.cmp(&right.class), + (Type::NominalInstance(left), Type::NominalInstance(right)) => { + left.class.ordering(db, right.class) + } (Type::NominalInstance(_), _) => Ordering::Less, (_, Type::NominalInstance(_)) => Ordering::Greater, (Type::ProtocolInstance(left_proto), Type::ProtocolInstance(right_proto)) => { - left_proto.cmp(right_proto) + left_proto.ordering(db, *right_proto) } (Type::ProtocolInstance(_), _) => Ordering::Less, (_, Type::ProtocolInstance(_)) => Ordering::Greater, - (Type::TypeVar(left), Type::TypeVar(right)) => left.cmp(right), + (Type::TypeVar(left), Type::TypeVar(right)) => { + // TODO: This compares by salsa ID, which can lead to non-deterministic ordering + left.cmp(right) + } (Type::TypeVar(_), _) => Ordering::Less, (_, Type::TypeVar(_)) => Ordering::Greater, @@ -148,15 +173,21 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (Type::BoundSuper(left), Type::BoundSuper(right)) => { (match (left.pivot_class(db), right.pivot_class(db)) { - (ClassBase::Class(left), ClassBase::Class(right)) => left.cmp(&right), + (ClassBase::Class(left), ClassBase::Class(right)) => left.ordering(db, right), (ClassBase::Class(_), _) => Ordering::Less, (_, ClassBase::Class(_)) => Ordering::Greater, - (ClassBase::Protocol(left), ClassBase::Protocol(right)) => left.cmp(&right), + (ClassBase::Protocol(left), ClassBase::Protocol(right)) => { + // TODO: This compares by salsa ID, which can lead to non-deterministic ordering + left.cmp(&right) + } (ClassBase::Protocol(_), _) => Ordering::Less, (_, ClassBase::Protocol(_)) => Ordering::Greater, - (ClassBase::Generic(left), ClassBase::Generic(right)) => left.cmp(&right), + (ClassBase::Generic(left), ClassBase::Generic(right)) => { + // TODO: This compares by salsa ID, which can lead to non-deterministic ordering + left.cmp(&right) + } (ClassBase::Generic(_), _) => Ordering::Less, (_, ClassBase::Generic(_)) => Ordering::Greater, @@ -165,11 +196,13 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( } }) .then_with(|| match (left.owner(db), right.owner(db)) { - (SuperOwnerKind::Class(left), SuperOwnerKind::Class(right)) => left.cmp(&right), + (SuperOwnerKind::Class(left), SuperOwnerKind::Class(right)) => { + left.ordering(db, right) + } (SuperOwnerKind::Class(_), _) => Ordering::Less, (_, SuperOwnerKind::Class(_)) => Ordering::Greater, (SuperOwnerKind::Instance(left), SuperOwnerKind::Instance(right)) => { - left.class.cmp(&right.class) + left.class.ordering(db, right.class) } (SuperOwnerKind::Instance(_), _) => Ordering::Less, (_, SuperOwnerKind::Instance(_)) => Ordering::Greater, @@ -247,12 +280,14 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (_, KnownInstanceType::OrderedDict) => Ordering::Greater, (KnownInstanceType::Generic(left), KnownInstanceType::Generic(right)) => { + // TODO: This compares by salsa ID, which can lead to non-deterministic ordering left.cmp(right) } (KnownInstanceType::Generic(_), _) => Ordering::Less, (_, KnownInstanceType::Generic(_)) => Ordering::Greater, (KnownInstanceType::Protocol(left), KnownInstanceType::Protocol(right)) => { + // TODO: This compares by salsa ID, which can lead to non-deterministic ordering left.cmp(right) } (KnownInstanceType::Protocol(_), _) => Ordering::Less, @@ -312,11 +347,15 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( ( KnownInstanceType::TypeAliasType(left), KnownInstanceType::TypeAliasType(right), - ) => left.cmp(right), + ) => { + // TODO: This compares by salsa ID, which can lead to non-deterministic ordering + left.cmp(right) + } (KnownInstanceType::TypeAliasType(_), _) => Ordering::Less, (_, KnownInstanceType::TypeAliasType(_)) => Ordering::Greater, (KnownInstanceType::TypeVar(left), KnownInstanceType::TypeVar(right)) => { + // TODO: This compares by salsa ID, which can lead to non-deterministic ordering left.cmp(right) } } @@ -325,7 +364,10 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (Type::KnownInstance(_), _) => Ordering::Less, (_, Type::KnownInstance(_)) => Ordering::Greater, - (Type::PropertyInstance(left), Type::PropertyInstance(right)) => left.cmp(right), + (Type::PropertyInstance(left), Type::PropertyInstance(right)) => { + // TODO: This compares by salsa ID, which can lead to non-deterministic ordering + left.cmp(right) + } (Type::PropertyInstance(_), _) => Ordering::Less, (_, Type::PropertyInstance(_)) => Ordering::Greater,