use std::any::Any;
pub struct PartialEqAny<'a> {
cmp: fn(&'a dyn Any, &'a dyn Any) -> bool,
val: &'a dyn Any,
}
impl<'a> PartialEqAny<'a> {
pub fn new<A: PartialEq + 'static>(a: &'a A) -> Self {
Self {
cmp: |this, other| {
debug_assert!(this.downcast_ref::<A>().is_some());
Some(unsafe { &*(this as *const dyn Any as *const A) }) == other.downcast_ref::<A>()
},
val: a,
}
}
pub fn always_false() -> Self {
struct AlwaysFalse;
impl PartialEq for AlwaysFalse {
fn eq(&self, _other: &Self) -> bool {
false
}
}
PartialEqAny::new(&AlwaysFalse)
}
}
impl<'a> PartialEq for PartialEqAny<'a> {
fn eq(&self, other: &PartialEqAny<'a>) -> bool {
(self.cmp)(self.val, other.val)
}
}
#[macro_export]
macro_rules! cmp_chain {
($e:expr) => {
$e
};
($e:expr, $($x:expr),+ $(,)?) => {
match $e {
std::cmp::Ordering::Equal => {
cmp_chain!($($x),+)
},
c => {
c
}
}
};
}
#[macro_export]
macro_rules! eq_chain {
($e:expr) => {
$e
};
($e:expr, $($x:expr),+ $(,)?) => {
if $e {
eq_chain!($($x),+)
} else {
false
}
};
}
#[cfg(test)]
mod tests {
use std::cmp::Ordering;
use crate::cmp::PartialEqAny;
struct Wrap<T>(T);
impl<T: PartialEq + 'static> Wrap<T> {
fn token(&self) -> PartialEqAny {
PartialEqAny::new(&self.0)
}
}
#[test]
fn test_cmp_any() {
let w1 = Wrap(1);
let w2 = Wrap(1);
let w3 = Wrap(2);
assert_eq!(w1.token() == w2.token(), true);
assert_eq!(w1.token() == w3.token(), false);
let w4 = Wrap("foo");
let w5 = Wrap("foo");
let w6 = Wrap("bar");
assert_eq!(w4.token() == w5.token(), true);
assert_eq!(w4.token() == w6.token(), false);
assert_eq!(w1.token() == w6.token(), false);
}
#[test]
#[allow(clippy::eq_op)]
fn always_false_cmp() {
let w = Wrap(1);
let f = PartialEqAny::always_false();
assert_eq!(f == f, false);
assert_eq!(f == w.token(), false);
}
#[test]
fn cmp_eq_chain() {
struct FakeComparable(
Box<dyn Fn() -> Ordering>,
Box<dyn Fn() -> Ordering>,
Box<dyn Fn() -> Ordering>,
);
impl PartialEq for FakeComparable {
fn eq(&self, _other: &Self) -> bool {
eq_chain!(
(self.0)() == Ordering::Equal,
(self.1)() == Ordering::Equal,
(self.2)() == Ordering::Equal,
)
}
}
impl Eq for FakeComparable {}
impl PartialOrd for FakeComparable {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for FakeComparable {
fn cmp(&self, _other: &Self) -> Ordering {
cmp_chain! {
(self.0)(),
(self.1)(),
(self.2)(),
}
}
}
let fake = FakeComparable(
Box::new(|| Ordering::Less),
Box::new(|| unreachable!("should return less")),
Box::new(|| unreachable!("should return less")),
);
assert_eq!(fake.cmp(&fake), Ordering::Less);
assert_eq!(fake.eq(&fake), false);
let fake = FakeComparable(
Box::new(|| Ordering::Greater),
Box::new(|| unreachable!("should return less")),
Box::new(|| unreachable!("should return less")),
);
assert_eq!(fake.cmp(&fake), Ordering::Greater);
assert_eq!(fake.eq(&fake), false);
let fake = FakeComparable(
Box::new(|| Ordering::Equal),
Box::new(|| Ordering::Less),
Box::new(|| unreachable!("should return less")),
);
assert_eq!(fake.cmp(&fake), Ordering::Less);
assert_eq!(fake.eq(&fake), false);
let fake = FakeComparable(
Box::new(|| Ordering::Equal),
Box::new(|| Ordering::Equal),
Box::new(|| Ordering::Greater),
);
assert_eq!(fake.cmp(&fake), Ordering::Greater);
assert_eq!(fake.eq(&fake), false);
let fake = FakeComparable(
Box::new(|| Ordering::Equal),
Box::new(|| Ordering::Equal),
Box::new(|| Ordering::Equal),
);
assert_eq!(fake.cmp(&fake), Ordering::Equal);
assert_eq!(fake.eq(&fake), true);
}
}