Skip to main content

oxc_span/
cmp.rs

1//! Specialized comparison traits
2
3use oxc_allocator::{ArenaBox, ArenaVec};
4
5/// This trait works similarly to [PartialEq] but it gives the liberty of checking the equality of the
6/// content loosely.
7///
8/// This would mean the implementor can skip some parts of the content while doing equality checks.
9/// As an example, In AST types we ignore fields such as [crate::Span].
10///
11/// One should always prefer using the [PartialEq] over this since implementations of this trait
12/// inherently are slower or in the best-case scenario as fast as the [PartialEq] comparison.
13pub trait ContentEq {
14    /// This method tests for contents of `self` and `other` to be equal.
15    #[must_use]
16    fn content_eq(&self, other: &Self) -> bool;
17
18    /// This method tests for contents of `self` and `other` not to be equal.
19    /// The default implementation is almost always
20    /// sufficient, and should not be overridden without very good reason.
21    #[inline]
22    #[must_use]
23    fn content_ne(&self, other: &Self) -> bool {
24        !self.content_eq(other)
25    }
26}
27
28impl ContentEq for () {
29    #[inline]
30    fn content_eq(&self, _other: &()) -> bool {
31        true
32    }
33
34    #[inline]
35    fn content_ne(&self, _other: &()) -> bool {
36        false
37    }
38}
39
40/// Compare `f64` as bits instead of using `==`.
41///
42/// Result is the same as `partial_eq` (`==`), with the following exceptions:
43///
44/// * `+0` and `-0` are not `content_eq` (they are `partial_eq`).
45/// * `f64::NAN` and `f64::NAN` are `content_eq` (they are not `partial_eq`).
46///
47/// <https://play.rust-lang.org/?version=stable&mode=release&edition=2021&gist=5f9ec4b26128363a660e27582d1de7cd>
48///
49/// ### NaN
50///
51/// Comparison of `NaN` is complicated. From Rust's docs for `f64`:
52///
53/// > Note that IEEE 754 doesn’t define just a single NaN value;
54/// > a plethora of bit patterns are considered to be NaN.
55///
56/// <https://doc.rust-lang.org/std/primitive.f64.html#associatedconstant.NAN>
57///
58/// If either value is `NaN`, `f64::content_eq` only returns `true` if both are the *same* `NaN`,
59/// with the same bit pattern. This means, for example:
60///
61/// ```text
62/// f64::NAN.content_eq(f64::NAN) == true
63/// f64::NAN.content_eq(-f64::NAN) == false
64/// f64::NAN.content_eq(--f64::NAN) == true
65/// ```
66///
67/// Any other `NaN`s which are created through an arithmetic operation, rather than explicitly
68/// with `f64::NAN`, are not guaranteed to equal `f64::NAN`.
69///
70/// ```text
71/// // This results in `false` on at least some flavors of `x84_64`,
72/// // but that's not specified - could also result in `true`!
73/// (-1f64).sqrt().content_eq(f64::NAN) == false
74/// ```
75impl ContentEq for f64 {
76    #[inline]
77    fn content_eq(&self, other: &Self) -> bool {
78        self.to_bits() == other.to_bits()
79    }
80}
81
82/// Blanket implementation for [Option] types
83impl<T: ContentEq> ContentEq for Option<T> {
84    #[inline]
85    fn content_eq(&self, other: &Self) -> bool {
86        // NOTE: based on the standard library
87        // Spelling out the cases explicitly optimizes better than
88        // `_ => false`
89        #[expect(clippy::match_same_arms)]
90        match (self, other) {
91            (Some(lhs), Some(rhs)) => lhs.content_eq(rhs),
92            (Some(_), None) => false,
93            (None, Some(_)) => false,
94            (None, None) => true,
95        }
96    }
97}
98
99/// Blanket implementation for [`Box`](ArenaBox) types
100impl<T: ContentEq> ContentEq for ArenaBox<'_, T> {
101    #[inline]
102    fn content_eq(&self, other: &Self) -> bool {
103        self.as_ref().content_eq(other.as_ref())
104    }
105}
106
107/// Blanket implementation for [`Vec`](ArenaVec) types.
108///
109/// # Warning
110/// This implementation is slow compared to [PartialEq] for native types which are [Copy] (e.g. `u32`).
111/// Prefer comparing the 2 vectors using `==` if they contain such native types (e.g. `Vec<u32>`).
112/// <https://godbolt.org/z/54on5sMWc>
113impl<T: ContentEq> ContentEq for ArenaVec<'_, T> {
114    #[inline]
115    fn content_eq(&self, other: &Self) -> bool {
116        if self.len() == other.len() {
117            !self.iter().zip(other).any(|(lhs, rhs)| lhs.content_ne(rhs))
118        } else {
119            false
120        }
121    }
122}
123
124mod content_eq_auto_impls {
125    use super::ContentEq;
126
127    macro_rules! content_eq_impl {
128        ($($t:ty)*) => ($(
129            impl ContentEq for $t {
130                #[inline]
131                fn content_eq(&self, other: &$t) -> bool { (*self) == (*other) }
132                #[inline]
133                fn content_ne(&self, other: &$t) -> bool { (*self) != (*other) }
134            }
135        )*)
136    }
137
138    content_eq_impl! {
139        char &str
140        bool isize usize
141        u8 u16 u32 u64 u128
142        i8 i16 i32 i64 i128
143    }
144}