Skip to content
Merged
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
20 changes: 20 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,23 @@ updates:
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "cargo"
directory: "/lib"
schedule:
interval: "weekly"
- package-ecosystem: "cargo"
directory: "/derive"
schedule:
interval: "weekly"
- package-ecosystem: "cargo"
directory: "/no-std-examples"
schedule:
interval: "weekly"
- package-ecosystem: "cargo"
directory: "/complex-example"
schedule:
interval: "weekly"
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ exclude = [

[workspace.package]
authors = ["Antonio Yang <yanganto@gmail.com>"]
version = "0.12.1"
version = "0.12.2"
edition = "2021"
categories = ["development-tools"]
keywords = ["struct", "patch", "macro", "derive", "overlay"]
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,9 @@ assert_eq!(item.list, vec![7]);
Deriving `Substrate` on a struct will help you expose the field information, and you can easy to expose in build.rs of other crate.
Deriving `Catalyst` on can read the field information of Substrate and generate a new Complex struct.
All the fields in substrate and catalyst need be public, and the fields in complex are also public.
The overall behavior likes chemical catalysts, a catalyst **bind** on a substrate to form a complex struct, which has all fields from substrate and catalyst.
Also, a complex can **decouple** and return a catalyst and substrate. Check the [complex-example](./complex-example/catalyst/src/lib.rs).
The overall behavior likes [chemical catalysts](https://en.wikipedia.org/wiki/Enzyme_catalysis), a catalyst **bind** on a substrate to form a complex struct, which has all fields from substrate and catalyst.
Also, a complex can **decouple** without clone and return a catalyst and substrate. Check the [complex-example](./complex-example/catalyst/src/lib.rs).
In the future, we may have a sub feature with catalyst, such that it will provide no memory moving decouple, but need unsafe code.

```rust
/// In $dependency_crate/src/lib.rs
Expand Down Expand Up @@ -153,14 +154,16 @@ struct Amyloid {
```

## Documentation and Examples
You can modify the patch structure by defining `#[patch(...)]`, `#[filler(...)]` or `#[complex(...)]`, `#[catalyst(...)]` attributes on the original struct or fields.
You can modify the patch structure by defining `#[patch(...)]`, `#[filler(...)]` or `#[complex(...)]`(catalyst feature), `#[catalyst(...)]`(catalyst feature) attributes on the original struct or fields.
Two macros are provided for the catalyst feature because we need to handle the behaviors of two structs simultaneously — the catalyst itself and the product (complex). In general, the complex macro takes precedence over the catalyst macro when any conflict arises.

Struct attributes:
- `#[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.
- `#[catalyst(bind = "...")]`: decide the base structure. (catalyst feature)
- `#[catalyst(keep_field_attribute)]`: the attributes of fields in substrate will also pass to complex. (catalyst feature)
- `#[catalyst(keep_field_attribute)]`: All field attributes from a substrate or catalyst will be passed through to the complex, unless an override is explicitly specified for that field. (catalyst feature)
- `#[complex(override_field_attribute("$substrate_field_name", ...))]`: override the complex field attribute, for example `serde(default = "default_str").` (catalyst feature)
- `#[complex(name = "...")]`: change the name of the generated complex struct. (catalyst feature)
- `#[complex(attribute(...))]`: add attributes to the generated complex struct. (catalyst feature)

Expand Down
2 changes: 1 addition & 1 deletion complex-example/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ struct-patch = { path = "../lib", features = ["catalyst"] }

[workspace.package]
authors = ["Antonio Yang <yanganto@gmail.com>"]
version = "0.12.1"
version = "0.12.2"
edition = "2021"
categories = ["development-tools"]
keywords = ["struct", "patch", "macro", "derive", "overlay"]
Expand Down
19 changes: 18 additions & 1 deletion complex-example/catalyst/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ fn default_str() -> String {
#[catalyst(bind = Base)]
#[complex(name = "SmallCpx")]
#[allow(dead_code)]
#[complex(attribute(derive(Default)))]
#[complex(attribute(derive(Default, Deserialize)))]
#[complex(override_field_attribute("field_string", serde(default = "default_str")))]
#[complex(override_field_attribute("field_string", serde(rename = "renamed_field")))]
struct SmallAmyloid {
pub extra_bool: bool,
}
Expand Down Expand Up @@ -75,4 +77,19 @@ extra_bool = true
let complex: AmyloidComplex = toml::from_str(toml_str).unwrap();
assert_eq!(complex.extra_string, "default");
}

#[test]
fn override_works() {
let toml_str = r#"field_bool = false
extra_bool = false
"#;
let complex: SmallCpx = toml::from_str(toml_str).unwrap();
assert_eq!(complex.field_string, "default");
let toml_str = r#"field_bool = false
renamed_field = "Renamed"
extra_bool = false
"#;
let complex: SmallCpx = toml::from_str(toml_str).unwrap();
assert_eq!(complex.field_string, "Renamed");
}
}
1 change: 1 addition & 0 deletions complex-example/substrate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use serde::Deserialize;
use struct_patch::Substrate;

// TODO verify no rename on a struct using Substrate
#[derive(Deserialize, Default, Substrate)]
pub struct Base {
#[serde(default)]
Expand Down
85 changes: 75 additions & 10 deletions derive/src/catalyst.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
extern crate proc_macro;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use std::collections::HashMap;
use std::str::FromStr;
use syn::{Attribute, meta::ParseNestedMeta, parenthesized, DeriveInput, LitStr, Result, Type};
use syn::{
meta::ParseNestedMeta, parenthesized, Attribute, DeriveInput, LitStr, Result, Token, Type,
};

pub(crate) struct Catalyst {
visibility: syn::Visibility,
Expand All @@ -13,6 +16,7 @@ pub(crate) struct Catalyst {
fields: syn::Fields,
bind: String,
keep_field_attribute: bool,
override_field_attributes: HashMap<String, Vec<TokenStream>>, // TODO handle no-std
}

struct Field {
Expand All @@ -27,6 +31,7 @@ const COMPLEX: &str = "complex";
const BIND: &str = "bind";
const NAME: &str = "name";
const ATTRIBUTE: &str = "attribute";
const OVERRIDE: &str = "override_field_attribute";
// TODO #[catalyst(keep_field_attrs = [ "serde" ])] for more detail selections
const KEEP_FIELD_ATTRIBUTE: &str = "keep_field_attribute";

Expand All @@ -42,6 +47,7 @@ impl Catalyst {
fields,
bind,
keep_field_attribute,
override_field_attributes,
} = self;

let substrate_name: Ident = {
Expand All @@ -54,7 +60,8 @@ impl Catalyst {
let mut substrate_fields: Vec<Field> = Vec::new();
let mut catalyst_fields: Vec<Field> = Vec::new();

let substrate_str = std::env::var(bind).expect("field information of substrate is absent, please expose it in build.rs");
let substrate_str = std::env::var(bind)
.expect("field information of substrate is absent, please expose it in build.rs");
let raw_substrate_fields: syn::Fields = syn_serde::json::from_str(&substrate_str).unwrap();

for field in raw_substrate_fields.into_iter() {
Expand All @@ -69,7 +76,7 @@ impl Catalyst {

let complex_fields = raw_complex_fields
.iter()
.map(|f| f.to_token_stream(*keep_field_attribute))
.map(|f| f.to_token_stream(*keep_field_attribute, override_field_attributes))
.collect::<Result<Vec<_>>>()?;

let unpack_complex_fields = raw_complex_fields
Expand Down Expand Up @@ -160,6 +167,7 @@ impl Catalyst {
let mut attributes = vec![];
let mut bind = String::new();
let mut keep_field_attribute = false;
let mut override_field_attributes = HashMap::<String, Vec<TokenStream>>::new();

for attr in attrs {
let attr_str = attr.path().to_string();
Expand Down Expand Up @@ -193,6 +201,19 @@ impl Catalyst {
let attribute: TokenStream = content.parse()?;
attributes.push(attribute);
}
OVERRIDE if attr_str == COMPLEX => {
let content;
syn::parenthesized!(content in meta.input);

let key: LitStr = content.parse()?;
content.parse::<Token![,]>()?;

let tokens: TokenStream = content.parse()?;
override_field_attributes
.entry(key.value())
.or_default()
.push(tokens);
}
BIND if attr_str == CATALYST => {
// #[catalyst(bind = SubstrateStruct)]
if let Some(lit) = get_struct(&meta)? {
Expand All @@ -217,7 +238,10 @@ impl Catalyst {
}

if bind.is_empty() {
return Err(syn::Error::new(ident.span(), "No substrate for Catalyst, please specify with #[catalyst(bind = ...)]"));
return Err(syn::Error::new(
ident.span(),
"No substrate for Catalyst, please specify with #[catalyst(bind = ...)]",
));
}
let complex_struct_name = name.unwrap_or({
let ts = TokenStream::from_str(&format!("{}Complex", &ident,)).unwrap();
Expand All @@ -234,26 +258,44 @@ impl Catalyst {
fields,
bind,
keep_field_attribute,
override_field_attributes,
})
}
}

impl Field {
/// Generate the token stream for the Complex struct fields
pub fn to_token_stream(&self, keep_field_attribute: bool) -> Result<TokenStream> {
pub fn to_token_stream(
&self,
keep_field_attribute: bool,
override_field_attributes: &HashMap<String, Vec<TokenStream>>,
) -> Result<TokenStream> {
let Field {
attrs,
attributes,
ident,
ty,
} = self;

if let Some(ident) = ident {
// from override
if let Some(attributes) = override_field_attributes.get(&ident.to_string()) {
return Ok(quote! {
#( #[ #attributes ] )*
pub #ident: #ty,
});
}
}

if keep_field_attribute {
if attrs.len() > 0 {
// from substrate
Ok(quote! {
#( #attrs )*
pub #ident: #ty,
})
} else {
// from catalyst
Ok(quote! {
#( #[ #attributes ] )*
pub #ident: #ty,
Expand All @@ -268,14 +310,23 @@ impl Field {

/// Generate the token stream for unpack Complex struct fields
pub fn to_unpack_stream(&self) -> Result<TokenStream> {
let Field { ident, ty: _, attributes: _, attrs: _ } = self;
let Field {
ident,
ty: _,
attributes: _,
attrs: _,
} = self;
Ok(quote! {
#ident,
})
}

/// Parse the Catalyst struct field
pub fn from_cat_ast(syn::Field { ident, ty, attrs, .. }: syn::Field) -> Field {
pub fn from_cat_ast(
syn::Field {
ident, ty, attrs, ..
}: syn::Field,
) -> Field {
let mut attributes = Vec::new();
for attr in attrs.iter() {
let attr_str = attr.path().to_string();
Expand All @@ -297,10 +348,24 @@ impl Field {
Ok(())
});
}
Field { ident, ty, attributes, attrs: Vec::new() }
Field {
ident,
ty,
attributes,
attrs: Vec::new(),
}
}
pub fn from_ast(syn::Field { ident, ty, attrs, .. }: syn::Field) -> Field {
Field { ident, ty, attrs, attributes: Vec::new() }
pub fn from_ast(
syn::Field {
ident, ty, attrs, ..
}: syn::Field,
) -> Field {
Field {
ident,
ty,
attrs,
attributes: Vec::new(),
}
}
}

Expand Down
Loading