diff --git a/README.md b/README.md index 26154f3..d040d20 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ The [examples][examples] demo following scenarios. This crate also includes the following optional features: - `status`(default): implements the `PatchStatus` trait for the patch struct, which provides the `is_empty` method. - `op` (default): provide operators `<<` between instance and patch, and `+` for patches + - default: when there is a field conflict between patches, `+` will add together if the `#[patch(addable)]` is provided, else it will panic. - `merge` (optional): implements the `Merge` trait for the patch struct, which provides the `merge` method, and `<<` between patches. - `std`(optional): - `box`: implements the `Patch>` trait for `T` where `T` implements `Patch

`. diff --git a/struct-patch-derive/src/lib.rs b/struct-patch-derive/src/lib.rs index 0c56a1f..8e0d931 100644 --- a/struct-patch-derive/src/lib.rs +++ b/struct-patch-derive/src/lib.rs @@ -11,6 +11,7 @@ const PATCH: &str = "patch"; const NAME: &str = "name"; const ATTRIBUTE: &str = "attribute"; const SKIP: &str = "skip"; +const ADDABLE: &str = "addable"; #[proc_macro_derive(Patch, attributes(patch))] pub fn derive_patch(item: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -35,6 +36,7 @@ struct Field { ty: Type, attributes: Vec, retyped: bool, + addable: bool, } impl Patch { @@ -123,6 +125,23 @@ impl Patch { #[cfg(not(feature = "merge"))] let patch_merge_impl = quote!(); + #[cfg(feature = "op")] + let addable_handles = fields + .iter() + .map(|f| { + if f.addable { + quote!( + Some(a + b) + ) + } else { + // TODO handle #[patch(add=fn)] fields + quote!( + panic!("There are conflict patches, please use `#[patch(addable)]` if you want to add these values.") + ) + } + }) + .collect::>(); + #[cfg(feature = "op")] let op_impl = quote! { impl #generics core::ops::Shl<#name #generics> for #struct_name #generics #where_clause { @@ -151,8 +170,7 @@ impl Patch { #( #renamed_field_names: match (self.#renamed_field_names, rhs.#renamed_field_names) { (Some(a), Some(b)) => { - // TODO handle #[patch(add=)] fields - panic!("There are conflict patches on {}.{}", stringify!(#name), stringify!(#renamed_field_names)) + #addable_handles }, (Some(a), None) => Some(a), (None, Some(b)) => Some(b), @@ -162,8 +180,7 @@ impl Patch { #( #original_field_names: match (self.#original_field_names, rhs.#original_field_names) { (Some(a), Some(b)) => { - // TODO handle #[patch(add=)] fields - panic!("There are conflict patches on {}.{}", stringify!(#name), stringify!(#original_field_names)) + #addable_handles }, (Some(a), None) => Some(a), (None, Some(b)) => Some(b), @@ -373,6 +390,7 @@ impl Field { let mut attributes = vec![]; let mut field_type = None; let mut skip = false; + let mut addable = false; for attr in attrs { if attr.path().to_string().as_str() != PATCH { @@ -404,6 +422,10 @@ impl Field { let expr: LitStr = meta.value()?.parse()?; field_type = Some(expr.parse()?) } + ADDABLE => { + // #[patch(addable)] + addable = true; + } _ => { return Err(meta.error(format_args!( "unknown patch field attribute `{}`", @@ -423,6 +445,7 @@ impl Field { retyped: field_type.is_some(), ty: field_type.unwrap_or(ty), attributes, + addable, })) } } @@ -500,6 +523,7 @@ mod tests { .unwrap(), attributes: vec![], retyped: true, + addable: false, }], }; let result = Patch::from_ast(syn::parse2(input).unwrap()).unwrap(); diff --git a/struct-patch/examples/op.rs b/struct-patch/examples/op.rs index af1d441..c00c964 100644 --- a/struct-patch/examples/op.rs +++ b/struct-patch/examples/op.rs @@ -4,6 +4,7 @@ use struct_patch::Patch; #[patch(attribute(derive(Clone, Debug, Default)))] struct Item { field_complete: bool, + #[patch(addable)] field_int: usize, field_string: String, } @@ -45,12 +46,10 @@ fn main() { field_string: None, }; - // let final_item_from_merge = item.clone() << (conflict_patch.clone() + the_other_patch.clone()); - // Will get panic `There are conflict patches on ItemPatch.field_int` - // - // TODO - // Will be handdled as the discussion - // https://github.com/yanganto/struct-patch/pull/32#issuecomment-2283154990 + // NOTE: The values of #[patch(addable)] can be added together. + let final_item_from_conflict = + item.clone() << (conflict_patch.clone() + the_other_patch.clone()); + assert_eq!(final_item_from_conflict.field_int, 3); let final_item_without_bracket = item.clone() << conflict_patch.clone() << the_other_patch.clone();