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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ struct-patch/examples
examples/no-std-examples/target
examples/complex-examples/target
examples/patch-examples/target
examples/filler-examples/target
9 changes: 5 additions & 4 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 @@ -199,9 +199,9 @@ Two attribute namespaces are provided for the catalyst feature because we need t
- `#[patch(name = "...")]`: change the name of the generated patch struct.
- `#[patch(attribute(...))]`: add attributes to the generated patch struct.
- `#[patch(attribute(derive(...)))]`: add derives to the generated patch struct.
- `#[patch(default_log(fn_path))]`: call `fn_path` with each patched field name on every `apply` call. Has no effect on `apply_with_log`. The function must accept `&str`.
- `#[patch(default_log(fn_path))]`: call `fn_path` with each patched field name on every `apply` call. Has no effect on `apply_with_log`. Function signature: `fn(&str)` without nesting feature, or `fn(&[&str], &str)` with nesting feature.
- `#[filler(attribute(...))]`: add attributes to the generated filler struct.
- `#[filler(default_log(fn_path))]`: call `fn_path` with each filled field name on every `apply` call. Has no effect on `apply_with_log`. The function must accept `&str`.
- `#[filler(default_log(fn_path))]`: call `fn_path` with each filled field name on every `apply` call. Has no effect on `apply_with_log`. Function signature: `fn(&str)` without nesting feature, or `fn(&[&str], &str)` with nesting feature.
- `#[catalyst(bind = ...)]`: specify the base (substrate) structure. Need substrate expose() in build (catalyst feature)
- `#[catalyst(bind = ..., src = "crate_name:/path/to/file")]`: specify the base (substrate) structure. No need substrate expose() and based on source code. Avoide syn protocol change (catalyst feature)
- `#[catalyst(keep_field_attribute)]`: pass all field attributes from a substrate or catalyst through to the complex, unless an override is explicitly specified for that field. (catalyst feature)
Expand Down Expand Up @@ -252,6 +252,7 @@ Examples are organised into focused sub-projects under [`examples/`](./examples)
**[filler-examples](./examples/filler-examples)** — `Filler` derive macro scenarios:
- show filler with all possible types (`filler.rs`)
- show operators on fillers (`filler-op.rs`)
- demonstrate `default_log` and `apply_with_log` for `Filler` with optional nesting support (`log.rs`)

**[no-std-examples](./examples/no-std-examples)** — `no_std` usage with a bare-metal target.

Expand All @@ -274,7 +275,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
111 changes: 107 additions & 4 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,17 @@ 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(feature = "nesting")]
let nesting_field_names = fields
.iter()
.filter(|f| f.nesting)
.map(|f| f.ident.as_ref())
.collect::<Vec<_>>();

let mapped_attributes = attributes
.iter()
.map(|a| {
Expand Down Expand Up @@ -163,6 +177,11 @@ impl Filler {
return false
}
)*
#(
if !self.#nesting_field_names.is_empty() {
return false
}
)*
true
}
}
Expand Down Expand Up @@ -228,6 +247,18 @@ impl Filler {
#[cfg(not(feature = "op"))]
let op_impl = quote!();

#[cfg(feature = "nesting")]
let make_log_calls = |names: &[Option<&Ident>]| -> Vec<TokenStream> {
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<TokenStream> {
if let Some(f) = default_log_fn {
names
Expand All @@ -242,6 +273,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 +317,71 @@ impl Filler {
}
}
)*
#nesting_apply_section
}

#[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));
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);
}
}
)*
}

fn apply_with_log<__L: FnMut(&str)>(&mut self, filler: #name #generics, mut log: __L) {
#[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));
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);
}
}
)*
#(
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 +518,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 +576,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 +607,8 @@ impl Field {
fty,
#[cfg(feature = "op")]
addable,
#[cfg(feature = "nesting")]
nesting,
}))
}
}
Expand Down
Loading