Skip to content

Commit ce2ee74

Browse files
committed
add #[difference(export)] attribute to allow pub diff types
1 parent eb770cc commit ce2ee74

File tree

12 files changed

+168
-82
lines changed

12 files changed

+168
-82
lines changed

README.md

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,22 @@ assert_ne!(diffed, second);
4848
For more examples take a look at [integration tests](/tests)
4949

5050
## Derive macro attributes
51-
- `#[difference(skip)]` - Do not consider this field when creating a diff
52-
- `#[difference(recurse)]` - Generate a StructDiff for this field when creating a diff
53-
- `#[difference(collection_strategy = {})]`
54-
- `"ordered_array_like"` - Generates a minimal changeset for ordered, array-like collections of items which implement `PartialEq`. (uses levenshtein difference)
55-
- `"unordered_array_like"` - Generates a minimal changeset for unordered, array-like collections of items which implement `Hash + Eq`.
56-
- `"unordered_map_like"` - Generates a minimal changeset for unordered, map-like collections for which the key implements `Hash + Eq`.
57-
- `#[difference(map_equality = {})]` - Used with `unordered_map_like`
58-
- `"key_only"` - only replace a key-value pair for which the key has changed
59-
- `"key_and_value"` - replace a key-value pair if either the key or value has changed
60-
- `#[difference(setters)]` - Generate setters for all fields in the struct (used on struct)
61-
- Example: for the `field1` of the `Example` struct used above, a function with the signature `set_field1_with_diff(&mut self, value: Option<usize>) -> Option<<Self as StructDiff>::Diff>` will be generated. Useful when a single field will be changed in a struct with many fields, as it saves the comparison of all other fields.
62-
- `#[difference(setter)]` - Generate setters for this struct field (used on field)
63-
- `#[difference(setter_name = {})]` - Use this name instead of the default value when generating a setter for this field (used on field)
51+
- Field level
52+
- `#[difference(skip)]` - Do not consider this field when creating a diff
53+
- `#[difference(recurse)]` - Generate a StructDiff for this field when creating a diff
54+
- `#[difference(collection_strategy = {})]`
55+
- `"ordered_array_like"` - Generates a minimal changeset for ordered, array-like collections of items which implement `PartialEq`. (uses levenshtein difference)
56+
- `"unordered_array_like"` - Generates a minimal changeset for unordered, array-like collections of items which implement `Hash + Eq`.
57+
- `"unordered_map_like"` - Generates a minimal changeset for unordered, map-like collections for which the key implements `Hash + Eq`.
58+
- `#[difference(map_equality = {})]` - Used with `unordered_map_like`
59+
- `"key_only"` - only replace a key-value pair for which the key has changed
60+
- `"key_and_value"` - replace a key-value pair if either the key or value has changed
61+
- `#[difference(setter)]` - Generate setters for this struct field
62+
- `#[difference(setter_name = {})]` - Use this name instead of the default value when generating a setter for this field (used on field)
63+
- Struct Level
64+
- `#[difference(setters)]` - Generate setters for all fields in the struct
65+
- Example: for the `field1` of the `Example` struct used above, a function with the signature `set_field1_with_diff(&mut self, value: Option<usize>) -> Option<<Self as StructDiff>::Diff>` will be generated. Useful when a single field will be changed in a struct with many fields, as it saves the comparison of all other fields.
66+
- `#[difference(expose)]`/`#[difference(expose = "MyDiffTypeName")]` - expose the generated difference type (optionally, with the specified name)
6467

6568
## Optional features
6669
- [`nanoserde`, `serde`] - Serialization of `Difference` derived associated types. Allows diffs to easily be sent over network.

derive/src/difference.rs

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use alloc::string::String;
66
use crate::parse::{Category, ConstValType, Enum, Generic, Struct, Type};
77
#[cfg(feature = "generated_setters")]
88
use crate::shared::{attrs_all_setters, attrs_setter};
9-
use crate::shared::{attrs_collection_type, attrs_recurse, attrs_skip};
9+
use crate::shared::{attrs_collection_type, attrs_expose, attrs_recurse, attrs_skip};
1010
use proc_macro::TokenStream;
1111

1212
fn get_used_lifetimes(ty: &Type) -> Vec<String> {
@@ -108,8 +108,16 @@ pub(crate) fn derive_struct_diff_struct(struct_: &Struct) -> TokenStream {
108108
#[cfg(feature = "generated_setters")]
109109
let mut setters_body = String::new();
110110

111-
let enum_name =
112-
String::from("__".to_owned() + struct_.name.as_ref().unwrap().as_str() + "StructDiffEnum");
111+
let exposed = attrs_expose(&struct_.attributes);
112+
113+
let enum_name = match exposed.clone() {
114+
Some(Some(name)) => name,
115+
Some(None) => String::from(struct_.name.as_ref().unwrap().to_string() + "StructDiffEnum"),
116+
_ => String::from(
117+
"__".to_owned() + struct_.name.as_ref().unwrap().as_str() + "StructDiffEnum",
118+
),
119+
};
120+
113121
let struct_generics_names_hash: HashSet<String> =
114122
struct_.generics.iter().map(|x| x.full()).collect();
115123

@@ -1000,14 +1008,13 @@ pub(crate) fn derive_struct_diff_struct(struct_: &Struct) -> TokenStream {
10001008
""
10011009
};
10021010

1011+
let const_start = "#[allow(non_camel_case_types)]\nconst _: () = {";
1012+
10031013
format!(
1004-
"#[allow(non_camel_case_types)]
1005-
const _: () = {{
1006-
use structdiff::collections::*;
1014+
"{non_exposed_const_start}
10071015
{type_aliases}
10081016
{ref_type_aliases}
10091017
{nanoserde_hack}
1010-
10111018
#[allow(non_camel_case_types)]
10121019
/// Generated type from StructDiff
10131020
#[derive({owned_derives})]{serde_bounds}
@@ -1027,6 +1034,9 @@ pub(crate) fn derive_struct_diff_struct(struct_: &Struct) -> TokenStream {
10271034
{{
10281035
{diff_ref_enum_body}
10291036
}}
1037+
{exposed_const_start}
1038+
1039+
10301040
10311041
impl{ref_enum_def_generics} Into<{enum_name}{owned_enum_impl_generics}> for {enum_name}Ref{ref_enum_impl_generics}
10321042
where
@@ -1070,6 +1080,8 @@ pub(crate) fn derive_struct_diff_struct(struct_: &Struct) -> TokenStream {
10701080
10711081
{setters}
10721082
}};",
1083+
non_exposed_const_start = if exposed.is_some() { "" } else { const_start },
1084+
exposed_const_start = if exposed.is_some() { const_start } else { "" },
10731085
type_aliases = owned_type_aliases,
10741086
ref_type_aliases = ref_type_aliases,
10751087
nanoserde_hack = nanoserde_hack,
@@ -1253,7 +1265,14 @@ pub(crate) fn derive_struct_diff_enum(enum_: &Enum) -> TokenStream {
12531265
let mut type_aliases = String::new();
12541266
let mut used_generics: Vec<&Generic> = Vec::new();
12551267

1256-
let enum_name = String::from("__".to_owned() + enum_.name.as_str() + "StructDiffEnum");
1268+
let exposed = attrs_expose(&enum_.attributes);
1269+
1270+
let enum_name = match exposed.clone() {
1271+
Some(Some(name)) => name,
1272+
Some(None) => String::from(enum_.name.clone() + "StructDiffEnum"),
1273+
_ => String::from("__".to_owned() + &enum_.name + "StructDiffEnum"),
1274+
};
1275+
12571276
let ref_into_owned_body = format!(
12581277
"Self::Replace(variant) => {}::Replace(variant.clone()),",
12591278
&enum_name
@@ -1425,12 +1444,10 @@ pub(crate) fn derive_struct_diff_enum(enum_: &Enum) -> TokenStream {
14251444
#[cfg(not(feature = "serde"))]
14261445
let serde_bound = "";
14271446

1428-
format!(
1429-
"const _: () = {{
1430-
use structdiff::collections::*;
1431-
{type_aliases}
1432-
{nanoserde_hack}
1447+
let const_start = "#[allow(non_camel_case_types)]\nconst _: () = {";
14331448

1449+
format!(
1450+
"{non_exposed_const_start}
14341451
/// Generated type from StructDiff
14351452
#[derive({owned_derives})]{serde_bounds}
14361453
#[allow(non_camel_case_types)]
@@ -1450,6 +1467,10 @@ pub(crate) fn derive_struct_diff_enum(enum_: &Enum) -> TokenStream {
14501467
{{
14511468
Replace(&'__diff_target {struct_name}{struct_generics})
14521469
}}
1470+
{exposed_const_start}
1471+
1472+
{type_aliases}
1473+
{nanoserde_hack}
14531474
14541475
impl{ref_enum_def_generics} Into<{enum_name}{enum_impl_generics}> for {enum_name}Ref{ref_enum_impl_generics}
14551476
where
@@ -1500,6 +1521,8 @@ pub(crate) fn derive_struct_diff_enum(enum_: &Enum) -> TokenStream {
15001521
}}
15011522
}}
15021523
}};",
1524+
non_exposed_const_start = if exposed.is_some() { "" } else { const_start },
1525+
exposed_const_start = if exposed.is_some() { const_start } else { "" },
15031526
type_aliases = type_aliases,
15041527
nanoserde_hack = nanoserde_hack,
15051528
owned_derives = owned_derives,

derive/src/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@ mod parse;
1616
pub fn derive_struct_diff(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
1717
let input = parse::parse_data(input);
1818

19-
// ok we have an ident, hopefully it's a struct
2019
let ts = match &input {
2120
parse::Data::Struct(struct_) if struct_.named => derive_struct_diff_struct(struct_),
2221
parse::Data::Enum(enum_) => derive_struct_diff_enum(enum_),
23-
_ => unimplemented!("Only structs are supported"),
22+
_ => unimplemented!("Only structs and enums are supported"),
2423
};
2524

2625
ts

derive/src/parse.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,23 +1136,25 @@ fn next_attribute<T: Iterator<Item = TokenTree>>(
11361136
next_eof(&mut args_group).is_some(),
11371137
next_punct(&mut args_group).as_deref() == Some(","),
11381138
) {
1139-
(true, _) => {
1139+
(false, true) => {
11401140
attrs.push(Attribute {
11411141
name: name.clone(),
11421142
tokens: std::mem::take(&mut attr_tokens),
11431143
});
1144-
break;
11451144
}
1146-
(false, true) => {
1145+
_ => {
11471146
attrs.push(Attribute {
11481147
name: name.clone(),
11491148
tokens: std::mem::take(&mut attr_tokens),
11501149
});
1150+
break;
11511151
}
1152-
_ => {}
11531152
}
11541153
}
11551154

1155+
// if !attrs.is_empty() {
1156+
// panic!("{:?}", attrs)
1157+
// }
11561158
return Some(Some(attrs));
11571159
}
11581160

derive/src/shared.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,11 @@ pub fn attrs_map_strategy(attributes: &[crate::parse::Attribute]) -> Option<MapS
100100
}
101101
})
102102
}
103+
104+
pub fn attrs_expose(attributes: &[crate::parse::Attribute]) -> Option<Option<String>> {
105+
attributes.iter().find_map(|attr| match attr.tokens.len() {
106+
1 if attr.tokens[0].starts_with("expose") => Some(None),
107+
2.. if attr.tokens[0] == "expose" => Some(Some((&attr.tokens[1]).to_string())),
108+
_ => return None,
109+
})
110+
}

src/collections/ordered_array_like.rs

Lines changed: 14 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ fn create_last_change_row<'src, 'target: 'src, T: Clone + PartialEq + 'target>(
237237
let mut target_forward = target_start..target_end;
238238
let mut target_rev = (target_end..target_start).rev();
239239

240+
#[allow(clippy::type_complexity)]
240241
let (target_range, source_range): (
241242
&mut dyn Iterator<Item = usize>,
242243
Box<dyn Fn() -> Box<dyn Iterator<Item = usize>>>,
@@ -750,9 +751,7 @@ mod test {
750751
return;
751752
};
752753

753-
let changed = apply(changes, s2.chars().collect::<Vec<_>>())
754-
.into_iter()
755-
.collect::<String>();
754+
let changed = apply(changes, s2.chars().collect::<Vec<_>>()).collect::<String>();
756755
assert_eq!(s1, changed)
757756
}
758757
}
@@ -765,18 +764,14 @@ mod test {
765764
let s1_vec = s1.chars().collect::<Vec<_>>();
766765
let s2_vec = s2.chars().collect::<Vec<_>>();
767766

768-
for diff_type in [
769-
// levenshtein,
770-
hirschberg,
771-
] {
767+
{
768+
let diff_type = hirschberg;
772769
let Some(changes) = diff_type(&s1_vec, &s2_vec) else {
773770
assert_eq!(&s1_vec, &s2_vec);
774771
return;
775772
};
776773

777-
let changed = apply(changes, s2.chars().collect::<Vec<_>>())
778-
.into_iter()
779-
.collect::<String>();
774+
let changed = apply(changes, s2.chars().collect::<Vec<_>>()).collect::<String>();
780775
assert_eq!(s1, changed)
781776
}
782777
}
@@ -786,10 +781,8 @@ mod test {
786781
let s1: Vec<char> = "abc".chars().collect();
787782
let s2: Vec<char> = "".chars().collect();
788783

789-
for diff_type in [
790-
// levenshtein,
791-
hirschberg,
792-
] {
784+
{
785+
let diff_type = hirschberg;
793786
let Some(changes) = diff_type(&s1, &s2) else {
794787
assert_eq!(s1, s2);
795788
return;
@@ -870,9 +863,7 @@ mod test {
870863
continue;
871864
};
872865

873-
let changed = apply(changes, s2_vec.clone())
874-
.into_iter()
875-
.collect::<Vec<char>>();
866+
let changed = apply(changes, s2_vec.clone()).collect::<Vec<char>>();
876867
assert_eq!(&s1_vec, &changed);
877868
}
878869
}
@@ -1005,9 +996,7 @@ mod test {
1005996
return;
1006997
};
1007998

1008-
let changed = apply(changes, s2.chars().collect::<Vec<_>>())
1009-
.into_iter()
1010-
.collect::<String>();
999+
let changed = apply(changes, s2.chars().collect::<Vec<_>>()).collect::<String>();
10111000
assert_eq!(s1, changed)
10121001
}
10131002

@@ -1024,9 +1013,7 @@ mod test {
10241013
return;
10251014
};
10261015

1027-
let changed = apply(changes, s2.chars().collect::<Vec<_>>())
1028-
.into_iter()
1029-
.collect::<String>();
1016+
let changed = apply(changes, s2.chars().collect::<Vec<_>>()).collect::<String>();
10301017
assert_eq!(s1, changed)
10311018
}
10321019

@@ -1043,9 +1030,7 @@ mod test {
10431030
return;
10441031
};
10451032

1046-
let changed = apply(changes, s2.chars().collect::<Vec<_>>())
1047-
.into_iter()
1048-
.collect::<String>();
1033+
let changed = apply(changes, s2.chars().collect::<Vec<_>>()).collect::<String>();
10491034
assert_eq!(s1, changed)
10501035
}
10511036
}
@@ -1063,9 +1048,7 @@ mod test {
10631048
return;
10641049
};
10651050

1066-
let changed = apply(changes, s2.chars().collect::<Vec<_>>())
1067-
.into_iter()
1068-
.collect::<String>();
1051+
let changed = apply(changes, s2.chars().collect::<Vec<_>>()).collect::<String>();
10691052
assert_eq!(s1, changed)
10701053
}
10711054
}
@@ -1083,9 +1066,7 @@ mod test {
10831066
return;
10841067
};
10851068

1086-
let changed = apply(changes, s2.chars().collect::<Vec<_>>())
1087-
.into_iter()
1088-
.collect::<String>();
1069+
let changed = apply(changes, s2.chars().collect::<Vec<_>>()).collect::<String>();
10891070
assert_eq!(s1, changed)
10901071
}
10911072
}
@@ -1103,9 +1084,7 @@ mod test {
11031084
return;
11041085
};
11051086

1106-
let changed = apply(changes, s2.chars().collect::<Vec<_>>())
1107-
.into_iter()
1108-
.collect::<String>();
1087+
let changed = apply(changes, s2.chars().collect::<Vec<_>>()).collect::<String>();
11091088
assert_eq!(s1, changed)
11101089
}
11111090
}

src/collections/unordered_array_like.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ pub fn unordered_hashcmp<
166166
UnorderedArrayLikeDiffInternal::Replace(
167167
current
168168
.into_iter()
169-
.flat_map(|(k, v)| std::iter::repeat(k).take(v))
169+
.flat_map(|(k, v)| std::iter::repeat_n(k, v))
170170
.collect(),
171171
),
172172
));
@@ -333,7 +333,7 @@ where
333333
Box::new(
334334
list_hash
335335
.into_iter()
336-
.flat_map(|(k, v)| std::iter::repeat(k).take(v)),
336+
.flat_map(|(k, v)| std::iter::repeat_n(k, v)),
337337
)
338338
}
339339

src/collections/unordered_map_like.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ pub fn unordered_hashcmp<
167167
return Some(UnorderedMapLikeDiff(UnorderedMapLikeDiffInternal::Replace(
168168
current
169169
.into_iter()
170-
.flat_map(|(k, (v, count))| std::iter::repeat((k, v)).take(count))
170+
.flat_map(|(k, (v, count))| std::iter::repeat_n((k, v), count))
171171
.collect(),
172172
)));
173173
}
@@ -319,7 +319,7 @@ pub fn apply_unordered_hashdiffs<
319319
Box::new(
320320
list_hash
321321
.into_iter()
322-
.flat_map(|(k, (v, count))| std::iter::repeat((k.clone(), v.clone())).take(count))
322+
.flat_map(|(k, (v, count))| std::iter::repeat_n((k.clone(), v.clone()), count))
323323
.collect::<Vec<_>>()
324324
.into_iter(),
325325
)

tests/derives.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![allow(unused_imports)]
1+
#![allow(unused_imports, clippy::type_complexity)]
22

33
use std::{
44
collections::{BTreeMap, BTreeSet, HashMap, HashSet},

0 commit comments

Comments
 (0)