From 4ae46aba06b9211354b5cacd8fafbda8fd175d8c Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Sat, 26 Sep 2026 10:21:06 +0800 Subject: [PATCH 1/2] log: fix nesting feature leaking --- derive/src/filler.rs | 149 ++++++++++---------- derive/src/patch.rs | 314 +++++++++++++++++++++---------------------- 2 files changed, 233 insertions(+), 230 deletions(-) diff --git a/derive/src/filler.rs b/derive/src/filler.rs index b8d5fac..569a8a2 100644 --- a/derive/src/filler.rs +++ b/derive/src/filler.rs @@ -247,27 +247,25 @@ impl Filler { #[cfg(not(feature = "op"))] let op_impl = quote!(); - #[cfg(feature = "nesting")] - let make_log_calls = |names: &[Option<&Ident>]| -> Vec { - if let Some(f) = default_log_fn { - names - .iter() - .map(|n| quote! { #f(&[], stringify!(#n)); }) - .collect() - } else { - names.iter().map(|_| quote! {}).collect() - } - }; - #[cfg(not(feature = "nesting"))] + // Per-field log-call token streams, parallel with each field-name vec. let make_log_calls = |names: &[Option<&Ident>]| -> Vec { if let Some(f) = default_log_fn { - names - .iter() - .map(|n| quote! { #f(stringify!(#n)); }) - .collect() - } else { - names.iter().map(|_| quote! {}).collect() + #[cfg(feature = "nesting")] + { + return names + .iter() + .map(|n| quote! { #f(&[], stringify!(#n)); }) + .collect(); + } + #[cfg(not(feature = "nesting"))] + { + return names + .iter() + .map(|n| quote! { #f(stringify!(#n)); }) + .collect(); + } } + names.iter().map(|_| quote! {}).collect() }; let native_value_log_calls = make_log_calls(&native_value_field_names); let extendable_log_calls = make_log_calls(&extendable_field_names); @@ -293,6 +291,65 @@ impl Filler { #[cfg(not(feature = "nesting"))] let nesting_apply_section: TokenStream = quote! {}; + #[cfg(not(feature = "nesting"))] + let apply_with_log_impl = quote! { + fn apply_with_log<__L: FnMut(&str)>(&mut self, filler: #name #generics, mut log: __L) { + #( + if self.#native_value_field_names == #native_value_field_empty_values { + log(stringify!(#native_value_field_names)); + self.#native_value_field_names = filler.#native_value_field_names; + } + )* + #( + if self.#extendable_field_names.is_empty() { + log(stringify!(#extendable_field_names)); + self.#extendable_field_names.extend(filler.#extendable_field_names.into_iter()); + } + )* + #( + if let Some(v) = filler.#option_field_names { + if self.#option_field_names.is_none() { + log(stringify!(#option_field_names)); + self.#option_field_names = Some(v); + } + } + )* + } + }; + #[cfg(feature = "nesting")] + let apply_with_log_impl = quote! { + fn apply_with_log<__L: FnMut(&[&str], &str)>(&mut self, filler: #name #generics, mut log: __L) { + #( + if self.#native_value_field_names == #native_value_field_empty_values { + log(&[], stringify!(#native_value_field_names)); + self.#native_value_field_names = filler.#native_value_field_names; + } + )* + #( + if self.#extendable_field_names.is_empty() { + log(&[], stringify!(#extendable_field_names)); + self.#extendable_field_names.extend(filler.#extendable_field_names.into_iter()); + } + )* + #( + if let Some(v) = filler.#option_field_names { + if self.#option_field_names.is_none() { + log(&[], stringify!(#option_field_names)); + self.#option_field_names = Some(v); + } + } + )* + #( + let nesting_field_name = stringify!(#nesting_field_names); + self.#nesting_field_names.apply_with_log(filler.#nesting_field_names, |prefixes: &[&str], field: &str| { + let mut new_prefixes = Vec::from(prefixes); + new_prefixes.push(nesting_field_name); + log(&new_prefixes, field); + }); + )* + } + }; + let filler_impl = quote! { #[automatically_derived] impl #generics struct_patch::traits::Filler< #name #generics > for #struct_name #generics #where_clause { @@ -320,61 +377,7 @@ impl Filler { #nesting_apply_section } - #[cfg(not(feature = "nesting"))] - fn apply_with_log<__L: FnMut(&str)>(&mut self, filler: #name #generics, mut log: __L) { - #( - if self.#native_value_field_names == #native_value_field_empty_values { - log(stringify!(#native_value_field_names)); - self.#native_value_field_names = filler.#native_value_field_names; - } - )* - #( - if self.#extendable_field_names.is_empty() { - log(stringify!(#extendable_field_names)); - self.#extendable_field_names.extend(filler.#extendable_field_names.into_iter()); - } - )* - #( - if let Some(v) = filler.#option_field_names { - if self.#option_field_names.is_none() { - log(stringify!(#option_field_names)); - self.#option_field_names = Some(v); - } - } - )* - } - - #[cfg(feature = "nesting")] - fn apply_with_log<__L: FnMut(&[&str], &str)>(&mut self, filler: #name #generics, mut log: __L) { - #( - if self.#native_value_field_names == #native_value_field_empty_values { - log(&[], stringify!(#native_value_field_names)); - self.#native_value_field_names = filler.#native_value_field_names; - } - )* - #( - if self.#extendable_field_names.is_empty() { - log(&[], stringify!(#extendable_field_names)); - self.#extendable_field_names.extend(filler.#extendable_field_names.into_iter()); - } - )* - #( - if let Some(v) = filler.#option_field_names { - if self.#option_field_names.is_none() { - log(&[], stringify!(#option_field_names)); - self.#option_field_names = Some(v); - } - } - )* - #( - let nesting_field_name = stringify!(#nesting_field_names); - self.#nesting_field_names.apply_with_log(filler.#nesting_field_names, |prefixes: &[&str], field: &str| { - let mut new_prefixes = Vec::from(prefixes); - new_prefixes.push(nesting_field_name); - log(&new_prefixes, field); - }); - )* - } + #apply_with_log_impl fn new_empty_filler() -> #name #generics { #name { diff --git a/derive/src/patch.rs b/derive/src/patch.rs index dff5c02..0e4ffaf 100644 --- a/derive/src/patch.rs +++ b/derive/src/patch.rs @@ -765,30 +765,24 @@ impl Patch { let op_impl = quote!(); // Per-field log-call token streams, parallel with each field-name vec. - // With nesting feature: emit `default_log_fn(&[], stringify!(field));` - // Without nesting feature: emit `default_log_fn(stringify!(field));` - // or empty token stream otherwise. - #[cfg(feature = "nesting")] - let make_log_calls = |names: &[Option<&Ident>]| -> Vec { - if let Some(f) = default_log_fn { - names - .iter() - .map(|n| quote! { #f(&[], stringify!(#n)); }) - .collect() - } else { - names.iter().map(|_| quote! {}).collect() - } - }; - #[cfg(not(feature = "nesting"))] let make_log_calls = |names: &[Option<&Ident>]| -> Vec { if let Some(f) = default_log_fn { - names - .iter() - .map(|n| quote! { #f(stringify!(#n)); }) - .collect() - } else { - names.iter().map(|_| quote! {}).collect() + #[cfg(feature = "nesting")] + { + return names + .iter() + .map(|n| quote! { #f(&[], stringify!(#n)); }) + .collect(); + } + #[cfg(not(feature = "nesting"))] + { + return names + .iter() + .map(|n| quote! { #f(stringify!(#n)); }) + .collect(); + } } + names.iter().map(|_| quote! {}).collect() }; let renamed_log_calls = make_log_calls(&renamed_field_names); let renamed_by_ev_log_calls = make_log_calls(&renamed_field_names_by_empty_value); @@ -801,10 +795,7 @@ impl Patch { make_log_calls(&skip_wrap_apply_by_plain_field_names); let apply_by_log_calls = make_log_calls(&apply_by_field_names); - // For the `apply` method: propagate `default_log_fn` into nesting fields so - // that sub-fields of nested structs are also logged when applying with a - // struct-level default log. When no default_log_fn is set, fall back to plain - // `.apply()` so nested structs use their own log config (if any). + // For the `apply` method: propagate `default_log_fn` into nesting fields. #[cfg(feature = "nesting")] let nesting_apply_section: TokenStream = if let Some(ref f) = default_log_fn { quote! { @@ -827,6 +818,125 @@ impl Patch { #[cfg(not(feature = "nesting"))] let nesting_apply_section: TokenStream = quote! {}; + #[cfg(not(feature = "nesting"))] + let apply_with_log_impl = quote! { + fn apply_with_log(&mut self, patch: #name #generics, mut log: F) { + #( + if let Some(v) = patch.#renamed_field_names { + log(stringify!(#renamed_field_names)); + self.#renamed_field_names.apply(v); + } + )* + #( + if patch.#renamed_field_names_by_empty_value != #renamed_field_name_empty_values { + log(stringify!(#renamed_field_names_by_empty_value)); + self.#renamed_field_names_by_empty_value.apply(patch.#renamed_field_names_by_empty_value); + } + )* + #( + if let Some(v) = patch.#original_field_names { + log(stringify!(#original_field_names)); + self.#original_field_names = v; + } + )* + #( + if patch.#original_field_names_by_empty_value != #original_field_name_empty_values { + log(stringify!(#original_field_names_by_empty_value)); + self.#original_field_names_by_empty_value = patch.#original_field_names_by_empty_value; + } + )* + #( + if let Some(v) = patch.#skip_wrap_field_names { + log(stringify!(#skip_wrap_field_names)); + self.#skip_wrap_field_names = Some(v); + } + )* + #( + if let Some(v) = patch.#skip_wrap_apply_by_option_field_names { + log(stringify!(#skip_wrap_apply_by_option_field_names)); + if let Some(ref mut orig) = self.#skip_wrap_apply_by_option_field_names { + #skip_wrap_apply_by_option_fns(orig, v); + } + } + )* + #( + { + log(stringify!(#skip_wrap_apply_by_plain_field_names)); + #skip_wrap_apply_by_plain_fns(&mut self.#skip_wrap_apply_by_plain_field_names, patch.#skip_wrap_apply_by_plain_field_names); + } + )* + #( + if let Some(v) = patch.#apply_by_field_names { + log(stringify!(#apply_by_field_names)); + #apply_by_fns(&mut self.#apply_by_field_names, v); + } + )* + } + }; + #[cfg(feature = "nesting")] + let apply_with_log_impl = quote! { + fn apply_with_log(&mut self, patch: #name #generics, mut log: F) { + #( + if let Some(v) = patch.#renamed_field_names { + log(&[], stringify!(#renamed_field_names)); + self.#renamed_field_names.apply(v); + } + )* + #( + if patch.#renamed_field_names_by_empty_value != #renamed_field_name_empty_values { + log(&[], stringify!(#renamed_field_names_by_empty_value)); + self.#renamed_field_names_by_empty_value.apply(patch.#renamed_field_names_by_empty_value); + } + )* + #( + if let Some(v) = patch.#original_field_names { + log(&[], stringify!(#original_field_names)); + self.#original_field_names = v; + } + )* + #( + if patch.#original_field_names_by_empty_value != #original_field_name_empty_values { + log(&[], stringify!(#original_field_names_by_empty_value)); + self.#original_field_names_by_empty_value = patch.#original_field_names_by_empty_value; + } + )* + #( + if let Some(v) = patch.#skip_wrap_field_names { + log(&[], stringify!(#skip_wrap_field_names)); + self.#skip_wrap_field_names = Some(v); + } + )* + #( + if let Some(v) = patch.#skip_wrap_apply_by_option_field_names { + log(&[], stringify!(#skip_wrap_apply_by_option_field_names)); + if let Some(ref mut orig) = self.#skip_wrap_apply_by_option_field_names { + #skip_wrap_apply_by_option_fns(orig, v); + } + } + )* + #( + { + log(&[], stringify!(#skip_wrap_apply_by_plain_field_names)); + #skip_wrap_apply_by_plain_fns(&mut self.#skip_wrap_apply_by_plain_field_names, patch.#skip_wrap_apply_by_plain_field_names); + } + )* + #( + if let Some(v) = patch.#apply_by_field_names { + log(&[], stringify!(#apply_by_field_names)); + #apply_by_fns(&mut self.#apply_by_field_names, v); + } + )* + #( + let nesting_field_name = stringify!(#nesting_field_names); + self.#nesting_field_names.apply_with_log(patch.#nesting_field_names, |prefixes: &[&str], field: &str| { + let mut new_prefixes = vec![nesting_field_name]; + new_prefixes.extend_from_slice(prefixes); + log(&new_prefixes, field); + }); + )* + } + }; + let patch_impl = quote! { #[automatically_derived] impl #generics struct_patch::traits::Patch< #name #generics > for #struct_name #generics #where_clause { @@ -884,121 +994,7 @@ impl Patch { #nesting_apply_section } - #[cfg(not(feature = "nesting"))] - fn apply_with_log(&mut self, patch: #name #generics, mut log: F) { - #( - if let Some(v) = patch.#renamed_field_names { - log(stringify!(#renamed_field_names)); - self.#renamed_field_names.apply(v); - } - )* - #( - if patch.#renamed_field_names_by_empty_value != #renamed_field_name_empty_values { - log(stringify!(#renamed_field_names_by_empty_value)); - self.#renamed_field_names_by_empty_value.apply(patch.#renamed_field_names_by_empty_value); - } - )* - #( - if let Some(v) = patch.#original_field_names { - log(stringify!(#original_field_names)); - self.#original_field_names = v; - } - )* - #( - if patch.#original_field_names_by_empty_value != #original_field_name_empty_values { - log(stringify!(#original_field_names_by_empty_value)); - self.#original_field_names_by_empty_value = patch.#original_field_names_by_empty_value; - } - )* - #( - if let Some(v) = patch.#skip_wrap_field_names { - log(stringify!(#skip_wrap_field_names)); - self.#skip_wrap_field_names = Some(v); - } - )* - #( - if let Some(v) = patch.#skip_wrap_apply_by_option_field_names { - log(stringify!(#skip_wrap_apply_by_option_field_names)); - if let Some(ref mut orig) = self.#skip_wrap_apply_by_option_field_names { - #skip_wrap_apply_by_option_fns(orig, v); - } - } - )* - #( - { - log(stringify!(#skip_wrap_apply_by_plain_field_names)); - #skip_wrap_apply_by_plain_fns(&mut self.#skip_wrap_apply_by_plain_field_names, patch.#skip_wrap_apply_by_plain_field_names); - } - )* - #( - if let Some(v) = patch.#apply_by_field_names { - log(stringify!(#apply_by_field_names)); - #apply_by_fns(&mut self.#apply_by_field_names, v); - } - )* - } - - #[cfg(feature = "nesting")] - fn apply_with_log(&mut self, patch: #name #generics, mut log: F) { - #( - if let Some(v) = patch.#renamed_field_names { - log(&[], stringify!(#renamed_field_names)); - self.#renamed_field_names.apply(v); - } - )* - #( - if patch.#renamed_field_names_by_empty_value != #renamed_field_name_empty_values { - log(&[], stringify!(#renamed_field_names_by_empty_value)); - self.#renamed_field_names_by_empty_value.apply(patch.#renamed_field_names_by_empty_value); - } - )* - #( - if let Some(v) = patch.#original_field_names { - log(&[], stringify!(#original_field_names)); - self.#original_field_names = v; - } - )* - #( - if patch.#original_field_names_by_empty_value != #original_field_name_empty_values { - log(&[], stringify!(#original_field_names_by_empty_value)); - self.#original_field_names_by_empty_value = patch.#original_field_names_by_empty_value; - } - )* - #( - if let Some(v) = patch.#skip_wrap_field_names { - log(&[], stringify!(#skip_wrap_field_names)); - self.#skip_wrap_field_names = Some(v); - } - )* - #( - if let Some(v) = patch.#skip_wrap_apply_by_option_field_names { - log(&[], stringify!(#skip_wrap_apply_by_option_field_names)); - if let Some(ref mut orig) = self.#skip_wrap_apply_by_option_field_names { - #skip_wrap_apply_by_option_fns(orig, v); - } - } - )* - #( - { - log(&[], stringify!(#skip_wrap_apply_by_plain_field_names)); - #skip_wrap_apply_by_plain_fns(&mut self.#skip_wrap_apply_by_plain_field_names, patch.#skip_wrap_apply_by_plain_field_names); - } - )* - #( - if let Some(v) = patch.#apply_by_field_names { - log(&[], stringify!(#apply_by_field_names)); - #apply_by_fns(&mut self.#apply_by_field_names, v); - } - )* - #( - let nesting_field_name = stringify!(#nesting_field_names); - self.#nesting_field_names.apply_with_log(patch.#nesting_field_names, |prefixes: &[&str], field: &str| { - let mut new_prefixes = vec![nesting_field_name]; - new_prefixes.extend_from_slice(prefixes); - log(&new_prefixes, field); - }); - )* - } + #apply_with_log_impl fn into_patch(self) -> #name #generics { #name { @@ -1134,6 +1130,26 @@ impl Patch { // chain when `Filler` (which shares the method name `apply`) is used in // the same crate, triggering a recursion-limit overflow. A concrete // impl per derived type terminates the solver immediately. + #[cfg(all(feature = "box", feature = "nesting"))] + let box_apply_with_log_impl = quote! { + fn apply_with_log<__F: ::core::ops::FnMut(&[&str], &str)>( + &mut self, + patch: struct_patch::__Box< #name #generics >, + log: __F, + ) { + struct_patch::traits::Patch::apply_with_log(self, *patch, log); + } + }; + #[cfg(all(feature = "box", not(feature = "nesting")))] + let box_apply_with_log_impl = quote! { + fn apply_with_log<__F: ::core::ops::FnMut(&str)>( + &mut self, + patch: struct_patch::__Box< #name #generics >, + log: __F, + ) { + struct_patch::traits::Patch::apply_with_log(self, *patch, log); + } + }; #[cfg(feature = "box")] let box_impl = quote! { #[automatically_derived] @@ -1144,23 +1160,7 @@ impl Patch { struct_patch::traits::Patch::apply(self, *patch); } - #[cfg(feature = "nesting")] - fn apply_with_log<__F: ::core::ops::FnMut(&[&str], &str)>( - &mut self, - patch: struct_patch::__Box< #name #generics >, - log: __F, - ) { - struct_patch::traits::Patch::apply_with_log(self, *patch, log); - } - - #[cfg(not(feature = "nesting"))] - fn apply_with_log<__F: ::core::ops::FnMut(&str)>( - &mut self, - patch: struct_patch::__Box< #name #generics >, - log: __F, - ) { - struct_patch::traits::Patch::apply_with_log(self, *patch, log); - } + #box_apply_with_log_impl fn into_patch(self) -> struct_patch::__Box< #name #generics > { struct_patch::__Box::new( From 70235162d935b065b7e2a2026a5f9cd2fc936cd8 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Sat, 26 Sep 2026 10:25:35 +0800 Subject: [PATCH 2/2] bump 0.14.3 --- Cargo.toml | 2 +- examples/complex-examples/Cargo.toml | 2 +- examples/filler-examples/Cargo.toml | 2 +- examples/no-std-examples/Cargo.toml | 2 +- examples/patch-examples/Cargo.toml | 2 +- lib/Cargo.toml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d98f9ad..0c3ed5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ exclude = [ [workspace.package] authors = ["Antonio Yang "] -version = "0.14.2" +version = "0.14.3" edition = "2021" categories = ["development-tools"] keywords = ["struct", "patch", "macro", "derive", "overlay"] diff --git a/examples/complex-examples/Cargo.toml b/examples/complex-examples/Cargo.toml index 5e1d63e..79e7d15 100644 --- a/examples/complex-examples/Cargo.toml +++ b/examples/complex-examples/Cargo.toml @@ -10,7 +10,7 @@ struct-patch = { path = "../../lib" } [workspace.package] authors = ["Antonio Yang "] -version = "0.14.2" +version = "0.14.3" edition = "2021" categories = ["development-tools"] keywords = ["struct", "patch", "macro", "derive", "overlay"] diff --git a/examples/filler-examples/Cargo.toml b/examples/filler-examples/Cargo.toml index 62dbcaf..0417376 100644 --- a/examples/filler-examples/Cargo.toml +++ b/examples/filler-examples/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "filler-examples" authors = ["Antonio Yang "] -version = "0.14.2" +version = "0.14.3" edition = "2021" license = "MIT" diff --git a/examples/no-std-examples/Cargo.toml b/examples/no-std-examples/Cargo.toml index 648fd5e..774d933 100644 --- a/examples/no-std-examples/Cargo.toml +++ b/examples/no-std-examples/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "no-std-examples" authors = ["Antonio Yang "] -version = "0.14.2" +version = "0.14.3" edition = "2021" license = "MIT" diff --git a/examples/patch-examples/Cargo.toml b/examples/patch-examples/Cargo.toml index b15f192..80b28f9 100644 --- a/examples/patch-examples/Cargo.toml +++ b/examples/patch-examples/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "patch-examples" authors = ["Antonio Yang "] -version = "0.14.2" +version = "0.14.3" edition = "2021" license = "MIT" diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 46b0fa0..5026405 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -12,7 +12,7 @@ readme.workspace = true rust-version.workspace = true [dependencies] -struct-patch-derive = { version = "=0.14.2", path = "../derive" } +struct-patch-derive = { version = "=0.14.3", path = "../derive" } [dev-dependencies] serde = { version = "1", features = ["derive"] }