diff --git a/CHANGELOG.md b/CHANGELOG.md index c88401d..27c10a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased -- None. +- **added:** Add `reflect_eq` # 0.1.15 (08. August, 2023) diff --git a/crates/mirror-mirror-macros/src/derive_reflect/attrs.rs b/crates/mirror-mirror-macros/src/derive_reflect/attrs.rs index ca500a8..143adcf 100644 --- a/crates/mirror-mirror-macros/src/derive_reflect/attrs.rs +++ b/crates/mirror-mirror-macros/src/derive_reflect/attrs.rs @@ -55,7 +55,9 @@ impl ItemAttrs { .filter(|attr| attr.meta.path().is_ident("reflect")) .peekable(); - let Some(attr) = reflect_attrs.next() else { return Ok(Self::new(docs)) }; + let Some(attr) = reflect_attrs.next() else { + return Ok(Self::new(docs)); + }; if let Some(next) = reflect_attrs.peek() { return Err(syn::Error::new_spanned( @@ -177,8 +179,12 @@ fn parse_docs(attrs: &[Attribute]) -> Vec { .filter(|attr| attr.meta.path().is_ident("doc")) .filter_map(|attr| { let name_value = attr.meta.require_name_value().ok()?; - let Expr::Lit(lit_expr) = &name_value.value else { return None }; - let Lit::Str(lit_str) = &lit_expr.lit else { return None }; + let Expr::Lit(lit_expr) = &name_value.value else { + return None; + }; + let Lit::Str(lit_str) = &lit_expr.lit else { + return None; + }; Some(lit_str.clone()) }) .collect::>() @@ -296,7 +302,9 @@ impl InnerAttrs { .filter(|attr| attr.meta.path().is_ident("reflect")) .peekable(); - let Some(attr) = reflect_attrs.next() else { return Ok(Self::new(docs)) }; + let Some(attr) = reflect_attrs.next() else { + return Ok(Self::new(docs)); + }; if let Some(next) = reflect_attrs.peek() { return Err(syn::Error::new_spanned( diff --git a/crates/mirror-mirror/src/foreign_impls/via_scalar.rs b/crates/mirror-mirror/src/foreign_impls/via_scalar.rs index b79308a..90ec17e 100644 --- a/crates/mirror-mirror/src/foreign_impls/via_scalar.rs +++ b/crates/mirror-mirror/src/foreign_impls/via_scalar.rs @@ -27,10 +27,12 @@ macro_rules! impl_reflect_via_scalar { impl Reflect for $ty { trivial_reflect_methods!(); + #[allow(clippy::redundant_closure_call)] fn reflect_owned(self: Box) -> ReflectOwned { ReflectOwned::Scalar(ScalarOwned::from($get_fn(&*self))) } + #[allow(clippy::redundant_closure_call)] fn reflect_ref(&self) -> ReflectRef<'_> { ReflectRef::Scalar(ScalarRef::from($get_fn(self))) } @@ -45,6 +47,7 @@ macro_rules! impl_reflect_via_scalar { } } + #[allow(clippy::redundant_closure_call)] fn to_value(&self) -> Value { $get_fn(self).to_value() } diff --git a/crates/mirror-mirror/src/lib.rs b/crates/mirror-mirror/src/lib.rs index 4b95462..6e01a61 100644 --- a/crates/mirror-mirror/src/lib.rs +++ b/crates/mirror-mirror/src/lib.rs @@ -334,6 +334,9 @@ pub mod value; pub mod try_visit; mod foreign_impls; +mod reflect_eq; + +pub use reflect_eq::reflect_eq; #[cfg(feature = "std")] #[cfg(test)] @@ -535,6 +538,12 @@ impl fmt::Debug for dyn Reflect { } } +impl PartialEq for dyn Reflect { + fn eq(&self, other: &Self) -> bool { + reflect_eq(self, other).unwrap_or(false) + } +} + macro_rules! impl_for_core_types { ($($ty:ident)*) => { $( @@ -972,7 +981,7 @@ impl<'a> ReflectRef<'a> { } /// An immutable reflected scalar value. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] #[allow(non_camel_case_types)] pub enum ScalarRef<'a> { usize(usize), diff --git a/crates/mirror-mirror/src/reflect_eq.rs b/crates/mirror-mirror/src/reflect_eq.rs new file mode 100644 index 0000000..e3b9413 --- /dev/null +++ b/crates/mirror-mirror/src/reflect_eq.rs @@ -0,0 +1,550 @@ +use crate::{ + enum_::{VariantField, VariantKind}, + Array, Enum, List, Map, Reflect, ReflectRef, Struct, Tuple, TupleStruct, +}; + +/// Compare two reflected values for equality. +/// +/// Returns `None` if either value contains a `ReflectRef::Opaque`. +pub fn reflect_eq(a: &dyn Reflect, b: &dyn Reflect) -> Option { + match (a.reflect_ref(), b.reflect_ref()) { + (ReflectRef::Scalar(a), ReflectRef::Scalar(b)) => Some(a == b), + (ReflectRef::Struct(a), ReflectRef::Struct(b)) => reflect_eq_struct(a, b), + (ReflectRef::TupleStruct(a), ReflectRef::TupleStruct(b)) => reflect_eq_tuple_struct(a, b), + (ReflectRef::Tuple(a), ReflectRef::Tuple(b)) => reflect_eq_tuple(a, b), + (ReflectRef::Enum(a), ReflectRef::Enum(b)) => reflect_eq_enum(a, b), + (ReflectRef::Array(a), ReflectRef::Array(b)) => reflect_eq_array(a, b), + (ReflectRef::List(a), ReflectRef::List(b)) => reflect_eq_list(a, b), + (ReflectRef::Map(a), ReflectRef::Map(b)) => reflect_eq_map(a, b), + (ReflectRef::Opaque(_), _) | (_, ReflectRef::Opaque(_)) => None, + + ( + ReflectRef::Struct(_) + | ReflectRef::Tuple(_) + | ReflectRef::Enum(_) + | ReflectRef::Array(_) + | ReflectRef::List(_) + | ReflectRef::Map(_) + | ReflectRef::Scalar(_), + ReflectRef::TupleStruct(_), + ) + | ( + ReflectRef::Struct(_) + | ReflectRef::TupleStruct(_) + | ReflectRef::Enum(_) + | ReflectRef::Array(_) + | ReflectRef::List(_) + | ReflectRef::Map(_) + | ReflectRef::Scalar(_), + ReflectRef::Tuple(_), + ) + | ( + ReflectRef::Struct(_) + | ReflectRef::TupleStruct(_) + | ReflectRef::Tuple(_) + | ReflectRef::Array(_) + | ReflectRef::List(_) + | ReflectRef::Map(_) + | ReflectRef::Scalar(_), + ReflectRef::Enum(_), + ) + | ( + ReflectRef::Struct(_) + | ReflectRef::TupleStruct(_) + | ReflectRef::Tuple(_) + | ReflectRef::Enum(_) + | ReflectRef::List(_) + | ReflectRef::Map(_) + | ReflectRef::Scalar(_), + ReflectRef::Array(_), + ) + | ( + ReflectRef::Struct(_) + | ReflectRef::TupleStruct(_) + | ReflectRef::Tuple(_) + | ReflectRef::Enum(_) + | ReflectRef::Array(_) + | ReflectRef::Map(_) + | ReflectRef::Scalar(_), + ReflectRef::List(_), + ) + | ( + ReflectRef::Struct(_) + | ReflectRef::TupleStruct(_) + | ReflectRef::Tuple(_) + | ReflectRef::Enum(_) + | ReflectRef::Array(_) + | ReflectRef::List(_) + | ReflectRef::Scalar(_), + ReflectRef::Map(_), + ) + | ( + ReflectRef::Struct(_) + | ReflectRef::TupleStruct(_) + | ReflectRef::Tuple(_) + | ReflectRef::Enum(_) + | ReflectRef::Array(_) + | ReflectRef::List(_) + | ReflectRef::Map(_), + ReflectRef::Scalar(_), + ) + | ( + ReflectRef::TupleStruct(_) + | ReflectRef::Tuple(_) + | ReflectRef::Enum(_) + | ReflectRef::Array(_) + | ReflectRef::List(_) + | ReflectRef::Map(_) + | ReflectRef::Scalar(_), + ReflectRef::Struct(_), + ) => Some(false), + } +} + +fn reflect_eq_struct(a: &dyn Struct, b: &dyn Struct) -> Option { + Some( + a.fields_len() == b.fields_len() && { + for (name, value_a) in a.fields() { + let Some(value_b) = b.field(name) else { + return Some(false); + }; + match reflect_eq(value_a, value_b) { + Some(true) => {} + Some(false) => { + return Some(false); + } + None => return None, + } + } + true + }, + ) +} + +fn reflect_eq_tuple_struct(a: &dyn TupleStruct, b: &dyn TupleStruct) -> Option { + Some( + a.fields_len() == b.fields_len() && { + for (value_a, value_b) in a.fields().zip(b.fields()) { + match reflect_eq(value_a, value_b) { + Some(true) => {} + Some(false) => { + return Some(false); + } + None => return None, + } + } + true + }, + ) +} + +fn reflect_eq_tuple(a: &dyn Tuple, b: &dyn Tuple) -> Option { + Some( + a.fields_len() == b.fields_len() && { + for (value_a, value_b) in a.fields().zip(b.fields()) { + match reflect_eq(value_a, value_b) { + Some(true) => {} + Some(false) => { + return Some(false); + } + None => return None, + } + } + true + }, + ) +} + +fn reflect_eq_enum(a: &dyn Enum, b: &dyn Enum) -> Option { + Some( + a.variant_name() == b.variant_name() && a.fields_len() == b.fields_len() && { + match (a.variant_kind(), b.variant_kind()) { + (VariantKind::Struct, VariantKind::Struct) => { + for field_a in a.fields() { + match field_a { + VariantField::Struct(name, value_a) => { + let Some(value_b) = b.field(name) else { + return Some(false); + }; + match reflect_eq(value_a, value_b) { + Some(true) => {} + Some(false) => { + return Some(false); + } + None => return None, + } + } + VariantField::Tuple(_) => return Some(false), + } + } + true + } + + (VariantKind::Tuple, VariantKind::Tuple) => { + for (field_a, field_b) in a.fields().zip(b.fields()) { + match (field_a, field_b) { + (VariantField::Tuple(value_a), VariantField::Tuple(value_b)) => { + match reflect_eq(value_a, value_b) { + Some(true) => {} + Some(false) => { + return Some(false); + } + None => return None, + } + } + ( + VariantField::Struct(_, _) | VariantField::Tuple(_), + VariantField::Struct(_, _), + ) + | (VariantField::Struct(_, _), VariantField::Tuple(_)) => { + return Some(false); + } + } + } + true + } + + (VariantKind::Unit, VariantKind::Unit) => { + return Some(true); + } + + (VariantKind::Unit | VariantKind::Tuple, VariantKind::Struct) + | (VariantKind::Unit | VariantKind::Struct, VariantKind::Tuple) + | (VariantKind::Struct | VariantKind::Tuple, VariantKind::Unit) => { + return Some(false); + } + } + }, + ) +} + +fn reflect_eq_array(a: &dyn Array, b: &dyn Array) -> Option { + Some( + a.len() == b.len() && { + for (value_a, value_b) in a.iter().zip(b.iter()) { + match reflect_eq(value_a, value_b) { + Some(true) => {} + Some(false) => { + return Some(false); + } + None => return None, + } + } + true + }, + ) +} + +fn reflect_eq_list(a: &dyn List, b: &dyn List) -> Option { + Some( + a.len() == b.len() && { + for (value_a, value_b) in a.iter().zip(b.iter()) { + match reflect_eq(value_a, value_b) { + Some(true) => {} + Some(false) => { + return Some(false); + } + None => return None, + } + } + true + }, + ) +} + +fn reflect_eq_map(a: &dyn Map, b: &dyn Map) -> Option { + Some( + a.len() == b.len() && { + for (key, value_a) in a.iter() { + let Some(value_b) = b.get(key) else { + return Some(false); + }; + match reflect_eq(value_a, value_b) { + Some(true) => {} + Some(false) => { + return Some(false); + } + None => return None, + } + } + true + }, + ) +} + +#[cfg(test)] +mod tests { + use alloc::collections::BTreeMap; + + use crate::{ + enum_::EnumValue, struct_::StructValue, tuple::TupleValue, tuple_struct::TupleStructValue, + }; + + #[allow(unused_imports)] + use super::*; + + #[test] + fn reflect_eq_scalar() { + assert!(reflect_eq(&1_usize, &1_usize).unwrap()); + assert!(!reflect_eq(&1_usize, &2_usize).unwrap()); + + assert!(reflect_eq(&1_u8, &1_u8).unwrap()); + assert!(!reflect_eq(&1_u8, &2_u8).unwrap()); + assert!(reflect_eq(&1_u16, &1_u16).unwrap()); + assert!(!reflect_eq(&1_u16, &2_u16).unwrap()); + assert!(reflect_eq(&1_u32, &1_u32).unwrap()); + assert!(!reflect_eq(&1_u32, &2_u32).unwrap()); + assert!(reflect_eq(&1_u64, &1_u64).unwrap()); + assert!(!reflect_eq(&1_u64, &2_u64).unwrap()); + assert!(reflect_eq(&1_u128, &1_u128).unwrap()); + assert!(!reflect_eq(&1_u128, &2_u128).unwrap()); + + assert!(reflect_eq(&1_i8, &1_i8).unwrap()); + assert!(!reflect_eq(&1_i8, &2_i8).unwrap()); + assert!(reflect_eq(&1_i16, &1_i16).unwrap()); + assert!(!reflect_eq(&1_i16, &2_i16).unwrap()); + assert!(reflect_eq(&1_i32, &1_i32).unwrap()); + assert!(!reflect_eq(&1_i32, &2_i32).unwrap()); + assert!(reflect_eq(&1_i64, &1_i64).unwrap()); + assert!(!reflect_eq(&1_i64, &2_i64).unwrap()); + assert!(reflect_eq(&1_i128, &1_i128).unwrap()); + assert!(!reflect_eq(&1_i128, &2_i128).unwrap()); + + assert!(reflect_eq(&true, &true).unwrap()); + assert!(reflect_eq(&false, &false).unwrap()); + assert!(!reflect_eq(&true, &false).unwrap()); + + assert!(reflect_eq(&'a', &'a').unwrap()); + assert!(!reflect_eq(&'a', &'b').unwrap()); + + assert!(reflect_eq(&1.0_f32, &1.0_f32).unwrap()); + assert!(!reflect_eq(&1.0_f32, &2.0_f32).unwrap()); + + assert!(reflect_eq(&1.0_f64, &1.0_f64).unwrap()); + assert!(!reflect_eq(&1.0_f64, &2.0_f64).unwrap()); + + assert!(reflect_eq(&String::from("a"), &String::from("a")).unwrap()); + assert!(!reflect_eq(&String::from("a"), &String::from("b")).unwrap()); + } + + #[test] + fn reflect_eq_struct() { + #[derive(Reflect, Debug, Clone)] + #[reflect(crate_name(crate))] + struct A { + foo: i32, + bar: bool, + } + + assert!(reflect_eq(&A { foo: 1, bar: true }, &A { foo: 1, bar: true }).unwrap()); + assert!(!reflect_eq(&A { foo: 2, bar: true }, &A { foo: 1, bar: true }).unwrap()); + assert!(reflect_eq( + &A { foo: 1, bar: true }, + &StructValue::new() + .with_field("foo", 1) + .with_field("bar", true) + ) + .unwrap()); + assert!(reflect_eq( + &A { foo: 1, bar: true }, + &StructValue::new() + .with_field("bar", true) + .with_field("foo", 1) + ) + .unwrap()); + assert!(!reflect_eq( + &A { foo: 1, bar: true }, + &StructValue::new() + .with_field("foo", 1) + .with_field("bar", true) + .with_field("baz", 123.0) + ) + .unwrap()); + assert!(!reflect_eq( + &StructValue::new() + .with_field("foo", 1) + .with_field("bar", true) + .with_field("baz", 123.0), + &A { foo: 1, bar: true }, + ) + .unwrap()); + assert!(!reflect_eq( + &StructValue::new() + .with_field("foo", 1) + .with_field("baz", 123.0), + &A { foo: 1, bar: true }, + ) + .unwrap()); + assert!(!reflect_eq( + &A { foo: 1, bar: true }, + &StructValue::new() + .with_field("foo", 1) + .with_field("baz", 123.0), + ) + .unwrap()); + } + + #[test] + fn reflect_eq_tuple_struct() { + #[derive(Reflect, Debug, Clone)] + #[reflect(crate_name(crate))] + struct A(i32, bool); + + assert!(reflect_eq(&A(1, true), &A(1, true)).unwrap()); + assert!(!reflect_eq(&A(2, true), &A(1, true)).unwrap()); + assert!(reflect_eq( + &A(1, true), + &TupleStructValue::new().with_field(1).with_field(true) + ) + .unwrap()); + // ordering does matter + assert!(!reflect_eq( + &A(1, true), + &TupleStructValue::new().with_field(true).with_field(1) + ) + .unwrap()); + assert!(!reflect_eq( + &A(1, true), + &TupleStructValue::new() + .with_field(true) + .with_field(1) + .with_field(1) + ) + .unwrap()); + } + + #[test] + fn reflect_eq_tuple() { + assert!(reflect_eq(&(1, true), &(1, true)).unwrap()); + assert!(!reflect_eq(&(2, true), &(1, true)).unwrap()); + assert!(reflect_eq( + &(1, true), + &TupleValue::new().with_field(1).with_field(true) + ) + .unwrap()); + // ordering does matter + assert!(!reflect_eq( + &(1, true), + &TupleValue::new().with_field(true).with_field(1) + ) + .unwrap()); + assert!(!reflect_eq( + &(1, true), + &TupleValue::new() + .with_field(true) + .with_field(1) + .with_field(1) + ) + .unwrap()); + } + + #[test] + fn reflect_eq_enum() { + #[derive(Reflect, Debug, Clone)] + #[reflect(crate_name(crate))] + enum A { + Struct { a: i32, b: bool }, + Tuple(i32, bool), + Unit, + } + + assert!(reflect_eq(&A::Struct { a: 1, b: true }, &A::Struct { a: 1, b: true }).unwrap()); + assert!(reflect_eq( + &A::Struct { a: 1, b: true }, + &EnumValue::new_struct_variant("Struct") + .with_struct_field("a", 1) + .with_struct_field("b", true) + .finish() + ) + .unwrap()); + assert!(reflect_eq( + &A::Struct { a: 1, b: true }, + &EnumValue::new_struct_variant("Struct") + // field order doesn't matter + .with_struct_field("b", true) + .with_struct_field("a", 1) + .finish() + ) + .unwrap()); + assert!(!reflect_eq( + &A::Struct { a: 1, b: true }, + // must have the same variant name + &EnumValue::new_struct_variant("NotStruct") + .with_struct_field("a", 1) + .with_struct_field("b", true) + .finish() + ) + .unwrap()); + assert!(!reflect_eq(&A::Struct { a: 1, b: false }, &A::Struct { a: 1, b: true }).unwrap()); + assert!(!reflect_eq(&A::Struct { a: 1, b: false }, &A::Unit).unwrap()); + assert!(reflect_eq(&A::Tuple(1, true), &A::Tuple(1, true)).unwrap()); + assert!(!reflect_eq(&A::Tuple(1, true), &A::Tuple(1, false)).unwrap()); + assert!(reflect_eq( + &A::Tuple(1, true), + &EnumValue::new_tuple_variant("Tuple") + .with_tuple_field(1) + .with_tuple_field(true) + .finish() + ) + .unwrap()); + assert!(!reflect_eq( + &A::Tuple(1, true), + &EnumValue::new_tuple_variant("Tuple") + .with_tuple_field(true) + .with_tuple_field(1) + .finish() + ) + .unwrap()); + assert!(!reflect_eq( + &A::Tuple(1, true), + &EnumValue::new_tuple_variant("NotTuple") + .with_tuple_field(1) + .with_tuple_field(true) + .finish() + ) + .unwrap()); + assert!(reflect_eq(&A::Unit, &A::Unit).unwrap()); + assert!(reflect_eq(&A::Unit, &EnumValue::new_unit_variant("Unit")).unwrap()); + assert!(!reflect_eq(&A::Unit, &EnumValue::new_unit_variant("NotUnit")).unwrap()); + } + + #[test] + fn reflect_eq_array() { + assert!(reflect_eq(&[1, 2, 3], &[1, 2, 3]).unwrap()); + assert!(!reflect_eq(&[1, 2, 3], &[1, 2, 3, 4]).unwrap()); + assert!(!reflect_eq(&[1, 2, 3, 4], &[1, 2, 3]).unwrap()); + assert!(!reflect_eq(&[1, 2, 3], &[1, 2, 4]).unwrap()); + } + + #[test] + fn reflect_eq_list() { + assert!(reflect_eq(&vec![1, 2, 3], &vec![1, 2, 3]).unwrap()); + assert!(!reflect_eq(&vec![1, 2, 3], &vec![1, 2, 3, 4]).unwrap()); + assert!(!reflect_eq(&vec![1, 2, 3, 4], &vec![1, 2, 3]).unwrap()); + assert!(!reflect_eq(&vec![1, 2, 3], &vec![1, 2, 4]).unwrap()); + } + + #[test] + fn reflect_eq_map() { + assert!(reflect_eq( + &BTreeMap::from([("a".to_owned(), 1), ("b".to_owned(), 2),]), + &BTreeMap::from([("a".to_owned(), 1), ("b".to_owned(), 2),]), + ) + .unwrap()); + assert!(reflect_eq( + &BTreeMap::from([("a".to_owned(), 1), ("b".to_owned(), 2),]), + &BTreeMap::from([("b".to_owned(), 2), ("a".to_owned(), 1),]), + ) + .unwrap()); + assert!(!reflect_eq( + &BTreeMap::from([("a".to_owned(), 2), ("b".to_owned(), 2),]), + &BTreeMap::from([("b".to_owned(), 2), ("a".to_owned(), 1),]), + ) + .unwrap()); + assert!(!reflect_eq( + &BTreeMap::from([("a".to_owned(), 1), ("b".to_owned(), 2),]), + &BTreeMap::from([("a".to_owned(), 1),]), + ) + .unwrap()); + } +}