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
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
run: |
nix develop .#ci -c cargo run --features=std --example instance
nix develop .#ci -c cargo run --features=std --example filler
nix develop .#ci -c cargo run --features=std --example filler-op
nix develop .#ci -c cargo run --features=std --example diff
nix develop .#ci -c cargo run --features=std --example json
nix develop .#ci -c cargo run --features=std --example rename-patch-struct
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ members = [

[workspace.package]
authors = ["Antonio Yang <yanganto@gmail.com>"]
version = "0.10.3"
version = "0.10.4"
edition = "2021"
categories = ["development-tools"]
keywords = ["struct", "patch", "macro", "derive", "overlay"]
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ The [examples][examples] demo following scenarios.
## Features
This crate also includes the following optional features:
- `status`(default): implements the `Status` trait for the patch struct, which provides the `is_empty` method.
- `op` (default): provide operators `<<` between instance and patch, and `+` for patches
- default: when there is a field conflict between patches, `+` will add together if the `#[patch(addable)]` or `#[patch(add=fn)]` is provided, else it will panic.
- `op` (default): provide operators `<<` between instance and patch/filler, and `+` for patches/fillers
- default: when there is a field conflict between patches/fillers, `+` will add together if the `#[patch(addable)]`, `#[patch(add=fn)]` or `#[filler(addable)]` is provided, else it will panic.
- `merge` (optional): implements the `Merge` trait for the patch struct, which provides the `merge` method, and `<<` (if `op` feature enabled) between patches.
- `std`(optional):
- `box`: implements the `Patch<Box<P>>` trait for `T` where `T` implements `Patch<P>`.
Expand Down
110 changes: 105 additions & 5 deletions derive/src/filler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ use syn::meta::ParseNestedMeta;
use syn::spanned::Spanned;
use syn::{parenthesized, DeriveInput, Error, Lit, LitStr, Result, Type};

#[cfg(feature = "op")]
use crate::Addable;

const FILLER: &str = "filler";
const ATTRIBUTE: &str = "attribute";
const EXTENDABLE: &str = "extendable";
const EMPTY_VALUE: &str = "empty_value";
const ADDABLE: &str = "addable";

pub(crate) struct Filler {
visibility: syn::Visibility,
Expand Down Expand Up @@ -49,6 +53,8 @@ struct Field {
ty: Type,
attributes: Vec<TokenStream>,
fty: FillerType,
#[cfg(feature = "op")]
addable: Addable,
}

impl Filler {
Expand All @@ -74,6 +80,13 @@ impl Filler {
.map(|f| f.ident.as_ref())
.collect::<Vec<_>>();

#[cfg(feature = "op")]
let option_field_names_addable = fields
.iter()
.filter(|f| matches!(f.fty, FillerType::Option))
.map(|f| !matches!(f.addable, Addable::Disable))
.collect::<Vec<_>>();

let extendable_field_names = fields
.iter()
.filter(|f| matches!(f.fty, FillerType::Extendable(_)))
Expand All @@ -86,18 +99,32 @@ impl Filler {
.map(|f| f.fty.inner())
.collect::<Vec<_>>();

#[cfg(feature = "op")]
let extendable_field_addable = fields
.iter()
.filter(|f| matches!(f.fty, FillerType::Extendable(_)))
.map(|f| !matches!(f.addable, Addable::Disable))
.collect::<Vec<_>>();

let native_value_field_names = fields
.iter()
.filter(|f| matches!(f.fty, FillerType::NativeValue(_)))
.map(|f| f.ident.as_ref())
.collect::<Vec<_>>();

let native_value_field_values = fields
let native_value_field_empty_values = fields
.iter()
.filter(|f| matches!(f.fty, FillerType::NativeValue(_)))
.map(|f| f.fty.value())
.collect::<Vec<_>>();

#[cfg(feature = "op")]
let native_value_field_addable = fields
.iter()
.filter(|f| matches!(f.fty, FillerType::NativeValue(_)))
.map(|f| !matches!(f.addable, Addable::Disable))
.collect::<Vec<_>>();

let mapped_attributes = attributes
.iter()
.map(|a| {
Expand Down Expand Up @@ -130,7 +157,7 @@ impl Filler {
}
)*
#(
if self.#native_value_field_names != #native_value_field_values {
if self.#native_value_field_names != #native_value_field_empty_values {
return false
}
)*
Expand All @@ -141,11 +168,67 @@ impl Filler {
#[cfg(not(feature = "status"))]
let status_impl = quote!();

#[cfg(feature = "op")]
let op_impl = quote! {
impl #generics core::ops::Shl<#name #generics> for #struct_name #generics #where_clause {
type Output = Self;

fn shl(mut self, rhs: #name #generics) -> Self {
struct_patch::traits::Filler::apply(&mut self, rhs);
self
}
}

impl #generics core::ops::Add<Self> for #name #generics #where_clause {
type Output = Self;

fn add(mut self, rhs: Self) -> Self {
#(
if self.#native_value_field_names == #native_value_field_empty_values {
self.#native_value_field_names = rhs.#native_value_field_names;
} else if #native_value_field_addable {
self.#native_value_field_names = self.#native_value_field_names + rhs.#native_value_field_names;
} else if rhs.#native_value_field_names != #native_value_field_empty_values {
panic!("`{}` conflict in fillers, please use `#[filler(addable)]`", stringify!(#native_value_field_names))
}
)*
#(
if self.#extendable_field_names.is_empty() {
self.#extendable_field_names = rhs.#extendable_field_names;
} else if #extendable_field_addable {
self.#extendable_field_names.extend(rhs.#extendable_field_names.into_iter());
} else if !rhs.#extendable_field_names.is_empty() {
panic!("`{}` conflict in fillers, please use `#[filler(addable)]`", stringify!(#extendable_field_names))
}
)*
#(
if let Some(b) = self.#option_field_names {
if let Some(a) = rhs.#option_field_names {
if #option_field_names_addable {
self.#option_field_names = Some(a + &b);
} else {
panic!("`{}` conflict in fillers, please use `#[filler(addable)]`", stringify!(#option_field_names))
}
} else {
self.#option_field_names = Some(b);
}
} else {
self.#option_field_names = rhs.#option_field_names;
}
)*
self
}
}
};

#[cfg(not(feature = "op"))]
let op_impl = quote!();

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 {
if self.#native_value_field_names == #native_value_field_empty_values {
self.#native_value_field_names = filler.#native_value_field_names;
}
)*
Expand All @@ -167,7 +250,7 @@ impl Filler {
#name {
#(#option_field_names: None,)*
#(#extendable_field_names: #extendable_field_types::default(),)*
#(#native_value_field_names: #native_value_field_values,)*
#(#native_value_field_names: #native_value_field_empty_values,)*
}
}
}
Expand All @@ -177,6 +260,7 @@ impl Filler {
#filler_struct
#status_impl
#filler_impl
#op_impl
})
}

Expand Down Expand Up @@ -293,6 +377,8 @@ impl Field {
) -> Result<Option<Field>> {
let mut fty = filler_type(&ty);
let mut attributes = vec![];
#[cfg(feature = "op")]
let mut addable = Addable::Disable;

for attr in attrs {
if attr.path().to_string().as_str() != FILLER {
Expand Down Expand Up @@ -336,9 +422,21 @@ impl Field {
.error("empty_value needs a clear value to define empty"));
}
}
#[cfg(feature = "op")]
ADDABLE => {
// #[filler(addable)]
addable = Addable::AddTrait;
}
#[cfg(not(feature = "op"))]
ADDABLE => {
return Err(syn::Error::new(
ident.span(),
"`addable` needs `op` feature",
));
},
_ => {
return Err(meta.error(format_args!(
"unknown filler field attribute `{}`",
"unknown patch field attribute `{}`",
path.replace(' ', "")
)));
}
Expand All @@ -352,6 +450,8 @@ impl Field {
ty,
attributes,
fty,
#[cfg(feature = "op")]
addable,
}))
}
}
Expand Down
7 changes: 7 additions & 0 deletions derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ mod patch;
use filler::Filler;
use patch::Patch;

#[cfg(feature = "op")]
pub(crate) enum Addable {
Disable,
AddTrait,
AddFn(proc_macro2::Ident),
}

#[proc_macro_derive(Patch, attributes(patch))]
pub fn derive_patch(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
Patch::from_ast(syn::parse_macro_input!(item as syn::DeriveInput))
Expand Down
37 changes: 20 additions & 17 deletions derive/src/patch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ use syn::{
Type,
};

#[cfg(feature = "op")]
use crate::Addable;

const PATCH: &str = "patch";
const NAME: &str = "name";
const ATTRIBUTE: &str = "attribute";
Expand All @@ -24,14 +27,6 @@ pub(crate) struct Patch {
fields: Vec<Field>,
}

#[cfg(feature = "op")]
pub(crate) enum Addable {
Disable,
AddTriat,
#[cfg(feature = "op")]
AddFn(Ident),
}

struct Field {
ident: Option<Ident>,
ty: Type,
Expand Down Expand Up @@ -66,7 +61,8 @@ impl Patch {
let field_names = fields
.iter()
.filter(|f| !f.nesting)
.map(|f| f.ident.as_ref()).collect::<Vec<_>>();
.map(|f| f.ident.as_ref())
.collect::<Vec<_>>();

#[cfg(not(feature = "nesting"))]
let renamed_field_names = fields
Expand Down Expand Up @@ -182,8 +178,8 @@ impl Patch {
.iter()
.map(|f| {
match &f.addable {
Addable::AddTriat => quote!(
Some(a + b)
Addable::AddTrait => quote!(
Some(a + &b)
),
Addable::AddFn(f) => {
quote!(
Expand Down Expand Up @@ -495,7 +491,10 @@ impl Field {
Some(ident) => {
if *nesting {
// TODO handle rename
let patch_type = syn::Ident::new(&format!("{}Patch", &ty.to_token_stream()), Span::call_site());
let patch_type = syn::Ident::new(
&format!("{}Patch", &ty.to_token_stream()),
Span::call_site(),
);
Ok(quote! {
#(#attributes)*
pub #ident: #patch_type,
Expand All @@ -506,7 +505,7 @@ impl Field {
pub #ident: Option<#ty>,
})
}
},
}
#[cfg(not(feature = "nesting"))]
None => Ok(quote! {
#(#attributes)*
Expand All @@ -516,7 +515,10 @@ impl Field {
None => {
if *nesting {
// TODO handle rename
let patch_type = syn::Ident::new(&format!("{}Patch", &ty.to_token_stream()), Span::call_site());
let patch_type = syn::Ident::new(
&format!("{}Patch", &ty.to_token_stream()),
Span::call_site(),
);
Ok(quote! {
#(#attributes)*
pub #patch_type,
Expand All @@ -527,7 +529,7 @@ impl Field {
pub Option<#ty>,
})
}
},
}
}
}

Expand Down Expand Up @@ -579,7 +581,7 @@ impl Field {
#[cfg(feature = "op")]
ADDABLE => {
// #[patch(addable)]
addable = Addable::AddTriat;
addable = Addable::AddTrait;
}
#[cfg(not(feature = "op"))]
ADDABLE => {
Expand All @@ -606,7 +608,8 @@ impl Field {
#[cfg(not(feature = "nesting"))]
NESTING => {
return Err(
meta.error("#[patch(nesting)] only work with `nesting` feature"));
meta.error("#[patch(nesting)] only work with `nesting` feature")
);
}
_ => {
return Err(meta.error(format_args!(
Expand Down
2 changes: 1 addition & 1 deletion lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ readme.workspace = true
rust-version.workspace = true

[dependencies]
struct-patch-derive = { version = "=0.10.3", path = "../derive" }
struct-patch-derive = { version = "=0.10.4", path = "../derive" }

[dev-dependencies]
serde_json = "1.0"
Expand Down
Loading