use facet::Facet;
use facet_core::{Def, StructKind, Type, UserType};
use facet_reflect::{HasFields, Peek};
use std::any::Any;
pub fn facet_eq<V>(a: &dyn Any, b: &dyn Any) -> bool
where
V: Facet<'static> + 'static,
{
let Some(a) = a.downcast_ref::<V>() else {
return false;
};
let Some(b) = b.downcast_ref::<V>() else {
return false;
};
facet_eq_direct(a, b)
}
#[inline]
pub fn facet_eq_direct<V>(a: &V, b: &V) -> bool
where
V: Facet<'static> + 'static,
{
let peek_a = Peek::new(a);
let peek_b = Peek::new(b);
peek_eq(peek_a, peek_b)
}
fn peek_eq<'mem, 'facet>(a: Peek<'mem, 'facet>, b: Peek<'mem, 'facet>) -> bool {
if a.shape() != b.shape() {
return false;
}
match a.shape().def {
Def::Option(_) => {
let Ok(opt_a) = a.into_option() else {
return false;
};
let Ok(opt_b) = b.into_option() else {
return false;
};
return match (opt_a.value(), opt_b.value()) {
(Some(inner_a), Some(inner_b)) => peek_eq(inner_a, inner_b),
(None, None) => true,
_ => false,
};
}
Def::Result(_) => {
let Ok(result_a) = a.into_result() else {
return false;
};
let Ok(result_b) = b.into_result() else {
return false;
};
return match (result_a.ok(), result_b.ok()) {
(Some(ok_a), Some(ok_b)) => peek_eq(ok_a, ok_b),
(None, None) => match (result_a.err(), result_b.err()) {
(Some(err_a), Some(err_b)) => peek_eq(err_a, err_b),
_ => false,
},
_ => false,
};
}
_ => {}
}
match a.shape().ty {
Type::User(UserType::Struct(_)) => {
let Ok(struct_a) = a.into_struct() else {
return false;
};
let Ok(struct_b) = b.into_struct() else {
return false;
};
let fields_a: Vec<_> = struct_a
.fields()
.filter(|(field, _)| !field.is_metadata())
.collect();
let fields_b: Vec<_> = struct_b
.fields()
.filter(|(field, _)| !field.is_metadata())
.collect();
if fields_a.len() != fields_b.len() {
return false;
}
for ((field_a, peek_a), (field_b, peek_b)) in fields_a.iter().zip(fields_b.iter()) {
if field_a.name != field_b.name {
return false;
}
if !peek_eq(*peek_a, *peek_b) {
return false;
}
}
true
}
Type::User(UserType::Enum(_)) => {
let Ok(enum_a) = a.into_enum() else {
return false;
};
let Ok(enum_b) = b.into_enum() else {
return false;
};
let Ok(variant_a) = enum_a.active_variant() else {
return false;
};
let Ok(variant_b) = enum_b.active_variant() else {
return false;
};
if variant_a.name != variant_b.name {
return false;
}
match variant_a.data.kind {
StructKind::Unit => {
true
}
StructKind::Tuple | StructKind::TupleStruct => {
if variant_a.data.fields.len() != variant_b.data.fields.len() {
return false;
}
for i in 0..variant_a.data.fields.len() {
let Ok(Some(field_a)) = enum_a.field(i) else {
return false;
};
let Ok(Some(field_b)) = enum_b.field(i) else {
return false;
};
if !peek_eq(field_a, field_b) {
return false;
}
}
true
}
StructKind::Struct => {
for field in variant_a.data.fields {
let Ok(Some(field_a)) = enum_a.field_by_name(field.name) else {
return false;
};
let Ok(Some(field_b)) = enum_b.field_by_name(field.name) else {
return false;
};
if !peek_eq(field_a, field_b) {
return false;
}
}
true
}
}
}
_ => {
match a.shape().def {
Def::List(_) | Def::Array(_) | Def::Slice(_) => {
let Ok(list_a) = a.into_list() else {
return false;
};
let Ok(list_b) = b.into_list() else {
return false;
};
if list_a.len() != list_b.len() {
return false;
}
for i in 0..list_a.len() {
let Some(elem_a) = list_a.get(i) else {
return false;
};
let Some(elem_b) = list_b.get(i) else {
return false;
};
if !peek_eq(elem_a, elem_b) {
return false;
}
}
true
}
Def::Map(_) => {
let Ok(map_a) = a.into_map() else {
return false;
};
let Ok(map_b) = b.into_map() else {
return false;
};
if map_a.len() != map_b.len() {
return false;
}
for (key_a, value_a) in map_a.iter() {
let Ok(Some(value_b)) = map_b.get_peek(key_a) else {
return false; };
if !peek_eq(value_a, value_b) {
return false;
}
}
true
}
Def::Set(_) => {
let Ok(set_a) = a.into_set() else {
return false;
};
let Ok(set_b) = b.into_set() else {
return false;
};
if set_a.len() != set_b.len() {
return false;
}
for elem_a in set_a.iter() {
let Ok(true) = set_b.contains_peek(elem_a) else {
return false; };
}
true
}
_ => {
if let Ok(result) = a.partial_eq(&b) {
return result;
}
false
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use facet::Facet;
use std::collections::HashMap;
#[allow(clippy::derived_hash_with_manual_eq)]
#[derive(Facet, Debug, Clone, Hash, Eq)]
struct NoPartialEq {
value: i32,
}
impl PartialEq for NoPartialEq {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
#[derive(Facet, Debug, Clone)]
struct TrulyNoEq {
data: i32,
}
#[derive(Facet, Debug, Clone, PartialEq)]
struct WithPartialEq {
value: i32,
}
#[derive(Facet, Debug, Clone, PartialEq)]
struct Container {
items: Vec<WithPartialEq>,
}
#[derive(Facet, Debug, Clone)]
struct ContainerNoEq {
items: Vec<TrulyNoEq>,
}
#[test]
fn test_simple_struct_equality() {
let a = WithPartialEq { value: 42 };
let b = WithPartialEq { value: 42 };
let c = WithPartialEq { value: 99 };
assert!(facet_eq_direct(&a, &b));
assert!(!facet_eq_direct(&a, &c));
}
#[test]
fn test_vec_with_partial_eq_elements() {
let a = Container {
items: vec![WithPartialEq { value: 1 }, WithPartialEq { value: 2 }],
};
let b = Container {
items: vec![WithPartialEq { value: 1 }, WithPartialEq { value: 2 }],
};
let c = Container {
items: vec![WithPartialEq { value: 1 }, WithPartialEq { value: 3 }],
};
assert!(facet_eq_direct(&a, &b));
assert!(!facet_eq_direct(&a, &c));
}
#[test]
fn test_vec_without_partial_eq_elements() {
let a = ContainerNoEq {
items: vec![TrulyNoEq { data: 1 }, TrulyNoEq { data: 2 }],
};
let b = ContainerNoEq {
items: vec![TrulyNoEq { data: 1 }, TrulyNoEq { data: 2 }],
};
let c = ContainerNoEq {
items: vec![TrulyNoEq { data: 1 }, TrulyNoEq { data: 3 }],
};
assert!(facet_eq_direct(&a, &b), "equal containers should be equal");
assert!(
!facet_eq_direct(&a, &c),
"different containers should not be equal"
);
}
#[test]
fn test_hashmap_equality() {
let mut a: HashMap<String, i32> = HashMap::new();
a.insert("one".to_string(), 1);
a.insert("two".to_string(), 2);
let mut b: HashMap<String, i32> = HashMap::new();
b.insert("two".to_string(), 2);
b.insert("one".to_string(), 1);
let mut c: HashMap<String, i32> = HashMap::new();
c.insert("one".to_string(), 1);
c.insert("two".to_string(), 99);
assert!(facet_eq_direct(&a, &b), "same maps should be equal");
assert!(
!facet_eq_direct(&a, &c),
"different values should not be equal"
);
}
#[test]
fn test_option_equality() {
let a: Option<i32> = Some(42);
let b: Option<i32> = Some(42);
let c: Option<i32> = Some(99);
let d: Option<i32> = None;
assert!(facet_eq_direct(&a, &b));
assert!(!facet_eq_direct(&a, &c));
assert!(!facet_eq_direct(&a, &d));
assert!(facet_eq_direct(&d, &d));
}
#[test]
fn test_nested_containers() {
let a: Vec<Vec<i32>> = vec![vec![1, 2], vec![3, 4]];
let b: Vec<Vec<i32>> = vec![vec![1, 2], vec![3, 4]];
let c: Vec<Vec<i32>> = vec![vec![1, 2], vec![3, 5]];
assert!(facet_eq_direct(&a, &b));
assert!(!facet_eq_direct(&a, &c));
}
#[test]
fn test_truly_no_eq_vtable_check() {
let a = TrulyNoEq { data: 42 };
let peek = Peek::new(&a);
let result = peek.partial_eq(&peek);
assert!(
result.is_err(),
"TrulyNoEq should NOT have PartialEq vtable, but got: {:?}",
result
);
}
}