diffus_derive/
lib.rs

1extern crate proc_macro;
2
3use quote::{format_ident, quote};
4
5type Output = proc_macro2::TokenStream;
6
7fn edit_fields(fields: &syn::Fields, lifetime: &syn::Lifetime) -> Output {
8    let edit_fields = fields.iter().map(|field| match field {
9        syn::Field {
10            ident: Some(ident),
11            ty,
12            vis,
13            ..
14        } => quote! {
15            #vis #ident: diffus::edit::Edit<#lifetime, #ty>
16        },
17        syn::Field {
18            ident: None,
19            ty,
20            vis,
21            ..
22        } => quote! {
23            #vis diffus::edit::Edit<#lifetime, #ty>
24        },
25    });
26
27    quote! { #(#edit_fields),* }
28}
29
30fn field_ident(enumerated_field: (usize, &syn::Field), prefix: &str) -> syn::Ident {
31    match enumerated_field {
32        (
33            _,
34            syn::Field {
35                ident: Some(ident), ..
36            },
37        ) => format_ident!("{}{}", prefix, ident),
38        (i, syn::Field { ident: None, .. }) => {
39            format_ident!("{}{}", prefix, unnamed_field_ident(i))
40        }
41    }
42}
43
44fn field_idents(fields: &syn::Fields, prefix: &str) -> Output {
45    let field_idents = fields
46        .iter()
47        .enumerate()
48        .map(|enumerated_field| field_ident(enumerated_field, prefix));
49
50    quote! { #(#field_idents),* }
51}
52
53fn renamed_field_ident(enumerated_field: (usize, &syn::Field), prefix: &str) -> Output {
54    match enumerated_field {
55        (
56            _,
57            syn::Field {
58                ident: Some(ident), ..
59            },
60        ) => {
61            let new_ident = format_ident!("{}{}", prefix, ident);
62
63            quote! { #ident: #new_ident }
64        }
65        (_, syn::Field { ident: None, .. }) => unreachable!(),
66    }
67}
68
69fn renamed_field_idents(fields: &syn::Fields, prefix: &str) -> Output {
70    let field_idents = fields
71        .iter()
72        .enumerate()
73        .map(|enumerated_field| renamed_field_ident(enumerated_field, prefix));
74
75    quote! { #(#field_idents),* }
76}
77
78fn matches_all_copy(fields: &syn::Fields) -> Output {
79    let edit_fields_copy = fields.iter().enumerate().map(|_| {
80        quote! { diffus::edit::Edit::Copy(_) }
81    });
82
83    quote! {
84        ( #(#edit_fields_copy),* ) => diffus::edit::Edit::Copy(self)
85    }
86}
87
88fn field_diffs(fields: &syn::Fields) -> Output {
89    let field_diffs = fields.iter().enumerate().map(|(index, field)| {
90        let field_name = match field {
91            syn::Field {
92                ident: Some(ident), ..
93            } => quote! { #ident },
94            syn::Field { ident: None, .. } => {
95                let ident = unnamed_field_name(index);
96
97                quote! { #ident }
98            }
99        };
100
101        quote! {
102            diffus::Diffable::diff(&self.#field_name, &other.#field_name)
103        }
104    });
105
106    quote! { #(#field_diffs),* }
107}
108
109fn unnamed_field_ident(i: usize) -> syn::Ident {
110    format_ident!("x{}", i as u32)
111}
112fn unnamed_field_name(i: usize) -> syn::Lit {
113    syn::parse_str(&format!("{}", i as u32)).unwrap()
114}
115
116fn input_lifetime(generics: &syn::Generics) -> Option<&syn::Lifetime> {
117    let mut lifetimes = generics.params.iter().filter_map(|generic_param| {
118        if let syn::GenericParam::Lifetime(syn::LifetimeDef { lifetime, .. }) = generic_param {
119            Some(lifetime)
120        } else {
121            None
122        }
123    });
124
125    let lifetime = lifetimes.next();
126
127    assert!(
128        lifetimes.next().is_none(),
129        "Multiple lifetimes not supported yet"
130    );
131
132    lifetime
133}
134
135#[proc_macro_derive(Diffus)]
136pub fn derive_diffus(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
137    let input: syn::DeriveInput = syn::parse2(proc_macro2::TokenStream::from(input)).unwrap();
138
139    let ident = &input.ident;
140    let vis = &input.vis;
141    let where_clause = &input.generics.where_clause;
142    let edited_ident = syn::parse_str::<syn::Path>(&format!("Edited{}", ident)).unwrap();
143
144    let data_lifetime = input_lifetime(&input.generics);
145    let default_lifetime = syn::parse_str::<syn::Lifetime>("'diffus_a").unwrap();
146    let impl_lifetime = data_lifetime.unwrap_or(&default_lifetime);
147
148    #[cfg(feature = "serialize-impl")]
149    let derive_serialize = Some(quote! { #[derive(serde::Serialize)] });
150    #[cfg(not(feature = "serialize-impl"))]
151    let derive_serialize: Option<proc_macro2::TokenStream> = None;
152
153    proc_macro::TokenStream::from(match input.data {
154        syn::Data::Enum(syn::DataEnum { variants, .. }) => {
155            let edit_variants = variants.iter().map(|syn::Variant { ident, fields, .. }| {
156                let edit_fields = edit_fields(&fields, &impl_lifetime);
157
158                match fields {
159                    syn::Fields::Named(syn::FieldsNamed { .. }) => {
160                        quote! {
161                            #ident { #edit_fields }
162                        }
163                    }
164                    syn::Fields::Unnamed(syn::FieldsUnnamed { .. }) => {
165                        quote! {
166                            #ident ( #edit_fields )
167                        }
168                    }
169                    syn::Fields::Unit => {
170                        quote! {
171                            #ident
172                        }
173                    }
174                }
175            });
176
177            let has_non_unit_variant = variants.iter().any(|syn::Variant { fields, .. }| {
178                if let syn::Fields::Unit = fields {
179                    false
180                } else {
181                    true
182                }
183            });
184
185            let unit_enum_impl_lifetime = if has_non_unit_variant {
186                Some(impl_lifetime.clone())
187            } else {
188                None
189            };
190
191            let variants_matches = variants.iter().map(|syn::Variant { ident: variant_ident, fields, .. }| {
192
193                let field_diffs = fields.iter().enumerate().map(|(i, field)| {
194                    let self_field_ident = field_ident((i, field), "self_");
195                    let other_field_ident = field_ident((i, field), "other_");
196
197                    quote! {
198                        #self_field_ident . diff(& #other_field_ident )
199                    }
200                });
201                let field_diffs = quote! { #(#field_diffs),* };
202
203                let matches_all_copy = matches_all_copy(&fields);
204                let just_field_idents = field_idents(&fields, "");
205                let self_field_idents = field_idents(&fields, "self_");
206                let other_field_idents = field_idents(&fields, "other_");
207
208                match fields {
209                    syn::Fields::Named(syn::FieldsNamed { .. }) => {
210                        let self_field_idents = renamed_field_idents(&fields, "self_");
211                        let other_field_idents = renamed_field_idents(&fields, "other_");
212
213                        quote! {
214                            (
215                                #ident::#variant_ident { #self_field_idents },
216                                #ident::#variant_ident { #other_field_idents }
217                            ) => {
218                                match ( #field_diffs ) {
219                                    #matches_all_copy,
220                                    ( #just_field_idents ) => {
221                                        diffus::edit::Edit::Change(
222                                            diffus::edit::enm::Edit::AssociatedChanged(
223                                                #edited_ident::#variant_ident { #just_field_idents }
224                                            )
225                                        )
226                                    }
227                                }
228                            }
229                        }
230                    },
231                    syn::Fields::Unnamed(syn::FieldsUnnamed { .. }) => {
232                        quote! {
233                            (
234                                #ident::#variant_ident( #self_field_idents ),
235                                #ident::#variant_ident( #other_field_idents )
236                            ) => {
237                                match ( #field_diffs ) {
238                                    #matches_all_copy,
239                                    ( #just_field_idents ) => {
240                                        diffus::edit::Edit::Change(
241                                            diffus::edit::enm::Edit::AssociatedChanged(
242                                                #edited_ident::#variant_ident ( #just_field_idents )
243                                            )
244                                        )
245                                    }
246                                }
247                            }
248                        }
249                    },
250                    syn::Fields::Unit => {
251                        quote! {
252                            (
253                                #ident::#variant_ident,
254                                #ident::#variant_ident
255                            ) => {
256                                diffus::edit::Edit::Copy(self)
257                            }
258                        }
259                    },
260                }
261            });
262
263            quote! {
264                #derive_serialize
265                #vis enum #edited_ident <#unit_enum_impl_lifetime> where #where_clause {
266                    #(#edit_variants),*
267                }
268
269                impl<#impl_lifetime> diffus::Diffable<#impl_lifetime> for #ident <#data_lifetime> where #where_clause {
270                    type Diff = diffus::edit::enm::Edit<#impl_lifetime, Self, #edited_ident <#unit_enum_impl_lifetime>>;
271
272                    fn diff(&#impl_lifetime self, other: &#impl_lifetime Self) -> diffus::edit::Edit<#impl_lifetime, Self> {
273                        match (self, other) {
274                            #(#variants_matches,)*
275                            (self_variant, other_variant) => diffus::edit::Edit::Change(diffus::edit::enm::Edit::VariantChanged(
276                                self_variant, other_variant
277                            )),
278                        }
279                    }
280                }
281            }
282        }
283        syn::Data::Struct(syn::DataStruct { fields, .. }) => {
284            let edit_fields = edit_fields(&fields, &impl_lifetime);
285            let field_diffs = field_diffs(&fields);
286            let field_idents = field_idents(&fields, "");
287            let matches_all_copy = matches_all_copy(&fields);
288
289            match fields {
290                syn::Fields::Named(_) => {
291                    quote! {
292                        #derive_serialize
293                        #vis struct #edited_ident<#impl_lifetime> where #where_clause {
294                            #edit_fields
295                        }
296
297                        impl<#impl_lifetime> diffus::Diffable<#impl_lifetime> for #ident <#data_lifetime> where #where_clause {
298                            type Diff = #edited_ident<#impl_lifetime>;
299
300                            fn diff(&#impl_lifetime self, other: &#impl_lifetime Self) -> diffus::edit::Edit<#impl_lifetime, Self> {
301                                match ( #field_diffs ) {
302                                    #matches_all_copy,
303                                    ( #field_idents ) => diffus::edit::Edit::Change(
304                                        #edited_ident { #field_idents }
305                                    )
306                                }
307                            }
308                        }
309                    }
310                }
311                syn::Fields::Unnamed(_) => {
312                    quote! {
313                        #derive_serialize
314                        #vis struct #edited_ident<#impl_lifetime> ( #edit_fields ) where #where_clause;
315
316                        impl<#impl_lifetime> diffus::Diffable<#impl_lifetime> for #ident <#data_lifetime> where #where_clause {
317                            type Diff = #edited_ident<#impl_lifetime>;
318
319                            fn diff(&#impl_lifetime self, other: &#impl_lifetime Self) -> diffus::edit::Edit<#impl_lifetime, Self> {
320                                match ( #field_diffs ) {
321                                    #matches_all_copy,
322                                    ( #field_idents ) => diffus::edit::Edit::Change(
323                                        #edited_ident ( #field_idents )
324                                    )
325                                }
326                            }
327                        }
328                    }
329                }
330                syn::Fields::Unit => {
331                    quote! {
332                        #derive_serialize
333                        #vis struct #edited_ident< > where #where_clause;
334
335                        impl<#impl_lifetime> diffus::Diffable<#impl_lifetime> for #ident< > where #where_clause {
336                            type Diff = #edited_ident;
337
338                            fn diff(&#impl_lifetime self, other: &#impl_lifetime Self) -> diffus::edit::Edit<#impl_lifetime, Self> {
339                                diffus::edit::Edit::Copy(self)
340                            }
341                        }
342                    }
343                }
344            }
345        }
346        syn::Data::Union(_) => panic!("union type not supported yet"),
347    })
348}