add `structural_type_ordering`

This commit is contained in:
Shunsuke Shibayama 2025-12-01 19:12:57 +09:00
parent b8d3feed53
commit ffffe97b23
22 changed files with 1006 additions and 57 deletions

View File

@ -492,6 +492,10 @@ impl File {
.map_or(PySourceType::Python, PySourceType::from_extension),
}
}
pub fn structural_ordering(self, db: &dyn Db, other: Self) -> std::cmp::Ordering {
self.path(db).cmp(other.path(db))
}
}
impl fmt::Debug for File {

View File

@ -11,7 +11,7 @@ use std::fmt::{Display, Formatter};
/// * a file stored on the [host system](crate::system::System).
/// * a virtual file stored on the [host system](crate::system::System).
/// * a vendored file stored in the [vendored file system](crate::vendored::VendoredFileSystem).
#[derive(Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, get_size2::GetSize)]
pub enum FilePath {
/// Path to a file on the [host system](crate::system::System).
System(SystemPathBuf),

View File

@ -88,7 +88,7 @@ impl ToOwned for VendoredPath {
}
#[repr(transparent)]
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
#[derive(Debug, Eq, PartialEq, Clone, Hash, PartialOrd, Ord)]
pub struct VendoredPathBuf(Utf8PathBuf);
impl get_size2::GetSize for VendoredPathBuf {

View File

@ -2410,7 +2410,7 @@ class C3:
self.x = [self.x[0].flip()]
# TODO: should be `list[Unknown | Sub] | list[Unknown | Base] | Unknown`
reveal_type(C3(Sub()).x) # revealed: list[Unknown | Sub] | list[Divergent] | Unknown
reveal_type(C3(Sub()).x) # revealed: list[Divergent] | list[Unknown | Sub] | Unknown
```
And cycles between many attributes:
@ -2469,7 +2469,7 @@ class ManyCycles2:
def f1(self: "ManyCycles2"):
# TODO: should be list[Unknown | int] | list[Divergent] | Unknown
reveal_type(self.x3) # revealed: list[Unknown | int] | list[Divergent] | list[Divergent] | Unknown
reveal_type(self.x3) # revealed: list[Divergent] | list[Divergent] | list[Unknown | int] | Unknown
self.x1 = [self.x2] + [self.x3]
self.x2 = [self.x1] + [self.x3]

View File

@ -248,9 +248,9 @@ IntOrStr = TypeAliasType(get_name(), int | str)
type OptNestedInt = int | tuple[OptNestedInt, ...] | None
def f(x: OptNestedInt) -> None:
reveal_type(x) # revealed: int | None | tuple[OptNestedInt, ...]
reveal_type(x) # revealed: tuple[OptNestedInt, ...] | None | int
if x is not None:
reveal_type(x) # revealed: int | tuple[OptNestedInt, ...]
reveal_type(x) # revealed: tuple[OptNestedInt, ...] | int
```
### Invalid self-referential
@ -344,12 +344,12 @@ def f(x: A):
reveal_type(y) # revealed: tuple[A]
def g(x: A | B):
reveal_type(x) # revealed: None | tuple[B]
reveal_type(x) # revealed: tuple[B] | None
from ty_extensions import Intersection
def h(x: Intersection[A, B]):
reveal_type(x) # revealed: None | tuple[B]
reveal_type(x) # revealed: tuple[B] | None
```
### Self-recursive callable type

View File

@ -102,6 +102,17 @@ impl<'db> Module<'db> {
.as_deref()
.unwrap_or_default()
}
pub fn structural_ordering(self, db: &'db dyn Db, other: Self) -> std::cmp::Ordering {
match (self, other) {
(Module::File(left), Module::File(right)) => left.structural_ordering(db, right),
(Module::Namespace(left), Module::Namespace(right)) => {
left.name(db).cmp(right.name(db))
}
(Module::File(_), Module::Namespace(_)) => std::cmp::Ordering::Less,
(Module::Namespace(_), Module::File(_)) => std::cmp::Ordering::Greater,
}
}
}
impl std::fmt::Debug for Module<'_> {
@ -274,6 +285,14 @@ pub struct FileModule<'db> {
pub(super) known: Option<KnownModule>,
}
impl FileModule<'_> {
pub fn structural_ordering(self, db: &dyn Db, other: Self) -> std::cmp::Ordering {
self.name(db)
.cmp(other.name(db))
.then_with(|| self.file(db).structural_ordering(db, other.file(db)))
}
}
/// A namespace package.
///
/// Namespace packages are special because there are

View File

@ -122,6 +122,17 @@ impl<'db> Definition<'db> {
_ => None,
}
}
pub(crate) fn structural_ordering(
self,
db: &'db dyn Db,
other: Definition<'db>,
) -> std::cmp::Ordering {
self.file(db)
.cmp(&other.file(db))
.then_with(|| self.file_scope(db).cmp(&other.file_scope(db)))
.then_with(|| self.place(db).cmp(&other.place(db)))
}
}
/// Get the module-level docstring for the given file

View File

@ -347,7 +347,7 @@ impl Hash for MemberExprRef<'_> {
/// Uniquely identifies a member in a scope.
#[newtype_index]
#[derive(get_size2::GetSize, salsa::Update)]
#[derive(PartialOrd, Ord, get_size2::GetSize, salsa::Update)]
pub struct ScopedMemberId;
/// The members of a scope. Allows lookup by member path and [`ScopedMemberId`].

View File

@ -133,7 +133,9 @@ impl std::fmt::Display for PlaceExprRef<'_> {
}
/// ID that uniquely identifies a place inside a [`Scope`](super::FileScopeId).
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, get_size2::GetSize, salsa::Update)]
#[derive(
Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord, get_size2::GetSize, salsa::Update,
)]
pub enum ScopedPlaceId {
Symbol(ScopedSymbolId),
Member(ScopedMemberId),

View File

@ -64,11 +64,21 @@ impl<'db> ScopeId<'db> {
NodeWithScopeKind::GeneratorExpression(_) => "<generator>",
}
}
pub(crate) fn structural_ordering(
self,
db: &'db dyn Db,
other: ScopeId<'db>,
) -> std::cmp::Ordering {
self.file(db)
.cmp(&other.file(db))
.then_with(|| self.file_scope_id(db).cmp(&other.file_scope_id(db)))
}
}
/// ID that uniquely identifies a scope inside of a module.
#[newtype_index]
#[derive(salsa::Update, get_size2::GetSize)]
#[derive(salsa::Update, get_size2::GetSize, PartialOrd, Ord)]
pub struct FileScopeId;
impl FileScopeId {

View File

@ -8,7 +8,7 @@ use std::ops::{Deref, DerefMut};
/// Uniquely identifies a symbol in a given scope.
#[newtype_index]
#[derive(get_size2::GetSize)]
#[derive(PartialOrd, Ord, get_size2::GetSize)]
pub struct ScopedSymbolId;
/// A symbol in a given scope.

View File

@ -19,7 +19,7 @@ use ruff_python_ast::name::Name;
use ruff_text_size::{Ranged, TextRange};
use smallvec::{SmallVec, smallvec};
use type_ordering::union_or_intersection_elements_ordering;
use type_ordering::{structural_type_ordering, union_or_intersection_elements_ordering};
pub(crate) use self::builder::{IntersectionBuilder, UnionBuilder};
pub use self::cyclic::CycleDetector;
@ -653,6 +653,26 @@ impl<'db> PropertyInstanceType<'db> {
getter_equivalence.and(db, setter_equivalence)
}
fn structural_ordering(self, db: &'db dyn Db, other: Self) -> std::cmp::Ordering {
let getter_ord = match (self.getter(db), other.getter(db)) {
(Some(left), Some(right)) => structural_type_ordering(db, &left, &right),
(Some(_), None) => std::cmp::Ordering::Greater,
(None, Some(_)) => std::cmp::Ordering::Less,
(None, None) => std::cmp::Ordering::Equal,
};
if getter_ord != std::cmp::Ordering::Equal {
return getter_ord;
}
match (self.setter(db), other.setter(db)) {
(Some(left), Some(right)) => structural_type_ordering(db, &left, &right),
(Some(_), None) => std::cmp::Ordering::Greater,
(None, Some(_)) => std::cmp::Ordering::Less,
(None, None) => std::cmp::Ordering::Equal,
}
}
}
bitflags! {
@ -756,6 +776,30 @@ impl<'db> DataclassParams<'db> {
params.field_specifiers(db),
)
}
pub(super) fn structural_ordering(self, db: &'db dyn Db, other: Self) -> std::cmp::Ordering {
let flag_ord = self.flags(db).bits().cmp(&other.flags(db).bits());
if flag_ord != std::cmp::Ordering::Equal {
return flag_ord;
}
let self_fields = self.field_specifiers(db);
let other_fields = other.field_specifiers(db);
let fields_count = self_fields.len().cmp(&other_fields.len());
if fields_count != std::cmp::Ordering::Equal {
return fields_count;
}
for (self_field, other_field) in self_fields.iter().zip(other_fields.iter()) {
let field_ord = structural_type_ordering(db, self_field, other_field);
if field_ord != std::cmp::Ordering::Equal {
return field_ord;
}
}
std::cmp::Ordering::Equal
}
}
/// Representation of a type: a set of possible values at runtime.
@ -8764,6 +8808,61 @@ impl<'db> KnownInstanceType<'db> {
fn repr(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db {
self.display_with(db, DisplaySettings::default())
}
fn structural_ordering(self, db: &'db dyn Db, other: Self) -> std::cmp::Ordering {
match (self, other) {
(Self::SubscriptedProtocol(left), Self::SubscriptedProtocol(right))
| (Self::SubscriptedGeneric(left), Self::SubscriptedGeneric(right))
| (Self::GenericContext(left), Self::GenericContext(right)) => {
left.structural_ordering(db, right)
}
(Self::TypeVar(left), Self::TypeVar(right)) => left
.identity(db)
.structural_ordering(db, right.identity(db)),
(Self::TypeAliasType(left), Self::TypeAliasType(right)) => {
left.structural_ordering(db, right)
}
(Self::Deprecated(left), Self::Deprecated(right)) => {
left.structural_ordering(db, right)
}
(Self::Field(left), Self::Field(right)) => left.structural_ordering(db, right),
// No need to compare structurally, they are used only in debugging contexts
(Self::ConstraintSet(left), Self::ConstraintSet(right)) => left.cmp(&right),
(Self::Specialization(left), Self::Specialization(right)) => {
left.structural_ordering(db, right)
}
(Self::UnionType(left), Self::UnionType(right)) => left.structural_ordering(db, right),
(Self::Literal(left), Self::Literal(right))
| (Self::Annotated(left), Self::Annotated(right))
| (Self::TypeGenericAlias(left), Self::TypeGenericAlias(right))
| (Self::LiteralStringAlias(left), Self::LiteralStringAlias(right)) => {
structural_type_ordering(db, &left.inner(db), &right.inner(db))
}
(Self::Callable(left), Self::Callable(right)) => left.structural_ordering(db, right),
(Self::NewType(left), Self::NewType(right)) => left.structural_ordering(db, right),
(left, right) => {
let index = |instance| match instance {
Self::SubscriptedProtocol(_) => 0,
Self::SubscriptedGeneric(_) => 1,
Self::TypeVar(_) => 2,
Self::TypeAliasType(_) => 3,
Self::Deprecated(_) => 4,
Self::Field(_) => 5,
Self::ConstraintSet(_) => 6,
Self::GenericContext(_) => 7,
Self::Specialization(_) => 8,
Self::UnionType(_) => 9,
Self::Literal(_) => 10,
Self::Annotated(_) => 11,
Self::TypeGenericAlias(_) => 12,
Self::Callable(_) => 13,
Self::LiteralStringAlias(_) => 14,
Self::NewType(_) => 15,
};
index(left).cmp(&index(right))
}
}
}
}
/// A type that is determined to be divergent during recursive type inference.
@ -8842,7 +8941,7 @@ impl std::fmt::Display for DynamicType {
bitflags! {
/// Type qualifiers that appear in an annotation expression.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, salsa::Update, Hash)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, salsa::Update, Hash, PartialOrd, Ord)]
pub(crate) struct TypeQualifiers: u8 {
/// `typing.ClassVar`
const CLASS_VAR = 1 << 0;
@ -9161,6 +9260,17 @@ pub struct DeprecatedInstance<'db> {
// The Salsa heap is tracked separately.
impl get_size2::GetSize for DeprecatedInstance<'_> {}
impl DeprecatedInstance<'_> {
fn structural_ordering(self, db: &dyn Db, other: DeprecatedInstance<'_>) -> std::cmp::Ordering {
match (self.message(db), other.message(db)) {
(Some(left), Some(right)) => left.structural_ordering(db, right),
(None, Some(_)) => std::cmp::Ordering::Less,
(Some(_), None) => std::cmp::Ordering::Greater,
(None, None) => std::cmp::Ordering::Equal,
}
}
}
/// Contains information about instances of `dataclasses.Field`, typically created using
/// `dataclasses.field()`.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
@ -9218,6 +9328,24 @@ impl<'db> FieldInstance<'db> {
self.alias(db),
))
}
fn structural_ordering(self, db: &dyn Db, other: FieldInstance<'_>) -> std::cmp::Ordering {
match (self.default_type(db), other.default_type(db)) {
(Some(left), Some(right)) => {
let ord = structural_type_ordering(db, &left, &right);
if ord != std::cmp::Ordering::Equal {
return ord;
}
}
(None, Some(_)) => return std::cmp::Ordering::Less,
(Some(_), None) => return std::cmp::Ordering::Greater,
(None, None) => {}
}
self.init(db)
.cmp(&other.init(db))
.then_with(|| self.kw_only(db).cmp(&other.kw_only(db)))
.then_with(|| self.alias(db).cmp(&other.alias(db)))
}
}
/// Whether this typevar was created via the legacy `TypeVar` constructor, using PEP 695 syntax,
@ -9271,6 +9399,19 @@ pub struct TypeVarIdentity<'db> {
impl get_size2::GetSize for TypeVarIdentity<'_> {}
impl TypeVarIdentity<'_> {
fn structural_ordering(self, db: &dyn Db, other: TypeVarIdentity<'_>) -> std::cmp::Ordering {
self.name(db).cmp(other.name(db)).then_with(|| {
match (self.definition(db), other.definition(db)) {
(Some(left), Some(right)) => left.structural_ordering(db, right),
(None, Some(_)) => std::cmp::Ordering::Less,
(Some(_), None) => std::cmp::Ordering::Greater,
(None, None) => std::cmp::Ordering::Equal,
}
})
}
}
/// A specific instance of a type variable that has not been bound to a generic context yet.
///
/// This is usually not the type that you want; if you are working with a typevar, in a generic
@ -9857,6 +9998,17 @@ impl<'db> BoundTypeVarInstance<'db> {
}
}
}
fn structural_ordering(self, db: &'db dyn Db, other: Self) -> std::cmp::Ordering {
self.typevar(db)
.identity(db)
.structural_ordering(db, other.typevar(db).identity(db))
.then_with(|| {
self.binding_context(db)
.definition()
.cmp(&other.binding_context(db).definition())
})
}
}
fn walk_bound_type_var_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
@ -10184,6 +10336,27 @@ impl<'db> UnionTypeInstance<'db> {
Some(Self::new(db, value_expr_types, union_type))
}
fn structural_ordering(self, db: &'db dyn Db, other: Self) -> std::cmp::Ordering {
match (self._value_expr_types(db), other._value_expr_types(db)) {
(Some(left_tys), Some(right_tys)) => {
let len_count = left_tys.len().cmp(&right_tys.len());
if len_count != std::cmp::Ordering::Equal {
return len_count;
}
for (left, right) in left_tys.iter().zip(right_tys.iter()) {
let ord = structural_type_ordering(db, left, right);
if ord != std::cmp::Ordering::Equal {
return ord;
}
}
std::cmp::Ordering::Equal
}
(Some(_), None) => std::cmp::Ordering::Less,
(None, Some(_)) => std::cmp::Ordering::Greater,
(None, None) => std::cmp::Ordering::Equal,
}
}
}
/// A salsa-interned `Type`
@ -11589,6 +11762,14 @@ impl<'db> BoundMethodType<'db> {
)
})
}
pub(super) fn structural_ordering(self, db: &'db dyn Db, other: Self) -> std::cmp::Ordering {
self.function(db)
.structural_ordering(db, other.function(db))
.then_with(|| {
structural_type_ordering(db, &self.self_instance(db), &other.self_instance(db))
})
}
}
/// This type represents the set of all callable objects with a certain, possibly overloaded,
@ -11777,6 +11958,15 @@ impl<'db> CallableType<'db> {
.is_equivalent_to_impl(db, other.signatures(db), inferable, visitor)
})
}
pub(super) fn structural_ordering(self, db: &'db dyn Db, other: Self) -> std::cmp::Ordering {
self.is_function_like(db)
.cmp(&other.is_function_like(db))
.then_with(|| {
self.signatures(db)
.structural_ordering(db, other.signatures(db))
})
}
}
/// Converting a type "into a callable" can possibly return a _union_ of callables. Eventually,
@ -12391,6 +12581,48 @@ impl<'db> KnownBoundMethodType<'db> {
}
}
}
pub(super) fn structural_ordering(self, db: &'db dyn Db, other: Self) -> std::cmp::Ordering {
match (self, other) {
(
KnownBoundMethodType::FunctionTypeDunderGet(self_function),
KnownBoundMethodType::FunctionTypeDunderGet(other_function),
) => self_function.structural_ordering(db, other_function),
(
KnownBoundMethodType::FunctionTypeDunderCall(self_function),
KnownBoundMethodType::FunctionTypeDunderCall(other_function),
) => self_function.structural_ordering(db, other_function),
(
KnownBoundMethodType::PropertyDunderGet(self_property),
KnownBoundMethodType::PropertyDunderGet(other_property),
) => self_property.structural_ordering(db, other_property),
(
KnownBoundMethodType::PropertyDunderSet(self_property),
KnownBoundMethodType::PropertyDunderSet(other_property),
) => self_property.structural_ordering(db, other_property),
(left, right) => {
let index = |known| match known {
KnownBoundMethodType::FunctionTypeDunderGet(_) => 0,
KnownBoundMethodType::FunctionTypeDunderCall(_) => 1,
KnownBoundMethodType::PropertyDunderGet(_) => 2,
KnownBoundMethodType::PropertyDunderSet(_) => 3,
KnownBoundMethodType::StrStartswith(_) => 4,
KnownBoundMethodType::ConstraintSetRange => 5,
KnownBoundMethodType::ConstraintSetAlways => 6,
KnownBoundMethodType::ConstraintSetNever => 7,
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) => 8,
KnownBoundMethodType::ConstraintSetSatisfies(_) => 9,
KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_) => 10,
KnownBoundMethodType::GenericContextSpecializeConstrained(_) => 11,
};
index(left).cmp(&index(right))
}
}
}
}
/// Represents a specific instance of `types.WrapperDescriptorType`
@ -12646,6 +12878,14 @@ impl<'db> ModuleLiteralType<'db> {
place_and_qualifiers
}
pub(super) fn structural_ordering(
self,
db: &'db dyn Db,
other: ModuleLiteralType<'db>,
) -> std::cmp::Ordering {
self.module(db).structural_ordering(db, other.module(db))
}
}
/// # Ordering
@ -12759,6 +12999,17 @@ impl<'db> PEP695TypeAliasType<'db> {
fn normalized_impl(self, _db: &'db dyn Db, _visitor: &NormalizedVisitor<'db>) -> Self {
self
}
fn structural_ordering(
self,
db: &'db dyn Db,
other: PEP695TypeAliasType<'db>,
) -> std::cmp::Ordering {
self.name(db).cmp(other.name(db)).then_with(|| {
self.rhs_scope(db)
.structural_ordering(db, other.rhs_scope(db))
})
}
}
fn generic_context_cycle_initial<'db>(
@ -12832,6 +13083,21 @@ impl<'db> ManualPEP695TypeAliasType<'db> {
.recursive_type_normalized_impl(db, div, true)?,
))
}
fn structural_ordering(
self,
db: &'db dyn Db,
other: ManualPEP695TypeAliasType<'db>,
) -> std::cmp::Ordering {
self.name(db).cmp(other.name(db)).then_with(|| {
match (self.definition(db), other.definition(db)) {
(Some(left), Some(right)) => left.structural_ordering(db, right),
(None, None) => std::cmp::Ordering::Equal,
(Some(_), None) => std::cmp::Ordering::Greater,
(None, Some(_)) => std::cmp::Ordering::Less,
}
})
}
}
#[derive(
@ -12952,6 +13218,21 @@ impl<'db> TypeAliasType<'db> {
TypeAliasType::ManualPEP695(_) => self,
}
}
fn structural_ordering(self, db: &'db dyn Db, other: TypeAliasType<'db>) -> std::cmp::Ordering {
match (self, other) {
(TypeAliasType::PEP695(left), TypeAliasType::PEP695(right)) => {
left.structural_ordering(db, right)
}
(TypeAliasType::ManualPEP695(left), TypeAliasType::ManualPEP695(right)) => {
left.structural_ordering(db, right)
}
(TypeAliasType::PEP695(_), TypeAliasType::ManualPEP695(_)) => std::cmp::Ordering::Less,
(TypeAliasType::ManualPEP695(_), TypeAliasType::PEP695(_)) => {
std::cmp::Ordering::Greater
}
}
}
}
/// Either the explicit `metaclass=` keyword of the class, or the inferred metaclass of one of its base classes.
@ -13356,8 +13637,7 @@ impl<'db> IntersectionType<'db> {
.map(|ty| ty.normalized_impl(db, visitor))
.collect();
elements
.sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r, false));
elements.sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r));
elements
}
@ -13599,6 +13879,14 @@ impl<'db> StringLiteralType<'db> {
pub(crate) fn python_len(self, db: &'db dyn Db) -> usize {
self.value(db).chars().count()
}
fn structural_ordering(
self,
db: &'db dyn Db,
other: StringLiteralType<'db>,
) -> std::cmp::Ordering {
self.value(db).cmp(other.value(db))
}
}
/// # Ordering
@ -13618,6 +13906,14 @@ impl<'db> BytesLiteralType<'db> {
pub(crate) fn python_len(self, db: &'db dyn Db) -> usize {
self.value(db).len()
}
fn structural_ordering(
self,
db: &'db dyn Db,
other: BytesLiteralType<'db>,
) -> std::cmp::Ordering {
self.value(db).cmp(other.value(db))
}
}
/// A singleton type corresponding to a specific enum member.
@ -13646,6 +13942,17 @@ impl<'db> EnumLiteralType<'db> {
pub(crate) fn enum_class_instance(self, db: &'db dyn Db) -> Type<'db> {
self.enum_class(db).to_non_generic_instance(db)
}
fn structural_ordering(
self,
db: &'db dyn Db,
other: EnumLiteralType<'db>,
) -> std::cmp::Ordering {
self.name(db).cmp(other.name(db)).then_with(|| {
self.enum_class(db)
.structural_ordering(db, other.enum_class(db))
})
}
}
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
@ -13706,6 +14013,23 @@ impl<'db> TypeIsType<'db> {
pub(crate) fn is_bound(self, db: &'db dyn Db) -> bool {
self.place_info(db).is_some()
}
pub(super) fn structural_ordering(
self,
db: &'db dyn Db,
other: TypeIsType<'db>,
) -> std::cmp::Ordering {
structural_type_ordering(db, &self.return_type(db), &other.return_type(db)).then_with(
|| match (self.place_info(db), other.place_info(db)) {
(Some((left_scope, left_place)), Some((right_scope, right_place))) => left_scope
.structural_ordering(db, right_scope)
.then_with(|| left_place.cmp(&right_place)),
(None, Some(_)) => std::cmp::Ordering::Less,
(Some(_), None) => std::cmp::Ordering::Greater,
(None, None) => std::cmp::Ordering::Equal,
},
)
}
}
impl<'db> VarianceInferable<'db> for TypeIsType<'db> {

View File

@ -41,7 +41,7 @@ use crate::types::enums::{enum_member_literals, enum_metadata};
use crate::types::type_ordering::union_or_intersection_elements_ordering;
use crate::types::{
BytesLiteralType, IntersectionType, KnownClass, StringLiteralType, Type,
TypeVarBoundOrConstraints, UnionType,
TypeVarBoundOrConstraints, UnionType, structural_type_ordering,
};
use crate::{Db, FxOrderSet};
use rustc_hash::FxHashSet;
@ -613,7 +613,11 @@ impl<'db> UnionBuilder<'db> {
}
if self.order_elements {
types.sort_unstable_by(|l, r| {
union_or_intersection_elements_ordering(self.db, l, r, self.cycle_recovery)
if self.cycle_recovery && self.recursively_defined.is_yes() {
structural_type_ordering(self.db, l, r)
} else {
union_or_intersection_elements_ordering(self.db, l, r)
}
});
}
match types.len() {

View File

@ -332,6 +332,19 @@ impl<'db> GenericAlias<'db> {
pub(super) fn is_typed_dict(self, db: &'db dyn Db) -> bool {
self.origin(db).is_typed_dict(db)
}
pub(super) fn structural_ordering(
self,
db: &'db dyn Db,
other: GenericAlias<'db>,
) -> std::cmp::Ordering {
self.origin(db)
.structural_ordering(db, other.origin(db))
.then_with(|| {
self.specialization(db)
.structural_ordering(db, other.specialization(db))
})
}
}
impl<'db> From<GenericAlias<'db>> for Type<'db> {
@ -1278,6 +1291,19 @@ impl<'db> ClassType<'db> {
pub(super) fn header_span(self, db: &'db dyn Db) -> Span {
self.class_literal(db).0.header_span(db)
}
pub(super) fn structural_ordering(self, db: &'db dyn Db, other: Self) -> std::cmp::Ordering {
match (self, other) {
(ClassType::NonGeneric(left), ClassType::NonGeneric(right)) => {
left.structural_ordering(db, right)
}
(ClassType::Generic(left), ClassType::Generic(right)) => {
left.structural_ordering(db, right)
}
(ClassType::NonGeneric(_), ClassType::Generic(_)) => std::cmp::Ordering::Less,
(ClassType::Generic(_), ClassType::NonGeneric(_)) => std::cmp::Ordering::Greater,
}
}
}
fn into_callable_cycle_initial<'db>(
@ -3769,6 +3795,20 @@ impl<'db> ClassLiteral<'db> {
pub(super) fn qualified_name(self, db: &'db dyn Db) -> QualifiedClassName<'db> {
QualifiedClassName { db, class: self }
}
pub(super) fn structural_ordering(
self,
db: &'db dyn Db,
other: ClassLiteral<'db>,
) -> std::cmp::Ordering {
self.name(db)
.cmp(other.name(db))
.then_with(|| self.known(db).cmp(&other.known(db)))
.then_with(|| {
self.body_scope(db)
.structural_ordering(db, other.body_scope(db))
})
}
}
impl<'db> From<ClassLiteral<'db>> for Type<'db> {
@ -4050,7 +4090,7 @@ pub(super) enum DisjointBaseKind {
/// Feel free to expand this enum if you ever find yourself using the same class in multiple
/// places.
/// Note: good candidates are any classes in `[crate::module_resolver::module::KnownModule]`
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, get_size2::GetSize)]
#[cfg_attr(test, derive(strum_macros::EnumIter))]
pub enum KnownClass {
// To figure out where an stdlib symbol is defined, you can go into `crates/ty_vendored`

View File

@ -1129,6 +1129,21 @@ impl<'db> FunctionType<'db> {
updated_last_definition_signature,
))
}
pub(super) fn structural_ordering(
self,
db: &'db dyn Db,
other: FunctionType<'db>,
) -> std::cmp::Ordering {
self.name(db).cmp(other.name(db)).then_with(|| {
match (self.updated_signature(db), other.updated_signature(db)) {
(Some(left_sig), Some(right_sig)) => left_sig.structural_ordering(db, right_sig),
(None, None) => std::cmp::Ordering::Equal,
(Some(_), None) => std::cmp::Ordering::Greater,
(None, Some(_)) => std::cmp::Ordering::Less,
}
})
}
}
/// Evaluate an `isinstance` call. Return `Truthiness::AlwaysTrue` if we can definitely infer that

View File

@ -21,7 +21,8 @@ use crate::types::{
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor,
KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, Type, TypeContext,
TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarIdentity, TypeVarInstance,
TypeVarKind, TypeVarVariance, UnionType, declaration_type, walk_bound_type_var_type,
TypeVarKind, TypeVarVariance, UnionType, declaration_type, structural_type_ordering,
walk_bound_type_var_type,
};
use crate::{Db, FxOrderMap, FxOrderSet};
@ -602,6 +603,26 @@ impl<'db> GenericContext<'db> {
) -> usize {
ruff_memory_usage::order_map_heap_size(variables)
}
pub(super) fn structural_ordering(self, db: &'db dyn Db, other: Self) -> std::cmp::Ordering {
let left = self.variables_inner(db);
let right = other.variables_inner(db);
let variables_count = left.len().cmp(&right.len());
if variables_count != std::cmp::Ordering::Equal {
return variables_count;
}
for (left_key, left_value) in left {
if let Some(right_value) = right.get(left_key) {
let ord = left_value.structural_ordering(db, *right_value);
if ord != std::cmp::Ordering::Equal {
return ord;
}
} else {
return std::cmp::Ordering::Greater;
}
}
std::cmp::Ordering::Equal
}
}
fn inferable_typevars_cycle_initial<'db>(
@ -1282,6 +1303,20 @@ impl<'db> Specialization<'db> {
// A tuple's specialization will include all of its element types, so we don't need to also
// look in `self.tuple`.
}
pub(super) fn structural_ordering(self, db: &'db dyn Db, other: Self) -> std::cmp::Ordering {
let types_count = self.types(db).len().cmp(&other.types(db).len());
if types_count != std::cmp::Ordering::Equal {
return types_count;
}
for (left, right) in self.types(db).iter().zip(other.types(db)) {
let ord = structural_type_ordering(db, left, right);
if ord != std::cmp::Ordering::Equal {
return ord;
}
}
std::cmp::Ordering::Equal
}
}
/// A mapping between type variables and types.

View File

@ -547,6 +547,25 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::Object => {}
}
}
pub(super) fn structural_ordering(self, db: &'db dyn Db, other: Self) -> std::cmp::Ordering {
match (self.0, other.0) {
(NominalInstanceInner::Object, NominalInstanceInner::Object) => {
std::cmp::Ordering::Equal
}
(NominalInstanceInner::Object, _) => std::cmp::Ordering::Less,
(_, NominalInstanceInner::Object) => std::cmp::Ordering::Greater,
(
NominalInstanceInner::ExactTuple(tuple1),
NominalInstanceInner::ExactTuple(tuple2),
) => tuple1.structural_ordering(db, tuple2),
(NominalInstanceInner::ExactTuple(_), _) => std::cmp::Ordering::Less,
(_, NominalInstanceInner::ExactTuple(_)) => std::cmp::Ordering::Greater,
(NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => {
class1.structural_ordering(db, class2)
}
}
}
}
impl<'db> From<NominalInstanceType<'db>> for Type<'db> {
@ -835,6 +854,19 @@ impl<'db> ProtocolInstanceType<'db> {
pub(super) fn interface(self, db: &'db dyn Db) -> ProtocolInterface<'db> {
self.inner.interface(db)
}
pub(super) fn structural_ordering(self, db: &'db dyn Db, other: Self) -> std::cmp::Ordering {
match (self.inner, other.inner) {
(Protocol::FromClass(left), Protocol::FromClass(right)) => {
left.structural_ordering(db, *right)
}
(Protocol::Synthesized(left), Protocol::Synthesized(right)) => {
left.structural_ordering(db, right)
}
(Protocol::FromClass(_), Protocol::Synthesized(_)) => std::cmp::Ordering::Less,
(Protocol::Synthesized(_), Protocol::FromClass(_)) => std::cmp::Ordering::Greater,
}
}
}
impl<'db> VarianceInferable<'db> for ProtocolInstanceType<'db> {
@ -963,6 +995,14 @@ mod synthesized_protocol {
self.0.recursive_type_normalized_impl(db, div, nested)?,
))
}
pub(in crate::types) fn structural_ordering(
self,
db: &'db dyn Db,
other: Self,
) -> std::cmp::Ordering {
self.0.structural_ordering(db, other.0)
}
}
impl<'db> VarianceInferable<'db> for SynthesizedProtocolType<'db> {

View File

@ -194,6 +194,13 @@ impl<'db> NewType<'db> {
self.try_map_base_class_type(db, |class_type| Some(f(class_type)))
.unwrap()
}
pub(super) fn structural_ordering(self, db: &'db dyn Db, other: Self) -> std::cmp::Ordering {
self.name(db).cmp(other.name(db)).then_with(|| {
self.definition(db)
.structural_ordering(db, other.definition(db))
})
}
}
pub(crate) fn walk_newtype_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(

View File

@ -6,7 +6,7 @@ use itertools::Itertools;
use ruff_python_ast::name::Name;
use rustc_hash::FxHashMap;
use crate::types::TypeContext;
use crate::types::{TypeContext, structural_type_ordering};
use crate::{
Db, FxOrderSet,
place::{Definedness, Place, PlaceAndQualifiers, place_from_bindings, place_from_declarations},
@ -461,6 +461,22 @@ impl<'db> ProtocolInterface<'db> {
interface: self,
}
}
pub(super) fn structural_ordering(self, db: &'db dyn Db, other: Self) -> std::cmp::Ordering {
let members_count = self.members(db).len().cmp(&other.members(db).len());
if members_count != std::cmp::Ordering::Equal {
return members_count;
}
for (left, right) in self.members(db).zip(other.members(db)) {
let ord = left.structural_ordering(db, &right);
if ord != std::cmp::Ordering::Equal {
return ord;
}
}
std::cmp::Ordering::Equal
}
}
impl<'db> VarianceInferable<'db> for ProtocolInterface<'db> {
@ -813,6 +829,42 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
}
}
}
fn structural_ordering(&self, db: &'db dyn Db, other: &Self) -> std::cmp::Ordering {
let name_ordering = self.name.cmp(other.name);
if name_ordering != std::cmp::Ordering::Equal {
return name_ordering;
}
let qualifiers_ordering = self.qualifiers.cmp(&other.qualifiers);
if qualifiers_ordering != std::cmp::Ordering::Equal {
return qualifiers_ordering;
}
match (&self.kind, &other.kind) {
(ProtocolMemberKind::Method(left), ProtocolMemberKind::Method(right)) => {
left.structural_ordering(db, *right)
}
(ProtocolMemberKind::Property(left), ProtocolMemberKind::Property(right)) => {
left.structural_ordering(db, *right)
}
(ProtocolMemberKind::Other(left), ProtocolMemberKind::Other(right)) => {
structural_type_ordering(db, left, right)
}
(left, right) => {
let left_index = match left {
ProtocolMemberKind::Method(_) => 0,
ProtocolMemberKind::Property(_) => 1,
ProtocolMemberKind::Other(_) => 2,
};
let right_index = match right {
ProtocolMemberKind::Method(_) => 0,
ProtocolMemberKind::Property(_) => 1,
ProtocolMemberKind::Other(_) => 2,
};
left_index.cmp(&right_index)
}
}
}
}
/// Returns `true` if a declaration or binding to a given name in a protocol class body

View File

@ -31,7 +31,8 @@ use crate::types::infer::nearest_enclosing_class;
use crate::types::{
ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassLiteral, FindLegacyTypeVarsVisitor,
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, MaterializationKind,
NormalizedVisitor, TypeContext, TypeMapping, TypeRelation, VarianceInferable, todo_type,
NormalizedVisitor, TypeContext, TypeMapping, TypeRelation, VarianceInferable,
structural_type_ordering, todo_type,
};
use crate::{Db, FxOrderSet};
use ruff_python_ast::{self as ast, name::Name};
@ -367,6 +368,23 @@ impl<'db> CallableSignature<'db> {
}
}
}
pub(super) fn structural_ordering(
&self,
db: &'db dyn Db,
other: &CallableSignature<'db>,
) -> std::cmp::Ordering {
if self.overloads.len() != other.overloads.len() {
return self.overloads.len().cmp(&other.overloads.len());
}
for (left_sig, right_sig) in self.overloads.iter().zip(&other.overloads) {
let ord = left_sig.structural_ordering(db, right_sig);
if ord != std::cmp::Ordering::Equal {
return ord;
}
}
std::cmp::Ordering::Equal
}
}
impl<'a, 'db> IntoIterator for &'a CallableSignature<'db> {
@ -1294,6 +1312,26 @@ impl<'db> Signature<'db> {
pub(crate) fn with_definition(self, definition: Option<Definition<'db>>) -> Self {
Self { definition, ..self }
}
fn structural_ordering(&self, db: &'db dyn Db, other: &Signature<'db>) -> std::cmp::Ordering {
let parameters_count = self.parameters.len().cmp(&other.parameters.len());
if parameters_count != std::cmp::Ordering::Equal {
return parameters_count;
}
for (left, right) in self.parameters.iter().zip(&other.parameters) {
let ord = left.structural_ordering(db, right);
if ord != std::cmp::Ordering::Equal {
return ord;
}
}
match (self.return_ty.as_ref(), other.return_ty.as_ref()) {
(Some(left_ty), Some(right_ty)) => structural_type_ordering(db, left_ty, right_ty),
(None, Some(_)) => std::cmp::Ordering::Less,
(Some(_), None) => std::cmp::Ordering::Greater,
(None, None) => std::cmp::Ordering::Equal,
}
}
}
impl<'db> VarianceInferable<'db> for &Signature<'db> {
@ -2074,6 +2112,17 @@ impl<'db> Parameter<'db> {
ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => None,
}
}
fn structural_ordering(&self, db: &'db dyn Db, other: &Parameter<'db>) -> std::cmp::Ordering {
self.kind.cmp(&other.kind).then_with(|| {
match (self.annotated_type.as_ref(), other.annotated_type.as_ref()) {
(Some(left_ty), Some(right_ty)) => structural_type_ordering(db, left_ty, right_ty),
(None, Some(_)) => std::cmp::Ordering::Less,
(Some(_), None) => std::cmp::Ordering::Greater,
(None, None) => std::cmp::Ordering::Equal,
}
})
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)]
@ -2115,6 +2164,46 @@ pub(crate) enum ParameterKind<'db> {
},
}
impl Ord for ParameterKind<'_> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match (self, other) {
(
Self::PositionalOnly { name: l_name, .. },
Self::PositionalOnly { name: r_name, .. },
) => l_name.cmp(r_name),
(
Self::PositionalOrKeyword { name: l_name, .. },
Self::PositionalOrKeyword { name: r_name, .. },
) => l_name.cmp(r_name),
(Self::Variadic { name: l_name }, Self::Variadic { name: r_name }) => {
l_name.cmp(r_name)
}
(Self::KeywordOnly { name: l_name, .. }, Self::KeywordOnly { name: r_name, .. }) => {
l_name.cmp(r_name)
}
(Self::KeywordVariadic { name: l_name }, Self::KeywordVariadic { name: r_name }) => {
l_name.cmp(r_name)
}
(left, right) => {
let index = |param: &_| match param {
Self::PositionalOnly { .. } => 0,
Self::PositionalOrKeyword { .. } => 1,
Self::Variadic { .. } => 2,
Self::KeywordOnly { .. } => 3,
Self::KeywordVariadic { .. } => 4,
};
index(left).cmp(&index(right))
}
}
}
}
impl PartialOrd for ParameterKind<'_> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<'db> ParameterKind<'db> {
fn apply_type_mapping_impl<'a>(
&self,

View File

@ -30,7 +30,7 @@ use crate::types::generics::InferableTypeVars;
use crate::types::{
ApplyTypeMappingVisitor, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, Type, TypeMapping, TypeRelation,
UnionBuilder, UnionType,
UnionBuilder, UnionType, structural_type_ordering,
};
use crate::types::{Truthiness, TypeContext};
use crate::{Db, FxOrderSet, Program};
@ -302,6 +302,10 @@ impl<'db> TupleType<'db> {
pub(crate) fn is_single_valued(self, db: &'db dyn Db) -> bool {
self.tuple(db).is_single_valued(db)
}
pub(super) fn structural_ordering(self, db: &'db dyn Db, other: Self) -> std::cmp::Ordering {
self.tuple(db).structural_ordering(db, other.tuple(db))
}
}
fn to_class_type_cycle_initial<'db>(
@ -583,6 +587,22 @@ impl<'db> FixedLengthTuple<Type<'db>> {
fn is_single_valued(&self, db: &'db dyn Db) -> bool {
self.0.iter().all(|ty| ty.is_single_valued(db))
}
fn structural_ordering(&self, db: &'db dyn Db, other: &Self) -> std::cmp::Ordering {
let len_count = self.0.len().cmp(&other.0.len());
if len_count != std::cmp::Ordering::Equal {
return len_count;
}
for (left, right) in self.0.iter().zip(&other.0) {
let ord = structural_type_ordering(db, left, right);
if ord != std::cmp::Ordering::Equal {
return ord;
}
}
std::cmp::Ordering::Equal
}
}
impl<'db> PyIndex<'db> for &FixedLengthTuple<Type<'db>> {
@ -1096,6 +1116,32 @@ impl<'db> VariableLengthTuple<Type<'db>> {
})
})
}
fn structural_ordering(&self, db: &'db dyn Db, other: &Self) -> std::cmp::Ordering {
let prefix_count = self.prefix.len().cmp(&other.prefix.len());
if prefix_count != std::cmp::Ordering::Equal {
return prefix_count;
}
let suffix_count = self.suffix.len().cmp(&other.suffix.len());
if suffix_count != std::cmp::Ordering::Equal {
return suffix_count;
}
for (left, right) in self.prefix.iter().zip(&other.prefix) {
let ord = structural_type_ordering(db, left, right);
if ord != std::cmp::Ordering::Equal {
return ord;
}
}
for (left, right) in self.suffix.iter().zip(&other.suffix) {
let ord = structural_type_ordering(db, left, right);
if ord != std::cmp::Ordering::Equal {
return ord;
}
}
structural_type_ordering(db, &self.variable, &other.variable)
}
}
impl<'db> PyIndex<'db> for &VariableLengthTuple<Type<'db>> {
@ -1470,6 +1516,19 @@ impl<'db> Tuple<Type<'db>> {
int_instance_ty,
])
}
pub(super) fn structural_ordering(&self, db: &'db dyn Db, other: &Self) -> std::cmp::Ordering {
match (self, other) {
(Tuple::Fixed(self_tuple), Tuple::Fixed(other_tuple)) => {
self_tuple.structural_ordering(db, other_tuple)
}
(Tuple::Variable(self_tuple), Tuple::Variable(other_tuple)) => {
self_tuple.structural_ordering(db, other_tuple)
}
(Tuple::Fixed(_), Tuple::Variable(_)) => std::cmp::Ordering::Less,
(Tuple::Variable(_), Tuple::Fixed(_)) => std::cmp::Ordering::Greater,
}
}
}
impl<T> From<FixedLengthTuple<T>> for Tuple<T> {

View File

@ -8,6 +8,262 @@ use super::{
DynamicType, TodoType, Type, TypeIsType, class_base::ClassBase, subclass_of::SubclassOfInner,
};
pub(super) fn structural_type_ordering<'db>(
db: &'db dyn Db,
left: &Type<'db>,
right: &Type<'db>,
) -> Ordering {
match (left, right) {
(Type::Never, _) => Ordering::Less,
(_, Type::Never) => Ordering::Greater,
(Type::LiteralString, _) => Ordering::Less,
(_, Type::LiteralString) => Ordering::Greater,
(Type::BooleanLiteral(left), Type::BooleanLiteral(right)) => left.cmp(right),
(Type::BooleanLiteral(_), _) => Ordering::Less,
(_, Type::BooleanLiteral(_)) => Ordering::Greater,
(Type::IntLiteral(left), Type::IntLiteral(right)) => left.cmp(right),
(Type::IntLiteral(_), _) => Ordering::Less,
(_, Type::IntLiteral(_)) => Ordering::Greater,
(Type::StringLiteral(left), Type::StringLiteral(right)) => {
left.structural_ordering(db, *right)
}
(Type::StringLiteral(_), _) => Ordering::Less,
(_, Type::StringLiteral(_)) => Ordering::Greater,
(Type::BytesLiteral(left), Type::BytesLiteral(right)) => {
left.structural_ordering(db, *right)
}
(Type::BytesLiteral(_), _) => Ordering::Less,
(_, Type::BytesLiteral(_)) => Ordering::Greater,
(Type::EnumLiteral(left), Type::EnumLiteral(right)) => left.structural_ordering(db, *right),
(Type::EnumLiteral(_), _) => Ordering::Less,
(_, Type::EnumLiteral(_)) => Ordering::Greater,
(Type::FunctionLiteral(left), Type::FunctionLiteral(right)) => {
left.structural_ordering(db, *right)
}
(Type::FunctionLiteral(_), _) => Ordering::Less,
(_, Type::FunctionLiteral(_)) => Ordering::Greater,
(Type::BoundMethod(left), Type::BoundMethod(right)) => left.structural_ordering(db, *right),
(Type::BoundMethod(_), _) => Ordering::Less,
(_, Type::BoundMethod(_)) => Ordering::Greater,
(Type::KnownBoundMethod(left), Type::KnownBoundMethod(right)) => {
left.structural_ordering(db, *right)
}
(Type::KnownBoundMethod(_), _) => Ordering::Less,
(_, Type::KnownBoundMethod(_)) => Ordering::Greater,
(Type::WrapperDescriptor(left), Type::WrapperDescriptor(right)) => left.cmp(right),
(Type::WrapperDescriptor(_), _) => Ordering::Less,
(_, Type::WrapperDescriptor(_)) => Ordering::Greater,
(Type::DataclassDecorator(left), Type::DataclassDecorator(right)) => {
left.structural_ordering(db, *right)
}
(Type::DataclassDecorator(_), _) => Ordering::Less,
(_, Type::DataclassDecorator(_)) => Ordering::Greater,
(Type::DataclassTransformer(left), Type::DataclassTransformer(right)) => left.cmp(right),
(Type::DataclassTransformer(_), _) => Ordering::Less,
(_, Type::DataclassTransformer(_)) => Ordering::Greater,
(Type::Callable(left), Type::Callable(right)) => left.structural_ordering(db, *right),
(Type::Callable(_), _) => Ordering::Less,
(_, Type::Callable(_)) => Ordering::Greater,
(Type::ModuleLiteral(left), Type::ModuleLiteral(right)) => {
left.structural_ordering(db, *right)
}
(Type::ModuleLiteral(_), _) => Ordering::Less,
(_, Type::ModuleLiteral(_)) => Ordering::Greater,
(Type::ClassLiteral(left), Type::ClassLiteral(right)) => {
left.structural_ordering(db, *right)
}
(Type::ClassLiteral(_), _) => Ordering::Less,
(_, Type::ClassLiteral(_)) => Ordering::Greater,
(Type::GenericAlias(left), Type::GenericAlias(right)) => {
left.structural_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.structural_ordering(db, right)
}
(SubclassOfInner::Class(_), _) => Ordering::Less,
(_, SubclassOfInner::Class(_)) => Ordering::Greater,
(SubclassOfInner::Dynamic(left), SubclassOfInner::Dynamic(right)) => {
dynamic_elements_ordering(left, right)
}
(SubclassOfInner::TypeVar(left), SubclassOfInner::TypeVar(right)) => {
left.structural_ordering(db, right)
}
(SubclassOfInner::TypeVar(_), _) => Ordering::Less,
(_, SubclassOfInner::TypeVar(_)) => Ordering::Greater,
}
}
(Type::SubclassOf(_), _) => Ordering::Less,
(_, Type::SubclassOf(_)) => Ordering::Greater,
(Type::TypeIs(left), Type::TypeIs(right)) => left.structural_ordering(db, *right),
(Type::TypeIs(_), _) => Ordering::Less,
(_, Type::TypeIs(_)) => Ordering::Greater,
(Type::NominalInstance(left), Type::NominalInstance(right)) => {
left.structural_ordering(db, *right)
}
(Type::NominalInstance(_), _) => Ordering::Less,
(_, Type::NominalInstance(_)) => Ordering::Greater,
(Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => {
left.structural_ordering(db, *right)
}
(Type::ProtocolInstance(_), _) => Ordering::Less,
(_, Type::ProtocolInstance(_)) => Ordering::Greater,
(Type::TypeVar(left), Type::TypeVar(right)) => left.structural_ordering(db, *right),
(Type::TypeVar(_), _) => Ordering::Less,
(_, Type::TypeVar(_)) => Ordering::Greater,
(Type::AlwaysTruthy, _) => Ordering::Less,
(_, Type::AlwaysTruthy) => Ordering::Greater,
(Type::AlwaysFalsy, _) => Ordering::Less,
(_, Type::AlwaysFalsy) => Ordering::Greater,
(Type::BoundSuper(left), Type::BoundSuper(right)) => {
(match (left.pivot_class(db), right.pivot_class(db)) {
(ClassBase::Class(left), ClassBase::Class(right)) => {
left.structural_ordering(db, right)
}
(ClassBase::Class(_), _) => Ordering::Less,
(_, ClassBase::Class(_)) => Ordering::Greater,
(ClassBase::Protocol, _) => Ordering::Less,
(_, ClassBase::Protocol) => Ordering::Greater,
(ClassBase::Generic, _) => Ordering::Less,
(_, ClassBase::Generic) => Ordering::Greater,
(ClassBase::TypedDict, _) => Ordering::Less,
(_, ClassBase::TypedDict) => Ordering::Greater,
(ClassBase::Dynamic(left), ClassBase::Dynamic(right)) => {
dynamic_elements_ordering(left, right)
}
})
.then_with(|| match (left.owner(db), right.owner(db)) {
(SuperOwnerKind::Class(left), SuperOwnerKind::Class(right)) => {
left.structural_ordering(db, right)
}
(SuperOwnerKind::Class(_), _) => Ordering::Less,
(_, SuperOwnerKind::Class(_)) => Ordering::Greater,
(SuperOwnerKind::Instance(left), SuperOwnerKind::Instance(right)) => {
left.structural_ordering(db, right)
}
(SuperOwnerKind::Instance(_), _) => Ordering::Less,
(_, SuperOwnerKind::Instance(_)) => Ordering::Greater,
(SuperOwnerKind::Dynamic(left), SuperOwnerKind::Dynamic(right)) => {
dynamic_elements_ordering(left, right)
}
})
}
(Type::BoundSuper(_), _) => Ordering::Less,
(_, Type::BoundSuper(_)) => Ordering::Greater,
(Type::SpecialForm(left), Type::SpecialForm(right)) => left.cmp(right),
(Type::SpecialForm(_), _) => Ordering::Less,
(_, Type::SpecialForm(_)) => Ordering::Greater,
(Type::KnownInstance(left), Type::KnownInstance(right)) => {
left.structural_ordering(db, *right)
}
(Type::KnownInstance(_), _) => Ordering::Less,
(_, Type::KnownInstance(_)) => Ordering::Greater,
(Type::PropertyInstance(left), Type::PropertyInstance(right)) => {
left.structural_ordering(db, *right)
}
(Type::PropertyInstance(_), _) => Ordering::Less,
(_, Type::PropertyInstance(_)) => Ordering::Greater,
(Type::Dynamic(left), Type::Dynamic(right)) => dynamic_elements_ordering(*left, *right),
(Type::Dynamic(_), _) => Ordering::Less,
(_, Type::Dynamic(_)) => Ordering::Greater,
(Type::TypeAlias(left), Type::TypeAlias(right)) => left.structural_ordering(db, *right),
(Type::TypeAlias(_), _) => Ordering::Less,
(_, Type::TypeAlias(_)) => Ordering::Greater,
(Type::TypedDict(left), Type::TypedDict(right)) => left
.defining_class()
.structural_ordering(db, right.defining_class()),
(Type::TypedDict(_), _) => Ordering::Less,
(_, Type::TypedDict(_)) => Ordering::Greater,
(Type::NewTypeInstance(left), Type::NewTypeInstance(right)) => {
left.structural_ordering(db, *right)
}
(Type::NewTypeInstance(_), _) => Ordering::Less,
(_, Type::NewTypeInstance(_)) => Ordering::Greater,
(Type::Union(left), Type::Union(right)) => {
if left.elements(db).len() != right.elements(db).len() {
return left.elements(db).len().cmp(&right.elements(db).len());
}
for (left, right) in left.elements(db).iter().zip(right.elements(db)) {
let ordering = structural_type_ordering(db, left, right);
if ordering != Ordering::Equal {
return ordering;
}
}
Ordering::Equal
}
(Type::Union(_), _) => Ordering::Less,
(_, Type::Union(_)) => Ordering::Greater,
(Type::Intersection(left), Type::Intersection(right)) => {
// Lexicographically compare the elements of the two unequal intersections.
let left_positive = left.positive(db);
let right_positive = right.positive(db);
if left_positive.len() != right_positive.len() {
return left_positive.len().cmp(&right_positive.len());
}
let left_negative = left.negative(db);
let right_negative = right.negative(db);
if left_negative.len() != right_negative.len() {
return left_negative.len().cmp(&right_negative.len());
}
for (left, right) in left_positive.iter().zip(right_positive) {
let ordering = structural_type_ordering(db, left, right);
if ordering != Ordering::Equal {
return ordering;
}
}
for (left, right) in left_negative.iter().zip(right_negative) {
let ordering = structural_type_ordering(db, left, right);
if ordering != Ordering::Equal {
return ordering;
}
}
Ordering::Equal
}
}
}
/// Return an [`Ordering`] that describes the canonical order in which two types should appear
/// in an [`crate::types::IntersectionType`] or a [`crate::types::UnionType`] in order for them
/// to be compared for equivalence.
@ -26,22 +282,17 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
db: &'db dyn Db,
left: &Type<'db>,
right: &Type<'db>,
cycle_recovery: bool,
) -> Ordering {
// If we sort union types in a cycle recovery function, this check is not necessary
// because the purpose is to stabilize the output and the sort order itself is not important.
if !cycle_recovery {
debug_assert_eq!(
*left,
left.normalized(db),
"`left` must be normalized before a meaningful ordering can be established"
);
debug_assert_eq!(
*right,
right.normalized(db),
"`right` must be normalized before a meaningful ordering can be established"
);
}
debug_assert_eq!(
*left,
left.normalized(db),
"`left` must be normalized before a meaningful ordering can be established"
);
debug_assert_eq!(
*right,
right.normalized(db),
"`right` must be normalized before a meaningful ordering can be established"
);
if left == right {
return Ordering::Equal;
@ -133,9 +384,7 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(Type::SubclassOf(_), _) => Ordering::Less,
(_, Type::SubclassOf(_)) => Ordering::Greater,
(Type::TypeIs(left), Type::TypeIs(right)) => {
typeis_ordering(db, *left, *right, cycle_recovery)
}
(Type::TypeIs(left), Type::TypeIs(right)) => typeis_ordering(db, *left, *right),
(Type::TypeIs(_), _) => Ordering::Less,
(_, Type::TypeIs(_)) => Ordering::Greater,
@ -246,15 +495,13 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
return left_negative.len().cmp(&right_negative.len());
}
for (left, right) in left_positive.iter().zip(right_positive) {
let ordering =
union_or_intersection_elements_ordering(db, left, right, cycle_recovery);
let ordering = union_or_intersection_elements_ordering(db, left, right);
if ordering != Ordering::Equal {
return ordering;
}
}
for (left, right) in left_negative.iter().zip(right_negative) {
let ordering =
union_or_intersection_elements_ordering(db, left, right, cycle_recovery);
let ordering = union_or_intersection_elements_ordering(db, left, right);
if ordering != Ordering::Equal {
return ordering;
}
@ -295,26 +542,17 @@ fn dynamic_elements_ordering(left: DynamicType, right: DynamicType) -> Ordering
/// * Boundness: Unbound precedes bound
/// * Symbol name: String comparison
/// * Guarded type: [`union_or_intersection_elements_ordering`]
fn typeis_ordering(
db: &dyn Db,
left: TypeIsType,
right: TypeIsType,
cycle_recovery: bool,
) -> Ordering {
fn typeis_ordering(db: &dyn Db, left: TypeIsType, right: TypeIsType) -> Ordering {
let (left_ty, right_ty) = (left.return_type(db), right.return_type(db));
match (left.place_info(db), right.place_info(db)) {
(None, Some(_)) => Ordering::Less,
(Some(_), None) => Ordering::Greater,
(None, None) => {
union_or_intersection_elements_ordering(db, &left_ty, &right_ty, cycle_recovery)
}
(None, None) => union_or_intersection_elements_ordering(db, &left_ty, &right_ty),
(Some(_), Some(_)) => match left.place_name(db).cmp(&right.place_name(db)) {
Ordering::Equal => {
union_or_intersection_elements_ordering(db, &left_ty, &right_ty, cycle_recovery)
}
Ordering::Equal => union_or_intersection_elements_ordering(db, &left_ty, &right_ty),
ordering => ordering,
},
}