[ty] Deterministic ordering of types

This commit is contained in:
David Peter 2025-05-14 11:54:38 +02:00
parent 2a217e80ca
commit 3a76557be2
6 changed files with 163 additions and 33 deletions

View file

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

View file

@ -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<GenericAlias<'db>> for Type<'db> {
@ -197,9 +207,7 @@ impl<'db> From<GenericAlias<'db>> 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<GenericAlias<'db>> for ClassType<'db> {
@ -506,6 +523,14 @@ pub struct ClassLiteral<'db> {
pub(crate) dataclass_transformer_params: Option<DataclassTransformerParams>,
}
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,

View file

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

View file

@ -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<NominalInstanceType<'db>> 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)
}
}
}

View file

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

View file

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