Skip to content

Commit c5eef7b

Browse files
committed
add op feature for filler
1 parent b1a1eea commit c5eef7b

File tree

7 files changed

+188
-21
lines changed

7 files changed

+188
-21
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ jobs:
3939
run: |
4040
nix develop -c cargo run --features=std --example instance
4141
nix develop -c cargo run --features=std --example filler
42+
nix develop -c cargo run --features=std --example filler-op
4243
nix develop -c cargo run --features=std --example diff
4344
nix develop -c cargo run --features=std --example json
4445
nix develop -c cargo run --features=std --example rename-patch-struct

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ The [examples][examples] demo following scenarios.
131131
## Features
132132
This crate also includes the following optional features:
133133
- `status`(default): implements the `Status` trait for the patch struct, which provides the `is_empty` method.
134-
- `op` (default): provide operators `<<` between instance and patch, and `+` for patches
135-
- 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.
134+
- `op` (default): provide operators `<<` between instance and patch/filler, and `+` for patches/fillers
135+
- 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.
136136
- `merge` (optional): implements the `Merge` trait for the patch struct, which provides the `merge` method, and `<<` (if `op` feature enabled) between patches.
137137
- `std`(optional):
138138
- `box`: implements the `Patch<Box<P>>` trait for `T` where `T` implements `Patch<P>`.

struct-patch-derive/src/filler.rs

Lines changed: 105 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@ use syn::meta::ParseNestedMeta;
55
use syn::spanned::Spanned;
66
use syn::{parenthesized, DeriveInput, Error, Lit, LitStr, Result, Type};
77

8+
#[cfg(feature = "op")]
9+
use crate::Addable;
10+
811
const FILLER: &str = "filler";
912
const ATTRIBUTE: &str = "attribute";
1013
const EXTENDABLE: &str = "extendable";
1114
const EMPTY_VALUE: &str = "empty_value";
15+
const ADDABLE: &str = "addable";
1216

1317
pub(crate) struct Filler {
1418
visibility: syn::Visibility,
@@ -49,6 +53,8 @@ struct Field {
4953
ty: Type,
5054
attributes: Vec<TokenStream>,
5155
fty: FillerType,
56+
#[cfg(feature = "op")]
57+
addable: Addable,
5258
}
5359

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

83+
#[cfg(feature = "op")]
84+
let option_field_names_addable = fields
85+
.iter()
86+
.filter(|f| matches!(f.fty, FillerType::Option))
87+
.map(|f| !matches!(f.addable, Addable::Disable))
88+
.collect::<Vec<_>>();
89+
7790
let extendable_field_names = fields
7891
.iter()
7992
.filter(|f| matches!(f.fty, FillerType::Extendable(_)))
@@ -86,18 +99,32 @@ impl Filler {
8699
.map(|f| f.fty.inner())
87100
.collect::<Vec<_>>();
88101

102+
#[cfg(feature = "op")]
103+
let extendable_field_addable = fields
104+
.iter()
105+
.filter(|f| matches!(f.fty, FillerType::Extendable(_)))
106+
.map(|f| !matches!(f.addable, Addable::Disable))
107+
.collect::<Vec<_>>();
108+
89109
let native_value_field_names = fields
90110
.iter()
91111
.filter(|f| matches!(f.fty, FillerType::NativeValue(_)))
92112
.map(|f| f.ident.as_ref())
93113
.collect::<Vec<_>>();
94114

95-
let native_value_field_values = fields
115+
let native_value_field_empty_values = fields
96116
.iter()
97117
.filter(|f| matches!(f.fty, FillerType::NativeValue(_)))
98118
.map(|f| f.fty.value())
99119
.collect::<Vec<_>>();
100120

121+
#[cfg(feature = "op")]
122+
let native_value_field_addable = fields
123+
.iter()
124+
.filter(|f| matches!(f.fty, FillerType::NativeValue(_)))
125+
.map(|f| !matches!(f.addable, Addable::Disable))
126+
.collect::<Vec<_>>();
127+
101128
let mapped_attributes = attributes
102129
.iter()
103130
.map(|a| {
@@ -130,7 +157,7 @@ impl Filler {
130157
}
131158
)*
132159
#(
133-
if self.#native_value_field_names != #native_value_field_values {
160+
if self.#native_value_field_names != #native_value_field_empty_values {
134161
return false
135162
}
136163
)*
@@ -141,11 +168,67 @@ impl Filler {
141168
#[cfg(not(feature = "status"))]
142169
let status_impl = quote!();
143170

171+
#[cfg(feature = "op")]
172+
let op_impl = quote! {
173+
impl #generics core::ops::Shl<#name #generics> for #struct_name #generics #where_clause {
174+
type Output = Self;
175+
176+
fn shl(mut self, rhs: #name #generics) -> Self {
177+
struct_patch::traits::Filler::apply(&mut self, rhs);
178+
self
179+
}
180+
}
181+
182+
impl #generics core::ops::Add<Self> for #name #generics #where_clause {
183+
type Output = Self;
184+
185+
fn add(mut self, rhs: Self) -> Self {
186+
#(
187+
if self.#native_value_field_names == #native_value_field_empty_values {
188+
self.#native_value_field_names = rhs.#native_value_field_names;
189+
} else if #native_value_field_addable {
190+
self.#native_value_field_names = self.#native_value_field_names + rhs.#native_value_field_names;
191+
} else {
192+
panic!("`{}` conflict in fillers, please use `#[filler(addable)]`", stringify!(#native_value_field_names))
193+
}
194+
)*
195+
#(
196+
if self.#extendable_field_names.is_empty() {
197+
self.#extendable_field_names = rhs.#extendable_field_names;
198+
} else if #extendable_field_addable {
199+
self.#extendable_field_names.extend(rhs.#extendable_field_names.into_iter());
200+
} else {
201+
panic!("`{}` conflict in fillers, please use `#[filler(addable)]`", stringify!(#extendable_field_names))
202+
}
203+
)*
204+
#(
205+
if let Some(b) = self.#option_field_names {
206+
if let Some(a) = rhs.#option_field_names {
207+
if #option_field_names_addable {
208+
self.#option_field_names = Some(a + &b);
209+
} else {
210+
panic!("`{}` conflict in fillers, please use `#[filler(addable)]`", stringify!(#option_field_names))
211+
}
212+
} else {
213+
self.#option_field_names = Some(b);
214+
}
215+
} else {
216+
self.#option_field_names = rhs.#option_field_names;
217+
}
218+
)*
219+
self
220+
}
221+
}
222+
};
223+
224+
#[cfg(not(feature = "op"))]
225+
let op_impl = quote!();
226+
144227
let filler_impl = quote! {
145228
impl #generics struct_patch::traits::Filler< #name #generics > for #struct_name #generics #where_clause {
146229
fn apply(&mut self, filler: #name #generics) {
147230
#(
148-
if self.#native_value_field_names == #native_value_field_values {
231+
if self.#native_value_field_names == #native_value_field_empty_values {
149232
self.#native_value_field_names = filler.#native_value_field_names;
150233
}
151234
)*
@@ -167,7 +250,7 @@ impl Filler {
167250
#name {
168251
#(#option_field_names: None,)*
169252
#(#extendable_field_names: #extendable_field_types::default(),)*
170-
#(#native_value_field_names: #native_value_field_values,)*
253+
#(#native_value_field_names: #native_value_field_empty_values,)*
171254
}
172255
}
173256
}
@@ -177,6 +260,7 @@ impl Filler {
177260
#filler_struct
178261
#status_impl
179262
#filler_impl
263+
#op_impl
180264
})
181265
}
182266

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

297383
for attr in attrs {
298384
if attr.path().to_string().as_str() != FILLER {
@@ -336,9 +422,21 @@ impl Field {
336422
.error("empty_value needs a clear value to define empty"));
337423
}
338424
}
425+
#[cfg(feature = "op")]
426+
ADDABLE => {
427+
// #[filler(addable)]
428+
addable = Addable::AddTrait;
429+
}
430+
#[cfg(not(feature = "op"))]
431+
ADDABLE => {
432+
return Err(syn::Error::new(
433+
ident.span(),
434+
"`addable` needs `op` feature",
435+
));
436+
},
339437
_ => {
340438
return Err(meta.error(format_args!(
341-
"unknown filler field attribute `{}`",
439+
"unknown patch field attribute `{}`",
342440
path.replace(' ', "")
343441
)));
344442
}
@@ -352,6 +450,8 @@ impl Field {
352450
ty,
353451
attributes,
354452
fty,
453+
#[cfg(feature = "op")]
454+
addable,
355455
}))
356456
}
357457
}

struct-patch-derive/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ mod patch;
55
use filler::Filler;
66
use patch::Patch;
77

8+
#[cfg(feature = "op")]
9+
pub(crate) enum Addable {
10+
Disable,
11+
AddTrait,
12+
AddFn(proc_macro2::Ident),
13+
}
14+
815
#[proc_macro_derive(Patch, attributes(patch))]
916
pub fn derive_patch(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
1017
Patch::from_ast(syn::parse_macro_input!(item as syn::DeriveInput))

struct-patch-derive/src/patch.rs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ use syn::{
77
Type,
88
};
99

10+
#[cfg(feature = "op")]
11+
use crate::Addable;
12+
1013
const PATCH: &str = "patch";
1114
const NAME: &str = "name";
1215
const ATTRIBUTE: &str = "attribute";
@@ -23,14 +26,6 @@ pub(crate) struct Patch {
2326
fields: Vec<Field>,
2427
}
2528

26-
#[cfg(feature = "op")]
27-
pub(crate) enum Addable {
28-
Disable,
29-
AddTriat,
30-
#[cfg(feature = "op")]
31-
AddFn(Ident),
32-
}
33-
3429
struct Field {
3530
ident: Option<Ident>,
3631
ty: Type,
@@ -131,8 +126,8 @@ impl Patch {
131126
.iter()
132127
.map(|f| {
133128
match &f.addable {
134-
Addable::AddTriat => quote!(
135-
Some(a + b)
129+
Addable::AddTrait => quote!(
130+
Some(a + &b)
136131
),
137132
Addable::AddFn(f) => {
138133
quote!(
@@ -472,7 +467,7 @@ impl Field {
472467
#[cfg(feature = "op")]
473468
ADDABLE => {
474469
// #[patch(addable)]
475-
addable = Addable::AddTriat;
470+
addable = Addable::AddTrait;
476471
}
477472
#[cfg(not(feature = "op"))]
478473
ADDABLE => {

struct-patch/examples/filler-op.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#[cfg(feature = "op")]
2+
fn main() {
3+
use std::collections::{
4+
BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque,
5+
};
6+
use std::iter::{Extend, IntoIterator};
7+
use struct_patch::Filler;
8+
9+
#[derive(Clone, Default, Filler)]
10+
#[filler(attribute(derive(Clone, Debug, Default)))]
11+
struct Item {
12+
_field_complete: bool,
13+
// Will check the field is equal to the value to define the field is empty or not
14+
#[filler(empty_value = 0)]
15+
// Will allow field add each other when `+` on fillers
16+
#[filler(addable)]
17+
field_int: usize,
18+
_field_string: String,
19+
maybe_field_int: Option<usize>,
20+
maybe_field_string: Option<String>,
21+
// Will allow field extend each other when `+` on fillers
22+
#[filler(addable)]
23+
list: Vec<usize>,
24+
_deque: VecDeque<usize>,
25+
_linked_list: LinkedList<usize>,
26+
_map: HashMap<usize, usize>,
27+
_bmap: BTreeMap<usize, usize>,
28+
_set: HashSet<usize>,
29+
_bset: BTreeSet<usize>,
30+
_heap: BinaryHeap<usize>,
31+
}
32+
33+
let item = Item::default();
34+
35+
let mut filler1: ItemFiller = Item::new_empty_filler();
36+
filler1.field_int = 7;
37+
filler1.list = vec![1, 2];
38+
39+
let mut filler2: ItemFiller = Item::new_empty_filler();
40+
filler2.field_int = 8;
41+
filler2.list = vec![3, 4];
42+
filler2.maybe_field_string = Some("Something".into());
43+
44+
let final_item_from_added_fillers = item.clone() << (filler1.clone() + filler2.clone());
45+
46+
assert_eq!(final_item_from_added_fillers.field_int, 15);
47+
assert_eq!(final_item_from_added_fillers.list, vec![1, 2, 3, 4]);
48+
assert_eq!(
49+
final_item_from_added_fillers.maybe_field_string,
50+
Some("Something".into())
51+
);
52+
53+
let final_item_after_fillers_applied = item << filler1 << filler2;
54+
55+
assert_eq!(final_item_after_fillers_applied.field_int, 7);
56+
assert_eq!(final_item_after_fillers_applied.list, vec![1, 2]);
57+
assert_eq!(
58+
final_item_after_fillers_applied.maybe_field_string,
59+
Some("Something".into())
60+
);
61+
}
62+
63+
#[cfg(not(feature = "op"))]
64+
fn main() {}

struct-patch/examples/filler.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use struct_patch::Filler;
55
use struct_patch::Status;
66

77
// NOTE: Default, Extend, IntoIterator, is_empty are required for extendable type
8-
#[derive(Debug, Default)]
8+
#[derive(Clone, Debug, Default)]
99
struct WrapVec {
1010
inner: Vec<usize>,
1111
}
@@ -30,8 +30,8 @@ impl WrapVec {
3030
}
3131
}
3232

33-
#[derive(Default, Filler)]
34-
#[filler(attribute(derive(Debug, Default)))]
33+
#[derive(Clone, Default, Filler)]
34+
#[filler(attribute(derive(Clone, Debug, Default)))]
3535
struct Item {
3636
field_complete: bool,
3737
// Will check the field is equal to the value to define the field is empty or not

0 commit comments

Comments
 (0)