mirror of https://github.com/astral-sh/ruff
[red-knot] Ensure differently ordered unions and intersections are considered equivalent (#15516)
This commit is contained in:
parent
b8e5b95423
commit
2b24b3b316
|
|
@ -110,10 +110,8 @@ python-version = "3.10"
|
||||||
from typing_extensions import assert_type
|
from typing_extensions import assert_type
|
||||||
|
|
||||||
def _(a: str | int):
|
def _(a: str | int):
|
||||||
assert_type(a, str | int) # fine
|
assert_type(a, str | int)
|
||||||
|
assert_type(a, int | str)
|
||||||
# TODO: Order-independent union handling in type equivalence
|
|
||||||
assert_type(a, int | str) # error: [type-assertion-failure]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Intersections
|
## Intersections
|
||||||
|
|
@ -135,8 +133,6 @@ def _(a: A):
|
||||||
if isinstance(a, B) and not isinstance(a, C) and not isinstance(a, D):
|
if isinstance(a, B) and not isinstance(a, C) and not isinstance(a, D):
|
||||||
reveal_type(a) # revealed: A & B & ~C & ~D
|
reveal_type(a) # revealed: A & B & ~C & ~D
|
||||||
|
|
||||||
assert_type(a, Intersection[A, B, Not[C], Not[D]]) # fine
|
assert_type(a, Intersection[A, B, Not[C], Not[D]])
|
||||||
|
assert_type(a, Intersection[B, A, Not[D], Not[C]])
|
||||||
# TODO: Order-independent intersection handling in type equivalence
|
|
||||||
assert_type(a, Intersection[B, A, Not[D], Not[C]]) # error: [type-assertion-failure]
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -32,4 +32,38 @@ static_assert(not is_equivalent_to(Literal[1, 0], Literal[1, 2]))
|
||||||
static_assert(not is_equivalent_to(Literal[1, 2, 3], Literal[1, 2]))
|
static_assert(not is_equivalent_to(Literal[1, 2, 3], Literal[1, 2]))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Differently ordered intersections and unions are equivalent
|
||||||
|
|
||||||
|
```py
|
||||||
|
from knot_extensions import is_equivalent_to, static_assert, Intersection, Not
|
||||||
|
|
||||||
|
class P: ...
|
||||||
|
class Q: ...
|
||||||
|
class R: ...
|
||||||
|
class S: ...
|
||||||
|
|
||||||
|
static_assert(is_equivalent_to(P | Q | R, P | R | Q)) # 1
|
||||||
|
static_assert(is_equivalent_to(P | Q | R, Q | P | R)) # 2
|
||||||
|
static_assert(is_equivalent_to(P | Q | R, Q | R | P)) # 3
|
||||||
|
static_assert(is_equivalent_to(P | Q | R, R | P | Q)) # 4
|
||||||
|
static_assert(is_equivalent_to(P | Q | R, R | Q | P)) # 5
|
||||||
|
static_assert(is_equivalent_to(P | R | Q, Q | P | R)) # 6
|
||||||
|
static_assert(is_equivalent_to(P | R | Q, Q | R | P)) # 7
|
||||||
|
static_assert(is_equivalent_to(P | R | Q, R | P | Q)) # 8
|
||||||
|
static_assert(is_equivalent_to(P | R | Q, R | Q | P)) # 9
|
||||||
|
static_assert(is_equivalent_to(Q | P | R, Q | R | P)) # 10
|
||||||
|
static_assert(is_equivalent_to(Q | P | R, R | P | Q)) # 11
|
||||||
|
static_assert(is_equivalent_to(Q | P | R, R | Q | P)) # 12
|
||||||
|
static_assert(is_equivalent_to(Q | R | P, R | P | Q)) # 13
|
||||||
|
static_assert(is_equivalent_to(Q | R | P, R | Q | P)) # 14
|
||||||
|
static_assert(is_equivalent_to(R | P | Q, R | Q | P)) # 15
|
||||||
|
|
||||||
|
static_assert(is_equivalent_to(str | None, None | str))
|
||||||
|
|
||||||
|
static_assert(is_equivalent_to(Intersection[P, Q], Intersection[Q, P]))
|
||||||
|
static_assert(is_equivalent_to(Intersection[Q, Not[P]], Intersection[Not[P], Q]))
|
||||||
|
static_assert(is_equivalent_to(Intersection[Q, R, Not[P]], Intersection[Not[P], R, Q]))
|
||||||
|
static_assert(is_equivalent_to(Intersection[Q | R, Not[P | S]], Intersection[Not[S | P], R | Q]))
|
||||||
|
```
|
||||||
|
|
||||||
[the equivalence relation]: https://typing.readthedocs.io/en/latest/spec/glossary.html#term-equivalent
|
[the equivalence relation]: https://typing.readthedocs.io/en/latest/spec/glossary.html#term-equivalent
|
||||||
|
|
|
||||||
|
|
@ -38,13 +38,11 @@ from knot_extensions import Intersection, Not, Unknown, is_gradual_equivalent_to
|
||||||
|
|
||||||
static_assert(is_gradual_equivalent_to(str | int, str | int))
|
static_assert(is_gradual_equivalent_to(str | int, str | int))
|
||||||
static_assert(is_gradual_equivalent_to(str | int | Any, str | int | Unknown))
|
static_assert(is_gradual_equivalent_to(str | int | Any, str | int | Unknown))
|
||||||
|
static_assert(is_gradual_equivalent_to(str | int, int | str))
|
||||||
# TODO: These should pass
|
|
||||||
static_assert(is_gradual_equivalent_to(str | int, int | str)) # error: [static-assert-error]
|
|
||||||
# error: [static-assert-error]
|
|
||||||
static_assert(
|
static_assert(
|
||||||
is_gradual_equivalent_to(Intersection[str, int, Not[bytes], Not[None]], Intersection[int, str, Not[None], Not[bytes]])
|
is_gradual_equivalent_to(Intersection[str, int, Not[bytes], Not[None]], Intersection[int, str, Not[None], Not[bytes]])
|
||||||
)
|
)
|
||||||
|
# TODO: `~type[Any]` shoudld be gradually equivalent to `~type[Unknown]`
|
||||||
# error: [static-assert-error]
|
# error: [static-assert-error]
|
||||||
static_assert(is_gradual_equivalent_to(Intersection[str | int, Not[type[Any]]], Intersection[int | str, Not[type[Unknown]]]))
|
static_assert(is_gradual_equivalent_to(Intersection[str | int, Not[type[Any]]], Intersection[int | str, Not[type[Unknown]]]))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use itertools::Itertools;
|
||||||
use ruff_db::diagnostic::Severity;
|
use ruff_db::diagnostic::Severity;
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
|
use type_ordering::union_elements_ordering;
|
||||||
|
|
||||||
pub(crate) use self::builder::{IntersectionBuilder, UnionBuilder};
|
pub(crate) use self::builder::{IntersectionBuilder, UnionBuilder};
|
||||||
pub(crate) use self::diagnostic::register_lints;
|
pub(crate) use self::diagnostic::register_lints;
|
||||||
|
|
@ -55,6 +56,7 @@ mod signatures;
|
||||||
mod slots;
|
mod slots;
|
||||||
mod string_annotation;
|
mod string_annotation;
|
||||||
mod subclass_of;
|
mod subclass_of;
|
||||||
|
mod type_ordering;
|
||||||
mod unpacker;
|
mod unpacker;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -1112,16 +1114,15 @@ impl<'db> Type<'db> {
|
||||||
///
|
///
|
||||||
/// [equivalent to]: https://typing.readthedocs.io/en/latest/spec/glossary.html#term-equivalent
|
/// [equivalent to]: https://typing.readthedocs.io/en/latest/spec/glossary.html#term-equivalent
|
||||||
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Type<'db>) -> bool {
|
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Type<'db>) -> bool {
|
||||||
if !(self.is_fully_static(db) && other.is_fully_static(db)) {
|
// TODO equivalent but not identical types: TypedDicts, Protocols, type aliases, etc.
|
||||||
return false;
|
|
||||||
|
match (self, other) {
|
||||||
|
(Type::Union(left), Type::Union(right)) => left.is_equivalent_to(db, right),
|
||||||
|
(Type::Intersection(left), Type::Intersection(right)) => {
|
||||||
|
left.is_equivalent_to(db, right)
|
||||||
|
}
|
||||||
|
_ => self.is_fully_static(db) && other.is_fully_static(db) && self == other,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO equivalent but not identical structural types, differently-ordered unions and
|
|
||||||
// intersections, other cases?
|
|
||||||
|
|
||||||
// For all other cases, types are equivalent iff they have the same internal
|
|
||||||
// representation.
|
|
||||||
self == other
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if both `self` and `other` are the same gradual form
|
/// Returns true if both `self` and `other` are the same gradual form
|
||||||
|
|
@ -1163,16 +1164,6 @@ impl<'db> Type<'db> {
|
||||||
|
|
||||||
(Type::Dynamic(_), Type::Dynamic(_)) => true,
|
(Type::Dynamic(_), Type::Dynamic(_)) => true,
|
||||||
|
|
||||||
(Type::Instance(instance), Type::SubclassOf(subclass))
|
|
||||||
| (Type::SubclassOf(subclass), Type::Instance(instance)) => {
|
|
||||||
let Some(base_class) = subclass.subclass_of().into_class() else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
instance.class.is_known(db, KnownClass::Type)
|
|
||||||
&& base_class.is_known(db, KnownClass::Object)
|
|
||||||
}
|
|
||||||
|
|
||||||
(Type::SubclassOf(first), Type::SubclassOf(second)) => {
|
(Type::SubclassOf(first), Type::SubclassOf(second)) => {
|
||||||
match (first.subclass_of(), second.subclass_of()) {
|
match (first.subclass_of(), second.subclass_of()) {
|
||||||
(first, second) if first == second => true,
|
(first, second) if first == second => true,
|
||||||
|
|
@ -1189,34 +1180,10 @@ impl<'db> Type<'db> {
|
||||||
&& iter::zip(first_elements, second_elements).all(equivalent)
|
&& iter::zip(first_elements, second_elements).all(equivalent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Handle equivalent unions with items in different order
|
(Type::Union(first), Type::Union(second)) => first.is_gradual_equivalent_to(db, second),
|
||||||
(Type::Union(first), Type::Union(second)) => {
|
|
||||||
let first_elements = first.elements(db);
|
|
||||||
let second_elements = second.elements(db);
|
|
||||||
|
|
||||||
if first_elements.len() != second_elements.len() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
iter::zip(first_elements, second_elements).all(equivalent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Handle equivalent intersections with items in different order
|
|
||||||
(Type::Intersection(first), Type::Intersection(second)) => {
|
(Type::Intersection(first), Type::Intersection(second)) => {
|
||||||
let first_positive = first.positive(db);
|
first.is_gradual_equivalent_to(db, second)
|
||||||
let first_negative = first.negative(db);
|
|
||||||
|
|
||||||
let second_positive = second.positive(db);
|
|
||||||
let second_negative = second.negative(db);
|
|
||||||
|
|
||||||
if first_positive.len() != second_positive.len()
|
|
||||||
|| first_negative.len() != second_negative.len()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
iter::zip(first_positive, second_positive).all(equivalent)
|
|
||||||
&& iter::zip(first_negative, second_negative).all(equivalent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
|
|
@ -1476,7 +1443,7 @@ impl<'db> Type<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the type does not contain any gradual forms (as a sub-part).
|
/// Returns true if the type does not contain any gradual forms (as a sub-part).
|
||||||
pub(crate) fn is_fully_static(self, db: &'db dyn Db) -> bool {
|
pub(crate) fn is_fully_static(&self, db: &'db dyn Db) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Type::Dynamic(_) => false,
|
Type::Dynamic(_) => false,
|
||||||
Type::Never
|
Type::Never
|
||||||
|
|
@ -1510,20 +1477,8 @@ impl<'db> Type<'db> {
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Type::Union(union) => union
|
Type::Union(union) => union.is_fully_static(db),
|
||||||
.elements(db)
|
Type::Intersection(intersection) => intersection.is_fully_static(db),
|
||||||
.iter()
|
|
||||||
.all(|elem| elem.is_fully_static(db)),
|
|
||||||
Type::Intersection(intersection) => {
|
|
||||||
intersection
|
|
||||||
.positive(db)
|
|
||||||
.iter()
|
|
||||||
.all(|elem| elem.is_fully_static(db))
|
|
||||||
&& intersection
|
|
||||||
.negative(db)
|
|
||||||
.iter()
|
|
||||||
.all(|elem| elem.is_fully_static(db))
|
|
||||||
}
|
|
||||||
Type::Tuple(tuple) => tuple
|
Type::Tuple(tuple) => tuple
|
||||||
.elements(db)
|
.elements(db)
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -4316,10 +4271,11 @@ impl<'db> UnionType<'db> {
|
||||||
|
|
||||||
/// Create a union from a list of elements
|
/// Create a union from a list of elements
|
||||||
/// (which may be eagerly simplified into a different variant of [`Type`] altogether).
|
/// (which may be eagerly simplified into a different variant of [`Type`] altogether).
|
||||||
pub fn from_elements<T: Into<Type<'db>>>(
|
pub fn from_elements<I, T>(db: &'db dyn Db, elements: I) -> Type<'db>
|
||||||
db: &'db dyn Db,
|
where
|
||||||
elements: impl IntoIterator<Item = T>,
|
I: IntoIterator<Item = T>,
|
||||||
) -> Type<'db> {
|
T: Into<Type<'db>>,
|
||||||
|
{
|
||||||
elements
|
elements
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.fold(UnionBuilder::new(db), |builder, element| {
|
.fold(UnionBuilder::new(db), |builder, element| {
|
||||||
|
|
@ -4337,6 +4293,93 @@ impl<'db> UnionType<'db> {
|
||||||
) -> Type<'db> {
|
) -> Type<'db> {
|
||||||
Self::from_elements(db, self.elements(db).iter().map(transform_fn))
|
Self::from_elements(db, self.elements(db).iter().map(transform_fn))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_fully_static(self, db: &'db dyn Db) -> bool {
|
||||||
|
self.elements(db).iter().all(|ty| ty.is_fully_static(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new union type with the elements sorted according to a canonical ordering.
|
||||||
|
#[must_use]
|
||||||
|
pub fn to_sorted_union(self, db: &'db dyn Db) -> Self {
|
||||||
|
let mut new_elements = self.elements(db).to_vec();
|
||||||
|
for element in &mut new_elements {
|
||||||
|
if let Type::Intersection(intersection) = element {
|
||||||
|
intersection.sort(db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new_elements.sort_unstable_by(union_elements_ordering);
|
||||||
|
UnionType::new(db, new_elements.into_boxed_slice())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return `true` if `self` represents the exact same set of possible runtime objects as `other`
|
||||||
|
pub fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||||
|
/// Inlined version of [`UnionType::is_fully_static`] to avoid having to lookup
|
||||||
|
/// `self.elements` multiple times in the Salsa db in this single method.
|
||||||
|
#[inline]
|
||||||
|
fn all_fully_static(db: &dyn Db, elements: &[Type]) -> bool {
|
||||||
|
elements.iter().all(|ty| ty.is_fully_static(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
let self_elements = self.elements(db);
|
||||||
|
let other_elements = other.elements(db);
|
||||||
|
|
||||||
|
if self_elements.len() != other_elements.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !all_fully_static(db, self_elements) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !all_fully_static(db, other_elements) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self == other {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sorted_self = self.to_sorted_union(db);
|
||||||
|
|
||||||
|
if sorted_self == other {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
sorted_self == other.to_sorted_union(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 fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||||
|
if self == other {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: `T | Unknown` should be gradually equivalent to `T | Unknown | Any`,
|
||||||
|
// since they have exactly the same set of possible static materializations
|
||||||
|
// (they represent the same set of possible sets of possible runtime objects)
|
||||||
|
if self.elements(db).len() != other.elements(db).len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sorted_self = self.to_sorted_union(db);
|
||||||
|
|
||||||
|
if sorted_self == other {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sorted_other = other.to_sorted_union(db);
|
||||||
|
|
||||||
|
if sorted_self == sorted_other {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
sorted_self
|
||||||
|
.elements(db)
|
||||||
|
.iter()
|
||||||
|
.zip(sorted_other.elements(db))
|
||||||
|
.all(|(self_ty, other_ty)| self_ty.is_gradual_equivalent_to(db, *other_ty))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[salsa::interned]
|
#[salsa::interned]
|
||||||
|
|
@ -4354,6 +4397,105 @@ pub struct IntersectionType<'db> {
|
||||||
negative: FxOrderSet<Type<'db>>,
|
negative: FxOrderSet<Type<'db>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'db> IntersectionType<'db> {
|
||||||
|
/// Return a new `IntersectionType` instance with the positive and negative types sorted
|
||||||
|
/// according to a canonical ordering.
|
||||||
|
#[must_use]
|
||||||
|
pub fn to_sorted_intersection(self, db: &'db dyn Db) -> Self {
|
||||||
|
let mut positive = self.positive(db).clone();
|
||||||
|
positive.sort_unstable_by(union_elements_ordering);
|
||||||
|
|
||||||
|
let mut negative = self.negative(db).clone();
|
||||||
|
negative.sort_unstable_by(union_elements_ordering);
|
||||||
|
|
||||||
|
IntersectionType::new(db, positive, negative)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform an in-place sort of this [`IntersectionType`] instance
|
||||||
|
/// according to a canonical ordering.
|
||||||
|
fn sort(&mut self, db: &'db dyn Db) {
|
||||||
|
*self = self.to_sorted_intersection(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_fully_static(self, db: &'db dyn Db) -> bool {
|
||||||
|
self.positive(db).iter().all(|ty| ty.is_fully_static(db))
|
||||||
|
&& self.negative(db).iter().all(|ty| ty.is_fully_static(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return `true` if `self` represents exactly the same set of possible runtime objects as `other`
|
||||||
|
pub fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||||
|
/// Inlined version of [`IntersectionType::is_fully_static`] to avoid having to lookup
|
||||||
|
/// `positive` and `negative` multiple times in the Salsa db in this single method.
|
||||||
|
#[inline]
|
||||||
|
fn all_fully_static(db: &dyn Db, elements: &FxOrderSet<Type>) -> bool {
|
||||||
|
elements.iter().all(|ty| ty.is_fully_static(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
let self_positive = self.positive(db);
|
||||||
|
if !all_fully_static(db, self_positive) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let self_negative = self.negative(db);
|
||||||
|
if !all_fully_static(db, self_negative) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let other_positive = other.positive(db);
|
||||||
|
if !all_fully_static(db, other_positive) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let other_negative = other.negative(db);
|
||||||
|
if !all_fully_static(db, other_negative) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self == other {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
self_positive.set_eq(other_positive) && self_negative.set_eq(other_negative)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||||
|
if self == other {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.positive(db).len() != other.positive(db).len()
|
||||||
|
|| self.negative(db).len() != other.negative(db).len()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sorted_self = self.to_sorted_intersection(db);
|
||||||
|
|
||||||
|
if sorted_self == other {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sorted_other = other.to_sorted_intersection(db);
|
||||||
|
|
||||||
|
if sorted_self == sorted_other {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
sorted_self
|
||||||
|
.positive(db)
|
||||||
|
.iter()
|
||||||
|
.zip(sorted_other.positive(db))
|
||||||
|
.all(|(self_ty, other_ty)| self_ty.is_gradual_equivalent_to(db, *other_ty))
|
||||||
|
&& sorted_self
|
||||||
|
.negative(db)
|
||||||
|
.iter()
|
||||||
|
.zip(sorted_other.negative(db))
|
||||||
|
.all(|(self_ty, other_ty)| self_ty.is_gradual_equivalent_to(db, *other_ty))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[salsa::interned]
|
#[salsa::interned]
|
||||||
pub struct StringLiteralType<'db> {
|
pub struct StringLiteralType<'db> {
|
||||||
#[return_ref]
|
#[return_ref]
|
||||||
|
|
|
||||||
|
|
@ -219,15 +219,16 @@ macro_rules! type_property_test {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn intersection<'db>(db: &'db TestDb, s: Type<'db>, t: Type<'db>) -> Type<'db> {
|
fn intersection<'db>(db: &'db TestDb, tys: impl IntoIterator<Item = Type<'db>>) -> Type<'db> {
|
||||||
IntersectionBuilder::new(db)
|
let mut builder = IntersectionBuilder::new(db);
|
||||||
.add_positive(s)
|
for ty in tys {
|
||||||
.add_positive(t)
|
builder = builder.add_positive(ty);
|
||||||
.build()
|
}
|
||||||
|
builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn union<'db>(db: &'db TestDb, s: Type<'db>, t: Type<'db>) -> Type<'db> {
|
fn union<'db>(db: &'db TestDb, tys: impl IntoIterator<Item = Type<'db>>) -> Type<'db> {
|
||||||
UnionType::from_elements(db, [s, t])
|
UnionType::from_elements(db, tys)
|
||||||
}
|
}
|
||||||
|
|
||||||
mod stable {
|
mod stable {
|
||||||
|
|
@ -253,6 +254,12 @@ mod stable {
|
||||||
forall types s, t, u. s.is_equivalent_to(db, t) && t.is_equivalent_to(db, u) => s.is_equivalent_to(db, u)
|
forall types s, t, u. s.is_equivalent_to(db, t) && t.is_equivalent_to(db, u) => s.is_equivalent_to(db, u)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Symmetry: If `S` is gradual equivalent to `T`, `T` is gradual equivalent to `S`.
|
||||||
|
type_property_test!(
|
||||||
|
gradual_equivalent_to_is_symmetric, db,
|
||||||
|
forall types s, t. s.is_gradual_equivalent_to(db, t) => t.is_gradual_equivalent_to(db, s)
|
||||||
|
);
|
||||||
|
|
||||||
// A fully static type `T` is a subtype of itself.
|
// A fully static type `T` is a subtype of itself.
|
||||||
type_property_test!(
|
type_property_test!(
|
||||||
subtype_of_is_reflexive, db,
|
subtype_of_is_reflexive, db,
|
||||||
|
|
@ -265,6 +272,12 @@ mod stable {
|
||||||
forall types s, t, u. s.is_subtype_of(db, t) && t.is_subtype_of(db, u) => s.is_subtype_of(db, u)
|
forall types s, t, u. s.is_subtype_of(db, t) && t.is_subtype_of(db, u) => s.is_subtype_of(db, u)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// `S <: T` and `T <: S` implies that `S` is equivalent to `T`.
|
||||||
|
type_property_test!(
|
||||||
|
subtype_of_is_antisymmetric, db,
|
||||||
|
forall types s, t. s.is_subtype_of(db, t) && t.is_subtype_of(db, s) => s.is_equivalent_to(db, t)
|
||||||
|
);
|
||||||
|
|
||||||
// `T` is not disjoint from itself, unless `T` is `Never`.
|
// `T` is not disjoint from itself, unless `T` is `Never`.
|
||||||
type_property_test!(
|
type_property_test!(
|
||||||
disjoint_from_is_irreflexive, db,
|
disjoint_from_is_irreflexive, db,
|
||||||
|
|
@ -336,7 +349,7 @@ mod stable {
|
||||||
all_fully_static_type_pairs_are_subtype_of_their_union, db,
|
all_fully_static_type_pairs_are_subtype_of_their_union, db,
|
||||||
forall types s, t.
|
forall types s, t.
|
||||||
s.is_fully_static(db) && t.is_fully_static(db)
|
s.is_fully_static(db) && t.is_fully_static(db)
|
||||||
=> s.is_subtype_of(db, union(db, s, t)) && t.is_subtype_of(db, union(db, s, t))
|
=> s.is_subtype_of(db, union(db, [s, t])) && t.is_subtype_of(db, union(db, [s, t]))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -348,6 +361,8 @@ mod stable {
|
||||||
/// tests to the `stable` section. In the meantime, it can still be useful to run these
|
/// tests to the `stable` section. In the meantime, it can still be useful to run these
|
||||||
/// tests (using [`types::property_tests::flaky`]), to see if there are any new obvious bugs.
|
/// tests (using [`types::property_tests::flaky`]), to see if there are any new obvious bugs.
|
||||||
mod flaky {
|
mod flaky {
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
use super::{intersection, union};
|
use super::{intersection, union};
|
||||||
|
|
||||||
// Currently fails due to https://github.com/astral-sh/ruff/issues/14899
|
// Currently fails due to https://github.com/astral-sh/ruff/issues/14899
|
||||||
|
|
@ -357,13 +372,6 @@ mod flaky {
|
||||||
forall types t. t.is_assignable_to(db, t)
|
forall types t. t.is_assignable_to(db, t)
|
||||||
);
|
);
|
||||||
|
|
||||||
// `S <: T` and `T <: S` implies that `S` is equivalent to `T`.
|
|
||||||
// This very often passes now, but occasionally flakes due to https://github.com/astral-sh/ruff/issues/15380
|
|
||||||
type_property_test!(
|
|
||||||
subtype_of_is_antisymmetric, db,
|
|
||||||
forall types s, t. s.is_subtype_of(db, t) && t.is_subtype_of(db, s) => s.is_equivalent_to(db, t)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Negating `T` twice is equivalent to `T`.
|
// Negating `T` twice is equivalent to `T`.
|
||||||
type_property_test!(
|
type_property_test!(
|
||||||
double_negation_is_identity, db,
|
double_negation_is_identity, db,
|
||||||
|
|
@ -387,7 +395,7 @@ mod flaky {
|
||||||
all_fully_static_type_pairs_are_supertypes_of_their_intersection, db,
|
all_fully_static_type_pairs_are_supertypes_of_their_intersection, db,
|
||||||
forall types s, t.
|
forall types s, t.
|
||||||
s.is_fully_static(db) && t.is_fully_static(db)
|
s.is_fully_static(db) && t.is_fully_static(db)
|
||||||
=> intersection(db, s, t).is_subtype_of(db, s) && intersection(db, s, t).is_subtype_of(db, t)
|
=> intersection(db, [s, t]).is_subtype_of(db, s) && intersection(db, [s, t]).is_subtype_of(db, t)
|
||||||
);
|
);
|
||||||
|
|
||||||
// And for non-fully-static types, the intersection of a pair of types
|
// And for non-fully-static types, the intersection of a pair of types
|
||||||
|
|
@ -395,20 +403,42 @@ mod flaky {
|
||||||
// Currently fails due to https://github.com/astral-sh/ruff/issues/14899
|
// Currently fails due to https://github.com/astral-sh/ruff/issues/14899
|
||||||
type_property_test!(
|
type_property_test!(
|
||||||
all_type_pairs_can_be_assigned_from_their_intersection, db,
|
all_type_pairs_can_be_assigned_from_their_intersection, db,
|
||||||
forall types s, t. intersection(db, s, t).is_assignable_to(db, s) && intersection(db, s, t).is_assignable_to(db, t)
|
forall types s, t. intersection(db, [s, t]).is_assignable_to(db, s) && intersection(db, [s, t]).is_assignable_to(db, t)
|
||||||
);
|
);
|
||||||
|
|
||||||
// For *any* pair of types, whether fully static or not,
|
// For *any* pair of types, whether fully static or not,
|
||||||
// each of the pair should be assignable to the union of the two.
|
// each of the pair should be assignable to the union of the two.
|
||||||
type_property_test!(
|
type_property_test!(
|
||||||
all_type_pairs_are_assignable_to_their_union, db,
|
all_type_pairs_are_assignable_to_their_union, db,
|
||||||
forall types s, t. s.is_assignable_to(db, union(db, s, t)) && t.is_assignable_to(db, union(db, s, t))
|
forall types s, t. s.is_assignable_to(db, union(db, [s, t])) && t.is_assignable_to(db, union(db, [s, t]))
|
||||||
);
|
);
|
||||||
|
|
||||||
// Symmetry: If `S` is gradual equivalent to `T`, `T` is gradual equivalent to `S`.
|
// Equal element sets of intersections implies equivalence
|
||||||
|
// flaky at least in part because of https://github.com/astral-sh/ruff/issues/15513
|
||||||
type_property_test!(
|
type_property_test!(
|
||||||
gradual_equivalent_to_is_symmetric, db,
|
intersection_equivalence_not_order_dependent, db,
|
||||||
forall types s, t. s.is_gradual_equivalent_to(db, t) => t.is_gradual_equivalent_to(db, s)
|
forall types s, t, u.
|
||||||
|
s.is_fully_static(db) && t.is_fully_static(db) && u.is_fully_static(db)
|
||||||
|
=> [s, t, u]
|
||||||
|
.into_iter()
|
||||||
|
.permutations(3)
|
||||||
|
.map(|trio_of_types| intersection(db, trio_of_types))
|
||||||
|
.permutations(2)
|
||||||
|
.all(|vec_of_intersections| vec_of_intersections[0].is_equivalent_to(db, vec_of_intersections[1]))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Equal element sets of unions implies equivalence
|
||||||
|
// flaky at laest in part because of https://github.com/astral-sh/ruff/issues/15513
|
||||||
|
type_property_test!(
|
||||||
|
union_equivalence_not_order_dependent, db,
|
||||||
|
forall types s, t, u.
|
||||||
|
s.is_fully_static(db) && t.is_fully_static(db) && u.is_fully_static(db)
|
||||||
|
=> [s, t, u]
|
||||||
|
.into_iter()
|
||||||
|
.permutations(3)
|
||||||
|
.map(|trio_of_types| union(db, trio_of_types))
|
||||||
|
.permutations(2)
|
||||||
|
.all(|vec_of_unions| vec_of_unions[0].is_equivalent_to(db, vec_of_unions[1]))
|
||||||
);
|
);
|
||||||
|
|
||||||
// A fully static type does not have any materializations.
|
// A fully static type does not have any materializations.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,266 @@
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
class_base::ClassBase, ClassLiteralType, DynamicType, InstanceType, KnownInstanceType,
|
||||||
|
TodoType, Type,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// Two unions with equal sets of elements will only compare equal if they have their element sets
|
||||||
|
/// ordered the same way.
|
||||||
|
///
|
||||||
|
/// ## Why not just implement [`Ord`] on [`Type`]?
|
||||||
|
///
|
||||||
|
/// It would be fairly easy to slap `#[derive(PartialOrd, Ord)]` on [`Type`], and the ordering we
|
||||||
|
/// create here is not user-facing. However, it doesn't really "make sense" for `Type` to implement
|
||||||
|
/// [`Ord`] in terms of the semantics. There are many different ways in which you could plausibly
|
||||||
|
/// sort a list of types; this is only one (somewhat arbitrary, at times) possible ordering.
|
||||||
|
pub(super) fn union_elements_ordering<'db>(left: &Type<'db>, right: &Type<'db>) -> Ordering {
|
||||||
|
if left == right {
|
||||||
|
return Ordering::Equal;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.cmp(right),
|
||||||
|
(Type::StringLiteral(_), _) => Ordering::Less,
|
||||||
|
(_, Type::StringLiteral(_)) => Ordering::Greater,
|
||||||
|
|
||||||
|
(Type::BytesLiteral(left), Type::BytesLiteral(right)) => left.cmp(right),
|
||||||
|
(Type::BytesLiteral(_), _) => Ordering::Less,
|
||||||
|
(_, Type::BytesLiteral(_)) => Ordering::Greater,
|
||||||
|
|
||||||
|
(Type::SliceLiteral(left), Type::SliceLiteral(right)) => left.cmp(right),
|
||||||
|
(Type::SliceLiteral(_), _) => Ordering::Less,
|
||||||
|
(_, Type::SliceLiteral(_)) => Ordering::Greater,
|
||||||
|
|
||||||
|
(Type::FunctionLiteral(left), Type::FunctionLiteral(right)) => left.cmp(right),
|
||||||
|
(Type::FunctionLiteral(_), _) => Ordering::Less,
|
||||||
|
(_, Type::FunctionLiteral(_)) => Ordering::Greater,
|
||||||
|
|
||||||
|
(Type::Tuple(left), Type::Tuple(right)) => left.cmp(right),
|
||||||
|
(Type::Tuple(_), _) => Ordering::Less,
|
||||||
|
(_, Type::Tuple(_)) => Ordering::Greater,
|
||||||
|
|
||||||
|
(Type::ModuleLiteral(left), Type::ModuleLiteral(right)) => left.cmp(right),
|
||||||
|
(Type::ModuleLiteral(_), _) => Ordering::Less,
|
||||||
|
(_, Type::ModuleLiteral(_)) => Ordering::Greater,
|
||||||
|
|
||||||
|
(
|
||||||
|
Type::ClassLiteral(ClassLiteralType { class: left }),
|
||||||
|
Type::ClassLiteral(ClassLiteralType { class: right }),
|
||||||
|
) => left.cmp(right),
|
||||||
|
(Type::ClassLiteral(_), _) => Ordering::Less,
|
||||||
|
(_, Type::ClassLiteral(_)) => Ordering::Greater,
|
||||||
|
|
||||||
|
(Type::SubclassOf(left), Type::SubclassOf(right)) => {
|
||||||
|
match (left.subclass_of(), right.subclass_of()) {
|
||||||
|
(ClassBase::Class(left), ClassBase::Class(right)) => left.cmp(&right),
|
||||||
|
(ClassBase::Class(_), _) => Ordering::Less,
|
||||||
|
(_, ClassBase::Class(_)) => Ordering::Greater,
|
||||||
|
(ClassBase::Dynamic(left), ClassBase::Dynamic(right)) => {
|
||||||
|
dynamic_elements_ordering(left, right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(Type::SubclassOf(_), _) => Ordering::Less,
|
||||||
|
(_, Type::SubclassOf(_)) => Ordering::Greater,
|
||||||
|
(
|
||||||
|
Type::Instance(InstanceType { class: left }),
|
||||||
|
Type::Instance(InstanceType { class: right }),
|
||||||
|
) => left.cmp(right),
|
||||||
|
|
||||||
|
(Type::Instance(_), _) => Ordering::Less,
|
||||||
|
(_, Type::Instance(_)) => Ordering::Greater,
|
||||||
|
|
||||||
|
(Type::AlwaysTruthy, _) => Ordering::Less,
|
||||||
|
(_, Type::AlwaysTruthy) => Ordering::Greater,
|
||||||
|
|
||||||
|
(Type::AlwaysFalsy, _) => Ordering::Less,
|
||||||
|
(_, Type::AlwaysFalsy) => Ordering::Greater,
|
||||||
|
|
||||||
|
(Type::KnownInstance(left_instance), Type::KnownInstance(right_instance)) => {
|
||||||
|
match (left_instance, right_instance) {
|
||||||
|
(KnownInstanceType::Any, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Any) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Tuple, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Tuple) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::AlwaysFalsy, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::AlwaysFalsy) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::AlwaysTruthy, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::AlwaysTruthy) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Annotated, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Annotated) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Callable, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Callable) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::ChainMap, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::ChainMap) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::ClassVar, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::ClassVar) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Concatenate, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Concatenate) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Counter, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Counter) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::DefaultDict, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::DefaultDict) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Deque, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Deque) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Dict, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Dict) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Final, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Final) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::FrozenSet, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::FrozenSet) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::TypeGuard, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::TypeGuard) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::List, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::List) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Literal, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Literal) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::LiteralString, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::LiteralString) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Optional, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Optional) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::OrderedDict, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::OrderedDict) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::NoReturn, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::NoReturn) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Never, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Never) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Set, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Set) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Type, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Type) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::TypeAlias, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::TypeAlias) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Unknown, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Unknown) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Not, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Not) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Intersection, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Intersection) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::TypeOf, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::TypeOf) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Unpack, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Unpack) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::TypingSelf, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::TypingSelf) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Required, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Required) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::NotRequired, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::NotRequired) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::TypeIs, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::TypeIs) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::ReadOnly, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::ReadOnly) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::Union, _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::Union) => Ordering::Greater,
|
||||||
|
|
||||||
|
(
|
||||||
|
KnownInstanceType::TypeAliasType(left),
|
||||||
|
KnownInstanceType::TypeAliasType(right),
|
||||||
|
) => left.cmp(right),
|
||||||
|
(KnownInstanceType::TypeAliasType(_), _) => Ordering::Less,
|
||||||
|
(_, KnownInstanceType::TypeAliasType(_)) => Ordering::Greater,
|
||||||
|
|
||||||
|
(KnownInstanceType::TypeVar(left), KnownInstanceType::TypeVar(right)) => {
|
||||||
|
left.cmp(right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(Type::KnownInstance(_), _) => Ordering::Less,
|
||||||
|
(_, Type::KnownInstance(_)) => Ordering::Greater,
|
||||||
|
|
||||||
|
(Type::Dynamic(left), Type::Dynamic(right)) => dynamic_elements_ordering(*left, *right),
|
||||||
|
(Type::Dynamic(_), _) => Ordering::Less,
|
||||||
|
(_, Type::Dynamic(_)) => Ordering::Greater,
|
||||||
|
|
||||||
|
(Type::Union(left), Type::Union(right)) => left.cmp(right),
|
||||||
|
(Type::Union(_), _) => Ordering::Less,
|
||||||
|
(_, Type::Union(_)) => Ordering::Greater,
|
||||||
|
|
||||||
|
(Type::Intersection(left), Type::Intersection(right)) => left.cmp(right),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine a canonical order for two instances of [`DynamicType`].
|
||||||
|
fn dynamic_elements_ordering(left: DynamicType, right: DynamicType) -> Ordering {
|
||||||
|
match (left, right) {
|
||||||
|
(DynamicType::Any, _) => Ordering::Less,
|
||||||
|
(_, DynamicType::Any) => Ordering::Greater,
|
||||||
|
|
||||||
|
(DynamicType::Unknown, _) => Ordering::Less,
|
||||||
|
(_, DynamicType::Unknown) => Ordering::Greater,
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
(DynamicType::Todo(left), DynamicType::Todo(right)) => match (left, right) {
|
||||||
|
(
|
||||||
|
TodoType::FileAndLine(left_file, left_line),
|
||||||
|
TodoType::FileAndLine(right_file, right_line),
|
||||||
|
) => left_file
|
||||||
|
.cmp(right_file)
|
||||||
|
.then_with(|| left_line.cmp(&right_line)),
|
||||||
|
(TodoType::FileAndLine(..), _) => Ordering::Less,
|
||||||
|
(_, TodoType::FileAndLine(..)) => Ordering::Greater,
|
||||||
|
|
||||||
|
(TodoType::Message(left), TodoType::Message(right)) => left.cmp(right),
|
||||||
|
},
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
(DynamicType::Todo(TodoType), DynamicType::Todo(TodoType)) => Ordering::Equal,
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue