Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ Deriving `Substrate` on a struct exposes the field information so that other cra
Deriving `Catalyst` reads the field information of a `Substrate` and generates a new complex struct.
In the other words, the catalyst is a struct with extra fields that the developer writes down in the downstream crate. The complex is a generated struct that combines the substrate's fields with the catalyst's extra fields. The overall behavior is like [chemical catalysts](https://en.wikipedia.org/wiki/Enzyme_catalysis): a catalyst **binds** onto a substrate to form a complex struct, which has all fields from both.
A complex can also **decouple** without cloning, returning the original catalyst and substrate. Check the [complex-examples](./examples/complex-examples/catalyst/src/lib.rs).
With the `unsafe` feature, `bind` and `decouple` use `ManuallyDrop` + `ptr::read` to avoid memory moves, and `__substrate_new` uses `MaybeUninit` + `ptr::write` while `__substrate_unpack` uses `ManuallyDrop` + `ptr::read`, such that the copy will be less.
With the `unsafe` feature, `bind` and `decouple` avoid memory moves, the copy will be less.

In terms of crate dependencies, the crate using `Substrate` is **upstream** (a dependency), and the crate using `Catalyst` is **downstream** (it depends on the substrate crate). There are two ways for the downstream crate to read the substrate's field layout:

Expand Down Expand Up @@ -274,7 +274,7 @@ This crate includes the following optional features:
- `nesting` *(optional)*: allows a field to use `Patch` derive with the `#[patch(nesting)]` attribute.
- `substrate` *(optional)*: enables the `Substrate` derive macro for exposing a struct's field layout so downstream crates can access it via `expose()` or source parsing.
- `catalyst` *(optional)*: enables the `Catalyst` and `Complex` derive macros for extending a struct with fields from another crate. Implies `substrate`.
- `unsafe` *(optional)*: uses `ManuallyDrop` + `ptr::read` / `MaybeUninit` + `ptr::write` in the generated `bind`, `decouple`, `__substrate_new`, and `__substrate_unpack` to avoid memory moves. Only meaningful with the `catalyst` feature.
- `unsafe` *(optional)*: avoid memory moves. Only meaningful with the `catalyst` feature.

[crates-badge]: https://img.shields.io/crates/v/struct-patch.svg
[crate-url]: https://crates.io/crates/struct-patch
Expand Down
109 changes: 104 additions & 5 deletions derive/src/filler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const EXTENDABLE: &str = "extendable";
const EMPTY_VALUE: &str = "empty_value";
const ADDABLE: &str = "addable";
const DEFAULT_LOG: &str = "default_log";
const NESTING: &str = "nesting";

pub(crate) struct Filler {
visibility: syn::Visibility,
Expand Down Expand Up @@ -55,6 +56,8 @@ struct Field {
fty: FillerType,
#[cfg(feature = "op")]
addable: Addable,
#[cfg(feature = "nesting")]
nesting: bool,
}

impl Filler {
Expand Down Expand Up @@ -126,6 +129,25 @@ impl Filler {
.map(|f| !matches!(f.addable, Addable::Disable))
.collect::<Vec<_>>();

// Nesting fields
#[cfg(not(feature = "nesting"))]
let nesting_field_names: Vec<Option<&Ident>> = Vec::new();
#[cfg(not(feature = "nesting"))]
let nesting_field_types: Vec<&Type> = Vec::new();

#[cfg(feature = "nesting")]
let nesting_field_names = fields
.iter()
.filter(|f| f.nesting)
.map(|f| f.ident.as_ref())
.collect::<Vec<_>>();
#[cfg(feature = "nesting")]
let nesting_field_types = fields
.iter()
.filter(|f| f.nesting)
.map(|f| &f.ty)
.collect::<Vec<_>>();

let mapped_attributes = attributes
.iter()
.map(|a| {
Expand Down Expand Up @@ -163,6 +185,11 @@ impl Filler {
return false
}
)*
#(
if !self.#nesting_field_names.is_empty() {
return false
}
)*
true
}
}
Expand Down Expand Up @@ -232,7 +259,7 @@ impl Filler {
if let Some(f) = default_log_fn {
names
.iter()
.map(|n| quote! { #f(stringify!(#n)); })
.map(|n| quote! { #f(&[], stringify!(#n)); })
.collect()
} else {
names.iter().map(|_| quote! {}).collect()
Expand All @@ -242,6 +269,26 @@ impl Filler {
let extendable_log_calls = make_log_calls(&extendable_field_names);
let option_log_calls = make_log_calls(&option_field_names);

// 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! {
#(
self.#nesting_field_names.apply_with_log(filler.#nesting_field_names, |_prefixes: &[&str], field: &str| {
#f(&[], field);
});
)*
}
} else {
quote! {
#(
self.#nesting_field_names.apply(filler.#nesting_field_names);
)*
}
};
#[cfg(not(feature = "nesting"))]
let nesting_apply_section: TokenStream = quote! {};

let filler_impl = quote! {
#[automatically_derived]
impl #generics struct_patch::traits::Filler< #name #generics > for #struct_name #generics #where_clause {
Expand All @@ -266,36 +313,71 @@ impl Filler {
}
}
)*
#nesting_apply_section
}

fn apply_with_log<__L: FnMut(&str)>(&mut self, filler: #name #generics, mut log: __L) {
#[cfg(not(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));
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));
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));
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);
});
)*
}

fn new_empty_filler() -> #name #generics {
#name {
#(#option_field_names: None,)*
#(#extendable_field_names: #extendable_field_types::default(),)*
#(#native_value_field_names: #native_value_field_empty_values,)*
#(#nesting_field_names: Default::default(),)*
}
}
}
Expand Down Expand Up @@ -432,6 +514,8 @@ impl Field {
let mut attributes = vec![];
#[cfg(feature = "op")]
let mut addable = Addable::Disable;
#[cfg(feature = "nesting")]
let mut nesting = false;

for attr in attrs {
if attr.path().to_string().as_str() != FILLER {
Expand Down Expand Up @@ -488,6 +572,19 @@ impl Field {
"`addable` needs `op` feature",
));
},
#[cfg(feature = "nesting")]
NESTING => {
// #[filler(nesting)]
nesting = true;
}
#[cfg(not(feature = "nesting"))]
NESTING => {
use syn::spanned::Spanned;
return Err(syn::Error::new(
ident.span(),
"`nesting` needs `nesting` feature",
));
},
_ => {
return Err(meta.error(format_args!(
"unknown patch field attribute `{}`",
Expand All @@ -506,6 +603,8 @@ impl Field {
fty,
#[cfg(feature = "op")]
addable,
#[cfg(feature = "nesting")]
nesting,
}))
}
}
Expand Down
93 changes: 79 additions & 14 deletions derive/src/patch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -765,13 +765,13 @@ impl Patch {
let op_impl = quote!();

// Per-field log-call token streams, parallel with each field-name vec.
// Emit `default_log_fn(stringify!(field));` when a struct-level log is configured,
// Emit `default_log_fn(&[], stringify!(field));` when a struct-level log is configured,
// or an empty token stream otherwise.
let make_log_calls = |names: &[Option<&Ident>]| -> Vec<TokenStream> {
if let Some(f) = default_log_fn {
names
.iter()
.map(|n| quote! { #f(stringify!(#n)); })
.map(|n| quote! { #f(&[], stringify!(#n)); })
.collect()
} else {
names.iter().map(|_| quote! {}).collect()
Expand All @@ -796,7 +796,12 @@ impl Patch {
let nesting_apply_section: TokenStream = if let Some(ref f) = default_log_fn {
quote! {
#(
self.#nesting_field_names.apply_with_log(patch.#nesting_field_names, #f);
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::from(prefixes);
new_prefixes.push(nesting_field_name);
#f(&new_prefixes, field);
});
)*
}
} else {
Expand Down Expand Up @@ -866,59 +871,119 @@ impl Patch {
#nesting_apply_section
}

fn apply_with_log<F: FnMut(&str)>(&mut self, patch: #name #generics, mut log: F) {
#[cfg(not(feature = "nesting"))]
fn apply_with_log<F: FnMut(&[&str], &str)>(&mut self, patch: #name #generics, mut log: F) {
#(
if let Some(v) = patch.#renamed_field_names {
log(stringify!(#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));
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));
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));
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));
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));
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));
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));
log(&[], stringify!(#apply_by_field_names));
#apply_by_fns(&mut self.#apply_by_field_names, v);
}
)*
}

#[cfg(feature = "nesting")]
fn apply_with_log<F: FnMut(&[&str], &str)>(&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);
}
)*
#(
self.#nesting_field_names.apply_with_log(patch.#nesting_field_names, &mut log);
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::from(prefixes);
new_prefixes.push(nesting_field_name);
log(&new_prefixes, field);
});
)*
}

Expand Down Expand Up @@ -1066,7 +1131,7 @@ impl Patch {
struct_patch::traits::Patch::apply(self, *patch);
}

fn apply_with_log<__F: ::core::ops::FnMut(&str)>(
fn apply_with_log<__F: ::core::ops::FnMut(&[&str], &str)>(
&mut self,
patch: struct_patch::__Box< #name #generics >,
log: __F,
Expand Down
Loading
Loading