From edc81dd65105f742a80c5e2dda9fbb94516ba048 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Sun, 13 Apr 2025 11:55:29 +0800 Subject: [PATCH 1/3] define empty value for native type --- struct-patch-derive/src/filler.rs | 83 ++++++++++++++++++++++++++++--- struct-patch/examples/filler.rs | 16 +++++- 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/struct-patch-derive/src/filler.rs b/struct-patch-derive/src/filler.rs index a0b2428..aad9cbc 100644 --- a/struct-patch-derive/src/filler.rs +++ b/struct-patch-derive/src/filler.rs @@ -1,11 +1,14 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; use std::str::FromStr; -use syn::{parenthesized, DeriveInput, LitStr, Result, Type}; +use syn::meta::ParseNestedMeta; +use syn::spanned::Spanned; +use syn::{parenthesized, DeriveInput, Error, Lit, LitStr, Result, Type}; const FILLER: &str = "filler"; const ATTRIBUTE: &str = "attribute"; const EXTENDABLE: &str = "extendable"; +const EMPTY_VALUE: &str = "empty_value"; pub(crate) struct Filler { visibility: syn::Visibility, @@ -16,10 +19,12 @@ pub(crate) struct Filler { fields: Vec, } -#[derive(Debug, PartialEq)] enum FillerType { Option, + /// The type with `Default`, `Extend`, `IntoIterator` and `is_empty` implementations Extendable(Ident), + /// The type defined a value for empty + NativeValue(Lit), } impl FillerType { @@ -27,7 +32,14 @@ impl FillerType { if let FillerType::Extendable(ident) = self { ident } else { - panic!("FillerType::Option has no inner indent") + panic!("Only FillerType::Extendable has inner indent") + } + } + fn value(&self) -> &Lit { + if let FillerType::NativeValue(lit) = self { + lit + } else { + panic!("Only FillerType::NativeValue has value") } } } @@ -58,7 +70,7 @@ impl Filler { let option_field_names = fields .iter() - .filter(|f| f.fty == FillerType::Option) + .filter(|f| matches!(f.fty, FillerType::Option)) .map(|f| f.ident.as_ref()) .collect::>(); @@ -74,6 +86,18 @@ impl Filler { .map(|f| f.fty.inner()) .collect::>(); + let native_value_field_names = fields + .iter() + .filter(|f| matches!(f.fty, FillerType::NativeValue(_))) + .map(|f| f.ident.as_ref()) + .collect::>(); + + let native_value_field_values = fields + .iter() + .filter(|f| matches!(f.fty, FillerType::NativeValue(_))) + .map(|f| f.fty.value()) + .collect::>(); + let mapped_attributes = attributes .iter() .map(|a| { @@ -105,6 +129,11 @@ impl Filler { return false } )* + #( + if self.#native_value_field_names != #native_value_field_values { + return false + } + )* true } } @@ -115,6 +144,11 @@ impl Filler { let filler_impl = quote! { impl #generics struct_patch::traits::Filler< #name #generics > for #struct_name #generics #where_clause { fn apply(&mut self, filler: #name #generics) { + #( + if self.#native_value_field_names == #native_value_field_values { + self.#native_value_field_names = filler.#native_value_field_names; + } + )* #( if self.#extendable_field_names.is_empty() { self.#extendable_field_names.extend(filler.#extendable_field_names.into_iter()); @@ -133,6 +167,7 @@ impl Filler { #name { #(#option_field_names: None,)* #(#extendable_field_names: #extendable_field_types::default(),)* + #(#native_value_field_names: #native_value_field_values,)* } } } @@ -282,7 +317,24 @@ impl Field { } EXTENDABLE => { // #[filler(extendable)] - fty = Some(FillerType::Extendable(extendable_filler_type(&ty))); + if fty.is_some() { + return Err(meta + .error("The field is already the field of filler, we can't defined more than once")); + } + fty = Some(FillerType::Extendable(none_option_filler_type(&ty))); + } + EMPTY_VALUE => { + // #[filler(empty_value=some value)] + if fty.is_some() { + return Err(meta + .error("The field is already the field of filler, we can't defined more than once")); + } + if let Some(lit) = get_lit(path, &meta)? { + fty = Some(FillerType::NativeValue(lit)); + } else { + return Err(meta + .error("empty_value needs a clear value to define empty")); + } } _ => { return Err(meta.error(format_args!( @@ -342,10 +394,29 @@ fn filler_type(ty: &Type) -> Option { None } -fn extendable_filler_type(ty: &Type) -> Ident { +fn none_option_filler_type(ty: &Type) -> Ident { if let Type::Path(type_path) = ty { type_path.path.segments[0].ident.clone() } else { panic!("#[filler(extendable)] should use on a type") } } + +fn get_lit(attr_name: String, meta: &ParseNestedMeta) -> syn::Result> { + let expr: syn::Expr = meta.value()?.parse()?; + let mut value = &expr; + while let syn::Expr::Group(e) = value { + value = &e.expr; + } + if let syn::Expr::Lit(syn::ExprLit { lit, .. }) = value { + Ok(Some(lit.clone())) + } else { + Err(Error::new( + expr.span(), + format!( + "expected serde {} attribute to be lit: `{} = \"...\"`", + attr_name, attr_name + ), + )) + } +} diff --git a/struct-patch/examples/filler.rs b/struct-patch/examples/filler.rs index 0586e3f..3e9b2a9 100644 --- a/struct-patch/examples/filler.rs +++ b/struct-patch/examples/filler.rs @@ -34,7 +34,11 @@ impl WrapVec { #[filler(attribute(derive(Debug, Default)))] struct Item { field_complete: bool, + // Will check the field is equal to the value to define the field is empty or not + #[filler(empty_value = 0)] field_int: usize, + // Will check the field is equal to String::default() to define the field is empty or not + // #[filler(empty=default)] field_string: String, maybe_field_int: Option, maybe_field_string: Option, @@ -81,7 +85,7 @@ fn main() { assert_eq!( format!("{filler:?}"), - "ItemFiller { maybe_field_int: Some(7), maybe_field_string: None, list: [], _deque: [], _linked_list: [], _set: {}, _bset: {}, _heap: [], _wrap: WrapVec { inner: [] } }" + "ItemFiller { field_int: 0, maybe_field_int: Some(7), maybe_field_string: None, list: [], _deque: [], _linked_list: [], _set: {}, _bset: {}, _heap: [], _wrap: WrapVec { inner: [] } }" ); item.apply(filler); @@ -115,4 +119,14 @@ fn main() { filler.list = vec![3, 4]; item.apply(filler); assert_eq!(item.list, vec![1, 2]); + + let mut filler: ItemFiller = Item::new_empty_filler(); + filler.field_int = 7; + item.apply(filler); + assert_eq!(item.field_int, 7); + + let mut filler: ItemFiller = Item::new_empty_filler(); + filler.field_int = 5; + item.apply(filler); + assert_eq!(item.field_int, 7); } From 2043277c2ff73c4f571834590f9814c4064f229b Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Mon, 28 Apr 2025 10:24:31 +0800 Subject: [PATCH 2/3] document --- struct-patch-derive/src/filler.rs | 2 +- struct-patch/examples/filler.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/struct-patch-derive/src/filler.rs b/struct-patch-derive/src/filler.rs index aad9cbc..28b336d 100644 --- a/struct-patch-derive/src/filler.rs +++ b/struct-patch-derive/src/filler.rs @@ -23,7 +23,7 @@ enum FillerType { Option, /// The type with `Default`, `Extend`, `IntoIterator` and `is_empty` implementations Extendable(Ident), - /// The type defined a value for empty + /// The type with a value defined for empty NativeValue(Lit), } diff --git a/struct-patch/examples/filler.rs b/struct-patch/examples/filler.rs index 3e9b2a9..b6e924f 100644 --- a/struct-patch/examples/filler.rs +++ b/struct-patch/examples/filler.rs @@ -37,8 +37,6 @@ struct Item { // Will check the field is equal to the value to define the field is empty or not #[filler(empty_value = 0)] field_int: usize, - // Will check the field is equal to String::default() to define the field is empty or not - // #[filler(empty=default)] field_string: String, maybe_field_int: Option, maybe_field_string: Option, From 0209dc5ce7710a5ccff549e50a06817a1c5ac0c8 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Mon, 28 Apr 2025 10:49:47 +0800 Subject: [PATCH 3/3] bounce version --- Cargo.toml | 2 +- struct-patch/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 34f08b4..6f5058f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ members = [ [workspace.package] authors = ["Antonio Yang "] -version = "0.9.2" +version = "0.9.3" edition = "2021" categories = ["development-tools"] keywords = ["struct", "patch", "macro", "derive", "overlay"] diff --git a/struct-patch/Cargo.toml b/struct-patch/Cargo.toml index 6124a25..36106d6 100644 --- a/struct-patch/Cargo.toml +++ b/struct-patch/Cargo.toml @@ -11,7 +11,7 @@ license.workspace = true readme.workspace = true [dependencies] -struct-patch-derive = { version = "=0.9.2", path = "../struct-patch-derive" } +struct-patch-derive = { version = "=0.9.3", path = "../struct-patch-derive" } [dev-dependencies] serde_json = "1.0"