From 566b76c2925df8e56368656a638aad5147c7b1db Mon Sep 17 00:00:00 2001 From: Gil Sharon Date: Wed, 18 Feb 2026 16:48:17 -0500 Subject: [PATCH 1/5] chore: add hash to entity macro derives --- docs/book/src/migration_guide.md | 2 +- examples/load-people/vaccine.rs | 4 +- .../population_loader.rs | 8 +- src/entity/property_impl_tests.rs | 16 ++-- src/macros/property_impl.rs | 73 ++++++++++--------- 5 files changed, 57 insertions(+), 46 deletions(-) diff --git a/docs/book/src/migration_guide.md b/docs/book/src/migration_guide.md index b20ad4da..1cb257a5 100644 --- a/docs/book/src/migration_guide.md +++ b/docs/book/src/migration_guide.md @@ -56,7 +56,7 @@ variant: ```rust // The downside is, we have to make sure the property type implements all the -// traits a property needs. +// traits a property needs (and optionally any extras we want). #[derive(Copy, Clone, Debug, PartialEq, Serialize)] pub enum InfectionStatus { Susceptible, diff --git a/examples/load-people/vaccine.rs b/examples/load-people/vaccine.rs index 9ab4c8ef..0de3aaff 100644 --- a/examples/load-people/vaccine.rs +++ b/examples/load-people/vaccine.rs @@ -12,7 +12,9 @@ define_property!( }, Person ); -define_property!(struct VaccineEfficacy(f64), Person); +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] +pub struct VaccineEfficacy(pub f64); +impl_property!(VaccineEfficacy, Person); define_property!(struct VaccineDoses(u8), Person); pub trait ContextVaccineExt: ContextRandomExt { diff --git a/examples/time-varying-infection/population_loader.rs b/examples/time-varying-infection/population_loader.rs index f235c51f..7177ed3f 100644 --- a/examples/time-varying-infection/population_loader.rs +++ b/examples/time-varying-infection/population_loader.rs @@ -14,11 +14,9 @@ define_property!( default_const = DiseaseStatus::S ); -define_property!( - struct InfectionTime(Option), - Person, - default_const = InfectionTime(None) -); +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] +pub struct InfectionTime(pub Option); +impl_property!(InfectionTime, Person, default_const = InfectionTime(None)); pub fn init(context: &mut Context) { let parameters = context diff --git a/src/entity/property_impl_tests.rs b/src/entity/property_impl_tests.rs index 5a00e72a..5971e152 100644 --- a/src/entity/property_impl_tests.rs +++ b/src/entity/property_impl_tests.rs @@ -14,14 +14,18 @@ define_property!(struct Pu32(u32), Person, default_const = Pu32(0)); define_property!(struct POu32(Option), Person, default_const = POu32(None)); define_property!(struct Name(&'static str), Person, default_const = Name("")); define_property!(struct Age(u8), Person, default_const = Age(0)); -define_property!(struct Weight(f64), Person, default_const = Weight(0.0)); +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] +struct Weight(f64); +impl_property!(Weight, Person, default_const = Weight(0.0)); // A struct with named fields -define_property!( - struct Innocculation { - time: f64, - dose: u8, - }, +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] +struct Innocculation { + time: f64, + dose: u8, +} +impl_property!( + Innocculation, Person, default_const = Innocculation { time: 0.0, dose: 0 } ); diff --git a/src/macros/property_impl.rs b/src/macros/property_impl.rs index 4ab57deb..1a2b6206 100644 --- a/src/macros/property_impl.rs +++ b/src/macros/property_impl.rs @@ -5,7 +5,7 @@ Macros for implementing properties. # [`define_property!`][macro@crate::define_property] For the most common cases, use the [`define_property!`][macro@crate::define_property] macro. This macro defines a struct or enum -with the standard derives required by the [`Property`][crate::entity::property::Property] trait and implements [`Property`][crate::entity::property::Property] (via +with a standard set of derives and implements [`Property`][crate::entity::property::Property] (via [`impl_property!`][macro@crate::impl_property]) for you. ```rust,ignore @@ -26,8 +26,8 @@ Notice the convenient `default_const = ` keyword argument that al define a compile-time constant default value for the property. This is an optional argument. If it is omitted, a value for the property must be supplied upon entity creation. -The primary advantage of using this macro is that it automatically derives the list of traits every -[`Property`][crate::entity::property::Property] needs to derive for you. You don't have to remember them. You also get a cute syntax for +The primary advantage of using this macro is that it automatically derives the standard property traits for you. You +don't have to remember them. You also get a cute syntax for specifying the default value, but it's not much harder to specify default values using other macros. Notice you need to use the `struct` or `enum` keywords, but you don't need to @@ -38,7 +38,9 @@ and to inner fields of tuple structs in the expansion. You can implement [`Property`][crate::entity::property::Property] for existing types using the [`impl_property!`][macro@crate::impl_property] macro. This macro defines the [`Property`][crate::entity::property::Property] trait implementation for you but doesn't take care of the `#[derive(..)]` boilerplate, so you -have to remember to `derive` all of `Copy, Clone, Debug, PartialEq, Serialize` in your type declaration. +have to remember to derive the traits your type needs. At minimum, property types must satisfy the +[`AnyProperty`][crate::entity::property::AnyProperty] bound (`Copy`, `Clone`, `Debug`, `PartialEq`, `Serialize`). +If you want consistency with the auto-generated `define_property!` derives, also add `Deserialize` and `Hash`. Some examples: @@ -48,15 +50,16 @@ define_entity!(Person); // The `define_property!` automatically adds `pub` visibility to the struct and its tuple // fields. If we want to restrict the visibility of our `Property` type, we can use the // `impl_property!` macro instead. The only -// catch is, we have to remember to `derive` all of `Copy, Clone, Debug, PartialEq, Serialize`. -#[derive(Copy, Clone, Debug, PartialEq, Serialize)] +// catch is, we have to remember to `derive` all of +// `Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Hash`. +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Hash)] struct Age(pub u8); impl_property!(Age, Person); // Here we derive `Default`, which also requires an attribute on one // of the variants. (`Property` has its own independent mechanism for // assigning default values for entities unrelated to the `Default` trait.) -#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize)] +#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, Hash)] enum InfectionStatus { #[default] Susceptible, @@ -68,7 +71,7 @@ impl_property!(InfectionStatus, Person, default_const = InfectionStatus::Suscept // Exactly equivalent to // `define_property!(struct Vaccinated(pub bool), Person, default_const = Vaccinated(false));` -#[derive(Copy, Clone, Debug, PartialEq, Serialize)] +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Hash)] pub struct Vaccinated(pub bool); impl_property!(Vaccinated, Person, default_const = Vaccinated(false)); ``` @@ -102,10 +105,10 @@ you also must provide a conversion function to and from the canonical type. ```rust,ignore define_entity!(WeatherStation); -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Hash)] pub struct DegreesFahrenheit(pub f64); -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Hash)] pub struct DegreesCelsius(pub f64); // Custom canonical type @@ -136,9 +139,9 @@ impl_property!( /// Expands to: /// ```rust /// # use ixa::{impl_property, define_entity}; -/// # use serde::Serialize; +/// # use serde::{Deserialize, Serialize}; /// # define_entity!(Person); -/// #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)] +/// #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Hash)] /// pub struct Age(u8); /// impl_property!(Age, Person); /// ``` @@ -157,9 +160,9 @@ impl_property!( /// Expands to: /// ```rust /// # use ixa::{impl_property, define_entity}; -/// # use serde::Serialize; +/// # use serde::{Deserialize, Serialize}; /// # define_entity!(Person); -/// #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)] +/// #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Hash)] /// pub struct Coordinates { x: i32, y: i32 } /// impl_property!(Coordinates, Person); /// ``` @@ -180,9 +183,9 @@ impl_property!( /// Expands to: /// ```rust /// # use ixa::{impl_property, define_entity}; -/// # use serde::Serialize; +/// # use serde::{Deserialize, Serialize}; /// # define_entity!(Person); -/// #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)] +/// #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Hash)] /// pub enum InfectionStatus { /// Susceptible, /// Infectious, @@ -194,7 +197,7 @@ impl_property!( /// ### Notes /// /// - The generated type always derives the following traits: -/// `Debug`, `PartialEq`, `Eq`, `Clone`, `Copy`, and `Serialize`. +/// `Debug`, `PartialEq`, `Clone`, `Copy`, `Serialize`, `Deserialize`, and `Hash`. /// - Use the optional `default_const = ` argument to define a compile-time constant /// default for the property. /// - If you need a more complex type definition (e.g., generics, attributes, or non-`Copy` @@ -207,7 +210,7 @@ macro_rules! define_property { $entity:ident $(, $($extra:tt)+),* ) => { - #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)] + #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, Hash)] pub struct $name(pub Option<$inner_ty>); // Use impl_property! to provide a custom display implementation @@ -230,7 +233,7 @@ macro_rules! define_property { $entity:ident $(, $($extra:tt)+),* ) => { - #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)] + #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, Hash)] pub struct $name($(pub $field_ty),*); $crate::impl_property!($name, $entity $(, $($extra)+)*); }; @@ -241,7 +244,7 @@ macro_rules! define_property { $entity:ident $(, $($extra:tt)+),* ) => { - #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)] + #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, Hash)] pub struct $name { $(pub $field_name : $field_ty),* } $crate::impl_property!($name, $entity $(, $($extra)+)*); }; @@ -254,7 +257,7 @@ macro_rules! define_property { $entity:ident $(, $($extra:tt)+),* ) => { - #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)] + #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, Hash)] pub enum $name { $($variant),* } @@ -278,9 +281,9 @@ macro_rules! define_property { /// /// ```rust /// # use ixa::{impl_property, define_entity}; -/// # use serde::Serialize; +/// # use serde::{Deserialize, Serialize}; /// # define_entity!(Person); -/// #[derive(Default, Debug, PartialEq, Eq, Clone, Copy, Serialize)] +/// #[derive(Default, Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Hash)] /// pub enum InfectionStatus { /// #[default] /// Susceptible, @@ -539,7 +542,7 @@ macro_rules! define_derived_property { // For `canonical_value` implementations: $(, $($extra:tt)+),* ) => { - #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize)] + #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize, Hash)] pub struct $name(pub Option<$inner_ty>); // Use impl_derived_property! to provide a custom display implementation @@ -569,7 +572,7 @@ macro_rules! define_derived_property { // For `canonical_value` implementations: $(, $($extra:tt)+),* ) => { - #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize)] + #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize, Hash)] pub struct $name( $(pub $field_ty),* ); $crate::impl_derived_property!( @@ -592,7 +595,7 @@ macro_rules! define_derived_property { // For `canonical_value` implementations: $(, $($extra:tt)+),* ) => { - #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize)] + #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize, Hash)] pub struct $name { $($visibility $field_name : $field_ty),* } $crate::impl_derived_property!( @@ -617,7 +620,7 @@ macro_rules! define_derived_property { // For `canonical_value` implementations: $(, $($extra:tt)+),* ) => { - #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize)] + #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize, Hash)] pub enum $name { $($variant),* } @@ -836,14 +839,18 @@ mod tests { define_property!(struct POu32(Option), Person, default_const = POu32(None)); define_property!(struct Name(&'static str), Person, default_const = Name("")); define_property!(struct Age(u8), Person, default_const = Age(0)); - define_property!(struct Weight(f64), Person, default_const = Weight(0.0)); + #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] + struct Weight(f64); + impl_property!(Weight, Person, default_const = Weight(0.0)); // A struct with named fields - define_property!( - struct Innocculation { - time: f64, - dose: u8, - }, + #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] + struct Innocculation { + time: f64, + dose: u8, + } + impl_property!( + Innocculation, Person, default_const = Innocculation { time: 0.0, dose: 0 } ); From e4caa69048cc8b89690d59ecc04224e7296c1206 Mon Sep 17 00:00:00 2001 From: Gil Sharon Date: Wed, 18 Feb 2026 16:52:17 -0500 Subject: [PATCH 2/5] chore: reverse doc --- docs/book/src/migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/src/migration_guide.md b/docs/book/src/migration_guide.md index 1cb257a5..b20ad4da 100644 --- a/docs/book/src/migration_guide.md +++ b/docs/book/src/migration_guide.md @@ -56,7 +56,7 @@ variant: ```rust // The downside is, we have to make sure the property type implements all the -// traits a property needs (and optionally any extras we want). +// traits a property needs. #[derive(Copy, Clone, Debug, PartialEq, Serialize)] pub enum InfectionStatus { Susceptible, From c2621a6e1f086b5013ce3ea05938eb19d9161be1 Mon Sep 17 00:00:00 2001 From: Gil Sharon Date: Fri, 27 Feb 2026 15:07:43 -0500 Subject: [PATCH 3/5] chore: property macros, define Eq/Hash/PartialEq via canonical property hash --- src/macros/property_impl.rs | 99 ++++++++++++++++++++++++++++++++----- 1 file changed, 86 insertions(+), 13 deletions(-) diff --git a/src/macros/property_impl.rs b/src/macros/property_impl.rs index 1a2b6206..1a11ac5c 100644 --- a/src/macros/property_impl.rs +++ b/src/macros/property_impl.rs @@ -141,7 +141,7 @@ impl_property!( /// # use ixa::{impl_property, define_entity}; /// # use serde::{Deserialize, Serialize}; /// # define_entity!(Person); -/// #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Hash)] +/// #[derive(Debug, Clone, Copy, Serialize, Deserialize)] /// pub struct Age(u8); /// impl_property!(Age, Person); /// ``` @@ -162,7 +162,7 @@ impl_property!( /// # use ixa::{impl_property, define_entity}; /// # use serde::{Deserialize, Serialize}; /// # define_entity!(Person); -/// #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Hash)] +/// #[derive(Debug, Clone, Copy, Serialize, Deserialize)] /// pub struct Coordinates { x: i32, y: i32 } /// impl_property!(Coordinates, Person); /// ``` @@ -185,7 +185,7 @@ impl_property!( /// # use ixa::{impl_property, define_entity}; /// # use serde::{Deserialize, Serialize}; /// # define_entity!(Person); -/// #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Hash)] +/// #[derive(Debug, Clone, Copy, Serialize, Deserialize)] /// pub enum InfectionStatus { /// Susceptible, /// Infectious, @@ -196,8 +196,10 @@ impl_property!( /// /// ### Notes /// -/// - The generated type always derives the following traits: -/// `Debug`, `PartialEq`, `Clone`, `Copy`, `Serialize`, `Deserialize`, and `Hash`. +/// - The generated type derives `Debug`, `Clone`, `Copy`, `Serialize`, and `Deserialize`. +/// - The generated type also implements `PartialEq`, `Eq`, and `Hash` in terms of +/// [`Property::hash_property_value`](crate::entity::property::Property::hash_property_value) +/// applied to canonical values. /// - Use the optional `default_const = ` argument to define a compile-time constant /// default for the property. /// - If you need a more complex type definition (e.g., generics, attributes, or non-`Copy` @@ -210,7 +212,7 @@ macro_rules! define_property { $entity:ident $(, $($extra:tt)+),* ) => { - #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, Hash)] + #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] pub struct $name(pub Option<$inner_ty>); // Use impl_property! to provide a custom display implementation @@ -225,6 +227,7 @@ macro_rules! define_property { } } ); + $crate::impl_property_value_traits!($name, $entity); }; // Struct (tuple) @@ -233,9 +236,10 @@ macro_rules! define_property { $entity:ident $(, $($extra:tt)+),* ) => { - #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, Hash)] + #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] pub struct $name($(pub $field_ty),*); $crate::impl_property!($name, $entity $(, $($extra)+)*); + $crate::impl_property_value_traits!($name, $entity); }; // Struct (named fields) @@ -244,9 +248,10 @@ macro_rules! define_property { $entity:ident $(, $($extra:tt)+),* ) => { - #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, Hash)] + #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] pub struct $name { $(pub $field_name : $field_ty),* } $crate::impl_property!($name, $entity $(, $($extra)+)*); + $crate::impl_property_value_traits!($name, $entity); }; // Enum @@ -257,11 +262,45 @@ macro_rules! define_property { $entity:ident $(, $($extra:tt)+),* ) => { - #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, Hash)] + #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] pub enum $name { $($variant),* } $crate::impl_property!($name, $entity $(, $($extra)+)*); + $crate::impl_property_value_traits!($name, $entity); + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! impl_property_value_traits { + ($property:ident, $entity:ident) => { + impl std::cmp::PartialEq for $property { + fn eq(&self, other: &Self) -> bool { + let lhs = + >::make_canonical(*self); + let rhs = + >::make_canonical(*other); + >::hash_property_value(&lhs) + == >::hash_property_value( + &rhs, + ) + } + } + + impl std::cmp::Eq for $property {} + + impl std::hash::Hash for $property { + fn hash(&self, state: &mut H) { + let canonical = + >::make_canonical(*self); + let hash = + >::hash_property_value( + &canonical, + ); + state.write_u128(hash); + } + } }; } @@ -542,7 +581,7 @@ macro_rules! define_derived_property { // For `canonical_value` implementations: $(, $($extra:tt)+),* ) => { - #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize, Hash)] + #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] pub struct $name(pub Option<$inner_ty>); // Use impl_derived_property! to provide a custom display implementation @@ -560,6 +599,7 @@ macro_rules! define_derived_property { } $(, $($extra)+)* ); + $crate::impl_property_value_traits!($name, $entity); }; // Struct (tuple) @@ -572,7 +612,7 @@ macro_rules! define_derived_property { // For `canonical_value` implementations: $(, $($extra:tt)+),* ) => { - #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize, Hash)] + #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] pub struct $name( $(pub $field_ty),* ); $crate::impl_derived_property!( @@ -583,6 +623,7 @@ macro_rules! define_derived_property { |$($param),+| $derive_fn $(, $($extra)+)* ); + $crate::impl_property_value_traits!($name, $entity); }; // Struct (named fields) @@ -595,7 +636,7 @@ macro_rules! define_derived_property { // For `canonical_value` implementations: $(, $($extra:tt)+),* ) => { - #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize, Hash)] + #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] pub struct $name { $($visibility $field_name : $field_ty),* } $crate::impl_derived_property!( @@ -606,6 +647,7 @@ macro_rules! define_derived_property { |$($param),+| $derive_fn $(, $($extra)+)* ); + $crate::impl_property_value_traits!($name, $entity); }; // Enum @@ -620,7 +662,7 @@ macro_rules! define_derived_property { // For `canonical_value` implementations: $(, $($extra:tt)+),* ) => { - #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize, Hash)] + #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] pub enum $name { $($variant),* } @@ -633,6 +675,7 @@ macro_rules! define_derived_property { |$($param),+| $derive_fn $(, $($extra)+)* ); + $crate::impl_property_value_traits!($name, $entity); }; // Internal branch to construct the compute function. @@ -839,6 +882,17 @@ mod tests { define_property!(struct POu32(Option), Person, default_const = POu32(None)); define_property!(struct Name(&'static str), Person, default_const = Name("")); define_property!(struct Age(u8), Person, default_const = Age(0)); + #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] + struct Signed(i32); + impl_property!( + Signed, + Person, + default_const = Signed(0), + canonical_value = u32, + make_canonical = |v: Signed| v.0.unsigned_abs(), + make_uncanonical = |v: u32| Signed(v as i32) + ); + impl_property_value_traits!(Signed, Person); #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] struct Weight(f64); impl_property!(Weight, Person, default_const = Weight(0.0)); @@ -1093,4 +1147,23 @@ mod tests { let debug_str = format!("{:?}", property); assert_eq!(debug_str, "POu32(Some(22))"); } + + #[test] + fn test_equality_and_hash_use_canonical_hash() { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let left = Signed(-7); + let right = Signed(7); + + assert_eq!(Signed::make_canonical(left), Signed::make_canonical(right)); + assert_eq!(left, right); + + let mut left_hasher = DefaultHasher::new(); + left.hash(&mut left_hasher); + let mut right_hasher = DefaultHasher::new(); + right.hash(&mut right_hasher); + + assert_eq!(left_hasher.finish(), right_hasher.finish()); + } } From f2fa2cb61eda1d76b93b8a22ad32738a75087836 Mon Sep 17 00:00:00 2001 From: Gil Sharon Date: Fri, 27 Feb 2026 16:14:33 -0500 Subject: [PATCH 4/5] fix: build --- docs/book/src/examples.md | 12 -------- src/macros/property_impl.rs | 56 +++++++++++++++++++++---------------- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/docs/book/src/examples.md b/docs/book/src/examples.md index 0688814e..83c38077 100644 --- a/docs/book/src/examples.md +++ b/docs/book/src/examples.md @@ -64,18 +64,6 @@ with different transmission rates by edge type. cargo run --example network-hhmodel ``` -### `reports` - -Legacy report example directory retained in the repository. - -### `reports-multi-threaded` - -Legacy multi-threaded report example directory retained in the repository. - -### `time-varying-infection` - -Legacy time-varying infection example directory retained in the repository. - ## External examples * [ixa-epi-covid](https://github.com/CDCgov/ixa-epi-isolation) diff --git a/src/macros/property_impl.rs b/src/macros/property_impl.rs index f9484f77..421d7bcc 100644 --- a/src/macros/property_impl.rs +++ b/src/macros/property_impl.rs @@ -40,7 +40,11 @@ You can implement [`Property`][crate::entity::property::Property] for existing t [`Property`][crate::entity::property::Property] trait implementation for you but doesn't take care of the `#[derive(..)]` boilerplate, so you have to remember to derive the traits your type needs. At minimum, property types must satisfy the [`AnyProperty`][crate::entity::property::AnyProperty] bound (`Copy`, `Clone`, `Debug`, `PartialEq`, `Serialize`). -If you want consistency with the auto-generated `define_property!` derives, also add `Deserialize` and `Hash`. +For consistency with the hash/equality semantics used by `define_property!`, +also invoke `impl_property_value_traits!(TypeName, EntityName)` after +`impl_property!`. +This is a separate macro call because the same type may implement `Property` +for multiple entities. Some examples: @@ -51,15 +55,16 @@ define_entity!(Person); // fields. If we want to restrict the visibility of our `Property` type, we can use the // `impl_property!` macro instead. The only // catch is, we have to remember to `derive` all of -// `Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Hash`. -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Hash)] +// `Copy, Clone, Debug, PartialEq, Serialize`. +#[derive(Copy, Clone, Debug, PartialEq, Serialize)] struct Age(pub u8); impl_property!(Age, Person); +impl_property_value_traits!(Age, Person); // Here we derive `Default`, which also requires an attribute on one // of the variants. (`Property` has its own independent mechanism for // assigning default values for entities unrelated to the `Default` trait.) -#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, Hash)] +#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize)] enum InfectionStatus { #[default] Susceptible, @@ -68,12 +73,14 @@ enum InfectionStatus { } // We also specify the default value explicitly for entities. impl_property!(InfectionStatus, Person, default_const = InfectionStatus::Susceptible); +impl_property_value_traits!(InfectionStatus, Person); // Exactly equivalent to // `define_property!(struct Vaccinated(pub bool), Person, default_const = Vaccinated(false));` -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Hash)] +#[derive(Copy, Clone, Debug, PartialEq, Serialize)] pub struct Vaccinated(pub bool); impl_property!(Vaccinated, Person, default_const = Vaccinated(false)); +impl_property_value_traits!(Vaccinated, Person); ``` # [`impl_property!`][macro@crate::impl_property] with options @@ -105,10 +112,10 @@ you also must provide a conversion function to and from the canonical type. ```rust,ignore define_entity!(WeatherStation); -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Hash)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)] pub struct DegreesFahrenheit(pub f64); -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Hash)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)] pub struct DegreesCelsius(pub f64); // Custom canonical type @@ -120,6 +127,7 @@ impl_property!( make_uncanonical = |v: DegreesCelsius| DegreesFahrenheit(v.0 * 9.0 / 5.0 + 32.0), display_impl = |v| format!("{:.1} °C", v.0) ); +impl_property_value_traits!(DegreesFahrenheit, WeatherStation); ``` */ @@ -139,9 +147,9 @@ impl_property!( /// Expands to: /// ```rust /// # use ixa::{impl_property, define_entity}; -/// # use serde::{Deserialize, Serialize}; +/// # use serde::Serialize; /// # define_entity!(Person); -/// #[derive(Debug, Clone, Copy, Serialize, Deserialize)] +/// #[derive(Debug, Clone, Copy, Serialize)] /// pub struct Age(u8); /// impl_property!(Age, Person); /// ``` @@ -160,9 +168,9 @@ impl_property!( /// Expands to: /// ```rust /// # use ixa::{impl_property, define_entity}; -/// # use serde::{Deserialize, Serialize}; +/// # use serde::Serialize; /// # define_entity!(Person); -/// #[derive(Debug, Clone, Copy, Serialize, Deserialize)] +/// #[derive(Debug, Clone, Copy, Serialize)] /// pub struct Coordinates { x: i32, y: i32 } /// impl_property!(Coordinates, Person); /// ``` @@ -183,9 +191,9 @@ impl_property!( /// Expands to: /// ```rust /// # use ixa::{impl_property, define_entity}; -/// # use serde::{Deserialize, Serialize}; +/// # use serde::Serialize; /// # define_entity!(Person); -/// #[derive(Debug, Clone, Copy, Serialize, Deserialize)] +/// #[derive(Debug, Clone, Copy, Serialize)] /// pub enum InfectionStatus { /// Susceptible, /// Infectious, @@ -196,7 +204,7 @@ impl_property!( /// /// ### Notes /// -/// - The generated type derives `Debug`, `Clone`, `Copy`, `Serialize`, and `Deserialize`. +/// - The generated type derives `Debug`, `Clone`, `Copy`, and `Serialize`. /// - The generated type also implements `PartialEq`, `Eq`, and `Hash` in terms of /// [`Property::hash_property_value`](crate::entity::property::Property::hash_property_value) /// applied to canonical values. @@ -212,7 +220,7 @@ macro_rules! define_property { $entity:ident $(, $($extra:tt)+),* ) => { - #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] + #[derive(Debug, Clone, Copy, serde::Serialize)] pub struct $name(pub Option<$inner_ty>); // Use impl_property! to provide a custom display implementation @@ -236,7 +244,7 @@ macro_rules! define_property { $entity:ident $(, $($extra:tt)+),* ) => { - #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] + #[derive(Debug, Clone, Copy, serde::Serialize)] pub struct $name($(pub $field_ty),*); $crate::impl_property!($name, $entity $(, $($extra)+)*); $crate::impl_property_value_traits!($name, $entity); @@ -248,7 +256,7 @@ macro_rules! define_property { $entity:ident $(, $($extra:tt)+),* ) => { - #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] + #[derive(Debug, Clone, Copy, serde::Serialize)] pub struct $name { $(pub $field_name : $field_ty),* } $crate::impl_property!($name, $entity $(, $($extra)+)*); $crate::impl_property_value_traits!($name, $entity); @@ -262,7 +270,7 @@ macro_rules! define_property { $entity:ident $(, $($extra:tt)+),* ) => { - #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] + #[derive(Debug, Clone, Copy, serde::Serialize)] pub enum $name { $($variant),* } @@ -320,9 +328,9 @@ macro_rules! impl_property_value_traits { /// /// ```rust /// # use ixa::{impl_property, define_entity}; -/// # use serde::{Deserialize, Serialize}; +/// # use serde::Serialize; /// # define_entity!(Person); -/// #[derive(Default, Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Hash)] +/// #[derive(Default, Debug, PartialEq, Eq, Clone, Copy, Serialize)] /// pub enum InfectionStatus { /// #[default] /// Susceptible, @@ -581,7 +589,7 @@ macro_rules! define_derived_property { // For `canonical_value` implementations: $(, $($extra:tt)+),* ) => { - #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] + #[derive(Debug, Clone, Copy, serde::Serialize)] pub struct $name(pub Option<$inner_ty>); // Use impl_derived_property! to provide a custom display implementation @@ -612,7 +620,7 @@ macro_rules! define_derived_property { // For `canonical_value` implementations: $(, $($extra:tt)+),* ) => { - #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] + #[derive(Debug, Clone, Copy, serde::Serialize)] pub struct $name( $(pub $field_ty),* ); $crate::impl_derived_property!( @@ -636,7 +644,7 @@ macro_rules! define_derived_property { // For `canonical_value` implementations: $(, $($extra:tt)+),* ) => { - #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] + #[derive(Debug, Clone, Copy, serde::Serialize)] pub struct $name { $($visibility $field_name : $field_ty),* } $crate::impl_derived_property!( @@ -662,7 +670,7 @@ macro_rules! define_derived_property { // For `canonical_value` implementations: $(, $($extra:tt)+),* ) => { - #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] + #[derive(Debug, Clone, Copy, serde::Serialize)] pub enum $name { $($variant),* } From 5d97354c8a794a50cc3b1804892d8af6caf65c73 Mon Sep 17 00:00:00 2001 From: Gil Sharon Date: Mon, 2 Mar 2026 13:56:59 -0500 Subject: [PATCH 5/5] chore: remove partial eq, eq for now --- src/macros/property_impl.rs | 49 +++++++++++++------------------------ 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/src/macros/property_impl.rs b/src/macros/property_impl.rs index 421d7bcc..b5cc302c 100644 --- a/src/macros/property_impl.rs +++ b/src/macros/property_impl.rs @@ -40,7 +40,7 @@ You can implement [`Property`][crate::entity::property::Property] for existing t [`Property`][crate::entity::property::Property] trait implementation for you but doesn't take care of the `#[derive(..)]` boilerplate, so you have to remember to derive the traits your type needs. At minimum, property types must satisfy the [`AnyProperty`][crate::entity::property::AnyProperty] bound (`Copy`, `Clone`, `Debug`, `PartialEq`, `Serialize`). -For consistency with the hash/equality semantics used by `define_property!`, +For consistency with the hash semantics used by `define_property!`, also invoke `impl_property_value_traits!(TypeName, EntityName)` after `impl_property!`. This is a separate macro call because the same type may implement `Property` @@ -149,7 +149,7 @@ impl_property_value_traits!(DegreesFahrenheit, WeatherStation); /// # use ixa::{impl_property, define_entity}; /// # use serde::Serialize; /// # define_entity!(Person); -/// #[derive(Debug, Clone, Copy, Serialize)] +/// #[derive(Debug, PartialEq, Clone, Copy, Serialize)] /// pub struct Age(u8); /// impl_property!(Age, Person); /// ``` @@ -170,7 +170,7 @@ impl_property_value_traits!(DegreesFahrenheit, WeatherStation); /// # use ixa::{impl_property, define_entity}; /// # use serde::Serialize; /// # define_entity!(Person); -/// #[derive(Debug, Clone, Copy, Serialize)] +/// #[derive(Debug, PartialEq, Clone, Copy, Serialize)] /// pub struct Coordinates { x: i32, y: i32 } /// impl_property!(Coordinates, Person); /// ``` @@ -193,7 +193,7 @@ impl_property_value_traits!(DegreesFahrenheit, WeatherStation); /// # use ixa::{impl_property, define_entity}; /// # use serde::Serialize; /// # define_entity!(Person); -/// #[derive(Debug, Clone, Copy, Serialize)] +/// #[derive(Debug, PartialEq, Clone, Copy, Serialize)] /// pub enum InfectionStatus { /// Susceptible, /// Infectious, @@ -204,8 +204,8 @@ impl_property_value_traits!(DegreesFahrenheit, WeatherStation); /// /// ### Notes /// -/// - The generated type derives `Debug`, `Clone`, `Copy`, and `Serialize`. -/// - The generated type also implements `PartialEq`, `Eq`, and `Hash` in terms of +/// - The generated type derives `Debug`, `PartialEq`, `Clone`, `Copy`, and `Serialize`. +/// - The generated type also implements `Hash` in terms of /// [`Property::hash_property_value`](crate::entity::property::Property::hash_property_value) /// applied to canonical values. /// - Use the optional `default_const = ` argument to define a compile-time constant @@ -220,7 +220,7 @@ macro_rules! define_property { $entity:ident $(, $($extra:tt)+),* ) => { - #[derive(Debug, Clone, Copy, serde::Serialize)] + #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)] pub struct $name(pub Option<$inner_ty>); // Use impl_property! to provide a custom display implementation @@ -244,7 +244,7 @@ macro_rules! define_property { $entity:ident $(, $($extra:tt)+),* ) => { - #[derive(Debug, Clone, Copy, serde::Serialize)] + #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)] pub struct $name($(pub $field_ty),*); $crate::impl_property!($name, $entity $(, $($extra)+)*); $crate::impl_property_value_traits!($name, $entity); @@ -256,7 +256,7 @@ macro_rules! define_property { $entity:ident $(, $($extra:tt)+),* ) => { - #[derive(Debug, Clone, Copy, serde::Serialize)] + #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)] pub struct $name { $(pub $field_name : $field_ty),* } $crate::impl_property!($name, $entity $(, $($extra)+)*); $crate::impl_property_value_traits!($name, $entity); @@ -270,7 +270,7 @@ macro_rules! define_property { $entity:ident $(, $($extra:tt)+),* ) => { - #[derive(Debug, Clone, Copy, serde::Serialize)] + #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)] pub enum $name { $($variant),* } @@ -283,21 +283,6 @@ macro_rules! define_property { #[macro_export] macro_rules! impl_property_value_traits { ($property:ident, $entity:ident) => { - impl std::cmp::PartialEq for $property { - fn eq(&self, other: &Self) -> bool { - let lhs = - >::make_canonical(*self); - let rhs = - >::make_canonical(*other); - >::hash_property_value(&lhs) - == >::hash_property_value( - &rhs, - ) - } - } - - impl std::cmp::Eq for $property {} - impl std::hash::Hash for $property { fn hash(&self, state: &mut H) { let canonical = @@ -589,7 +574,7 @@ macro_rules! define_derived_property { // For `canonical_value` implementations: $(, $($extra:tt)+),* ) => { - #[derive(Debug, Clone, Copy, serde::Serialize)] + #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)] pub struct $name(pub Option<$inner_ty>); // Use impl_derived_property! to provide a custom display implementation @@ -620,7 +605,7 @@ macro_rules! define_derived_property { // For `canonical_value` implementations: $(, $($extra:tt)+),* ) => { - #[derive(Debug, Clone, Copy, serde::Serialize)] + #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)] pub struct $name( $(pub $field_ty),* ); $crate::impl_derived_property!( @@ -644,7 +629,7 @@ macro_rules! define_derived_property { // For `canonical_value` implementations: $(, $($extra:tt)+),* ) => { - #[derive(Debug, Clone, Copy, serde::Serialize)] + #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)] pub struct $name { $($visibility $field_name : $field_ty),* } $crate::impl_derived_property!( @@ -670,7 +655,7 @@ macro_rules! define_derived_property { // For `canonical_value` implementations: $(, $($extra:tt)+),* ) => { - #[derive(Debug, Clone, Copy, serde::Serialize)] + #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)] pub enum $name { $($variant),* } @@ -890,7 +875,7 @@ mod tests { define_property!(struct POu32(Option), Person, default_const = POu32(None)); define_property!(struct Name(&'static str), Person, default_const = Name("")); define_property!(struct Age(u8), Person, default_const = Age(0)); - #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] + #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)] struct Signed(i32); impl_property!( Signed, @@ -1151,7 +1136,7 @@ mod tests { } #[test] - fn test_equality_and_hash_use_canonical_hash() { + fn test_hash_uses_canonical_value() { use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; @@ -1159,7 +1144,7 @@ mod tests { let right = Signed(7); assert_eq!(Signed::make_canonical(left), Signed::make_canonical(right)); - assert_eq!(left, right); + assert_ne!(left, right); let mut left_hasher = DefaultHasher::new(); left.hash(&mut left_hasher);