Skip to main content

deltapatcher_macros/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::{Span, TokenStream as TokenStream2};
3use quote::quote;
4use syn::{parse_macro_input, Data, DeriveInput};
5
6use darling::{FromAttributes, FromDeriveInput};
7
8/// Field-level config via attributes.
9#[derive(FromAttributes)]
10#[darling(attributes(deltapatcher))]
11struct MacroFieldConfig {
12    /// For fields with multiple candidate delta types,
13    /// use this to specify the delta type to use.
14    delta_ty: syn::Type,
15}
16
17/// Struct-level config via attributes.
18#[derive(FromDeriveInput)]
19#[darling(attributes(deltapatcher))]
20struct MacroOverallConfig {
21    delta_name: Option<syn::Ident>,
22    delta_vis: Option<syn::Visibility>,
23    delta_derive: Option<Vec<syn::Ident>>,
24}
25
26impl MacroOverallConfig {
27    fn delta_name_or_default(&self, ty_name: &syn::Ident) -> syn::Ident {
28        self.delta_name
29            .clone()
30            .unwrap_or_else(|| syn::Ident::new(&format!("{ty_name}Delta"), Span::call_site()))
31    }
32}
33
34/// Helper to detect if `Diff` is also being derived, to avoid duplicate code generation.
35fn has_derive_diff(ast: &DeriveInput) -> bool {
36    ast.attrs.iter().any(|attr| {
37        if attr.path().is_ident("derive") {
38            let mut found_diff = false;
39            let _ = attr.parse_nested_meta(|meta| {
40                if meta.path.is_ident("Diff") {
41                    found_diff = true;
42                }
43                Ok(())
44            });
45            found_diff
46        } else {
47            false
48        }
49    })
50}
51
52/// Derives [`Diff`] and [`Patch`] for a struct or enum, generating a corresponding Delta type.
53#[proc_macro_derive(Diff, attributes(deltapatcher))]
54pub fn derive_diff(input: TokenStream) -> TokenStream {
55    let ast = parse_macro_input!(input as DeriveInput);
56    expand_macro(ast, true)
57}
58
59/// Derives only [`Patch`] for a struct or enum, generating a corresponding Delta type.
60#[proc_macro_derive(Patch, attributes(deltapatcher))]
61pub fn derive_patch(input: TokenStream) -> TokenStream {
62    let ast = parse_macro_input!(input as DeriveInput);
63    
64    // If both Diff and Patch are derived, let derive(Diff) handle everything
65    // to prevent duplicate Delta structs and implementations.
66    if has_derive_diff(&ast) {
67        return TokenStream::new();
68    }
69    
70    expand_macro(ast, false)
71}
72
73fn expand_macro(ast: DeriveInput, generate_diff: bool) -> TokenStream {
74    let top_level_config = MacroOverallConfig::from_derive_input(&ast).unwrap();
75
76    match ast.data {
77        Data::Enum(e) => expand_enum(ast.ident, e, top_level_config, generate_diff),
78        Data::Struct(s) => expand_struct(ast.vis, ast.ident, s, top_level_config, generate_diff),
79        Data::Union(_) => Err(syn::Error::new(
80            Span::call_site(),
81            "Unions are not supported by deltapatcher derives",
82        )),
83    }
84    .unwrap_or_else(syn::Error::into_compile_error)
85    .into()
86}
87
88fn expand_enum(
89    ident: syn::Ident,
90    e: syn::DataEnum,
91    config: MacroOverallConfig,
92    generate_diff: bool,
93) -> syn::Result<TokenStream2> {
94    let delta_name = config.delta_name_or_default(&ident);
95    let delta_derive = config
96        .delta_derive
97        .map(|vals| quote! { #[derive(#(#vals),*)] })
98        .unwrap_or(quote! {});
99    let delta_doc = format!("Autogenerated delta for enum [`{ident}`]");
100
101    struct VariantInfo {
102        ident: syn::Ident,
103        fields: syn::Fields,
104        delta_types: Vec<syn::Type>,
105    }
106
107    let mut variants: Vec<VariantInfo> = Vec::with_capacity(e.variants.len());
108    for variant in &e.variants {
109        let mut delta_types = Vec::with_capacity(variant.fields.len());
110        for field in &variant.fields {
111            if let Ok(field_config) = MacroFieldConfig::from_attributes(&field.attrs) {
112                delta_types.push(field_config.delta_ty);
113            } else {
114                return Err(syn::Error::new_spanned(
115                    field,
116                    "Cannot infer delta type. Field must be annotated with #[deltapatcher(delta_ty = SomeDelta)].",
117                ));
118            }
119        }
120        variants.push(VariantInfo {
121            ident: variant.ident.clone(),
122            fields: variant.fields.clone(),
123            delta_types,
124        });
125    }
126
127    // ── Build the delta enum variants ──────────────────────────────────────
128    let delta_variant_defs = variants.iter().map(|v| {
129        let vname = &v.ident;
130        let dtypes = &v.delta_types;
131        match &v.fields {
132            syn::Fields::Named(n) => {
133                let names: Vec<_> = n.named.iter().map(|f| f.ident.clone().unwrap()).collect();
134                quote! { #vname { #(#names: #dtypes),* } }
135            }
136            syn::Fields::Unnamed(_) => quote! { #vname(#(#dtypes),*) },
137            syn::Fields::Unit => quote! { #vname },
138        }
139    });
140
141    // ── diff() match arms (Conditional) ───────────────────────────────────
142    let diff_impl = if generate_diff {
143        let diff_arms = variants.iter().map(|v| {
144            let vname = &v.ident;
145            let ftypes = v.fields.iter().map(|f| f.ty.clone()).collect::<Vec<_>>();
146            let dtypes = &v.delta_types;
147            match &v.fields {
148                syn::Fields::Named(n) => {
149                    let names: Vec<_> = n.named.iter().map(|f| f.ident.clone().unwrap()).collect();
150                    let s_names: Vec<_> = names
151                        .iter()
152                        .map(|n| syn::Ident::new(&format!("s_{n}"), Span::call_site()))
153                        .collect();
154                    let i_names: Vec<_> = names
155                        .iter()
156                        .map(|n| syn::Ident::new(&format!("i_{n}"), Span::call_site()))
157                        .collect();
158                    quote! {
159                        (#ident::#vname { #(#names: #s_names),* }, #ident::#vname { #(#names: #i_names),* }) =>
160                            #delta_name::#vname {
161                                #(#names: <#ftypes as deltapatcher::Diff<#dtypes>>::diff(#s_names, #i_names)),*
162                            }
163                    }
164                }
165                syn::Fields::Unnamed(un) => {
166                    let count = un.unnamed.len();
167                    let s_ids: Vec<_> = (0..count)
168                        .map(|i| syn::Ident::new(&format!("s_{i}"), Span::call_site()))
169                        .collect();
170                    let i_ids: Vec<_> = (0..count)
171                        .map(|i| syn::Ident::new(&format!("i_{i}"), Span::call_site()))
172                        .collect();
173                    quote! {
174                        (#ident::#vname(#(#s_ids),*), #ident::#vname(#(#i_ids),*)) =>
175                            #delta_name::#vname(
176                                #(<#ftypes as deltapatcher::Diff<#dtypes>>::diff(#s_ids, #i_ids)),*
177                            )
178                    }
179                }
180                syn::Fields::Unit => quote! {
181                    (#ident::#vname, #ident::#vname) => #delta_name::#vname
182                },
183            }
184        });
185
186        quote! {
187            impl deltapatcher::Diff<#delta_name> for #ident {
188                fn diff(&self, initial: &Self) -> #delta_name {
189                    match (self, initial) {
190                        #(#diff_arms,)*
191                        _ => #delta_name::Replace(self.clone()),
192                    }
193                }
194            }
195        }
196    } else {
197        quote! {}
198    };
199
200    // ── patch() match arms ────────────────────────────────────────────────
201    let patch_arms = variants.iter().map(|v| {
202        let vname = &v.ident;
203        let ftypes = v.fields.iter().map(|f| f.ty.clone()).collect::<Vec<_>>();
204        let dtypes = &v.delta_types;
205        match &v.fields {
206            syn::Fields::Named(n) => {
207                let names: Vec<_> = n.named.iter().map(|f| f.ident.clone().unwrap()).collect();
208                let d_names: Vec<_> = names
209                    .iter()
210                    .map(|n| syn::Ident::new(&format!("d_{n}"), Span::call_site()))
211                    .collect();
212                quote! {
213                    (#ident::#vname { #(#names),* }, #delta_name::#vname { #(#names: #d_names),* }) => {
214                        #(<#ftypes as deltapatcher::Patch<#dtypes>>::patch(#names, #d_names);)*
215                    }
216                }
217            }
218            syn::Fields::Unnamed(un) => {
219                let count = un.unnamed.len();
220                let s_ids: Vec<_> = (0..count)
221                    .map(|i| syn::Ident::new(&format!("s_{i}"), Span::call_site()))
222                    .collect();
223                let d_ids: Vec<_> = (0..count)
224                    .map(|i| syn::Ident::new(&format!("d_{i}"), Span::call_site()))
225                    .collect();
226                quote! {
227                    (#ident::#vname(#(#s_ids),*), #delta_name::#vname(#(#d_ids),*)) => {
228                        #(<#ftypes as deltapatcher::Patch<#dtypes>>::patch(#s_ids, #d_ids);)*
229                    }
230                }
231            }
232            syn::Fields::Unit => quote! {
233                (#ident::#vname, #delta_name::#vname) => {}
234            },
235        }
236    });
237
238    // ── aggregate() match arms ────────────────────────────────────────────
239    let aggregate_arms = variants.iter().map(|v| {
240        let vname = &v.ident;
241        let dtypes = &v.delta_types;
242        match &v.fields {
243            syn::Fields::Named(n) => {
244                let names: Vec<_> = n.named.iter().map(|f| f.ident.clone().unwrap()).collect();
245                let d_names: Vec<_> = names
246                    .iter()
247                    .map(|n| syn::Ident::new(&format!("d_{n}"), Span::call_site()))
248                    .collect();
249                quote! {
250                    (Self::#vname { #(#names),* }, Self::#vname { #(#names: #d_names),* }) => {
251                        #(<#dtypes as deltapatcher::delta::Delta>::aggregate(#names, #d_names);)*
252                    }
253                }
254            }
255            syn::Fields::Unnamed(un) => {
256                let count = un.unnamed.len();
257                let s_ids: Vec<_> = (0..count)
258                    .map(|i| syn::Ident::new(&format!("s_{i}"), Span::call_site()))
259                    .collect();
260                let d_ids: Vec<_> = (0..count)
261                    .map(|i| syn::Ident::new(&format!("d_{i}"), Span::call_site()))
262                    .collect();
263                quote! {
264                    (Self::#vname(#(#s_ids),*), Self::#vname(#(#d_ids),*)) => {
265                        #(<#dtypes as deltapatcher::delta::Delta>::aggregate(#s_ids, #d_ids);)*
266                    }
267                }
268            }
269            syn::Fields::Unit => quote! {
270                (Self::#vname, Self::#vname) => {}
271            },
272        }
273    });
274
275    // ── aggregate_owned() match arms ──────────────────────────────────────
276    let aggregate_owned_arms = variants.iter().map(|v| {
277        let vname = &v.ident;
278        let dtypes = &v.delta_types;
279        match &v.fields {
280            syn::Fields::Named(n) => {
281                let names: Vec<_> = n.named.iter().map(|f| f.ident.clone().unwrap()).collect();
282                let d_names: Vec<_> = names
283                    .iter()
284                    .map(|n| syn::Ident::new(&format!("d_{n}"), Span::call_site()))
285                    .collect();
286                quote! {
287                    (Self::#vname { #(#names),* }, Self::#vname { #(#names: #d_names),* }) => {
288                        #(<#dtypes as deltapatcher::delta::Delta>::aggregate_owned(#names, #d_names);)*
289                    }
290                }
291            }
292            syn::Fields::Unnamed(un) => {
293                let count = un.unnamed.len();
294                let s_ids: Vec<_> = (0..count)
295                    .map(|i| syn::Ident::new(&format!("s_{i}"), Span::call_site()))
296                    .collect();
297                let d_ids: Vec<_> = (0..count)
298                    .map(|i| syn::Ident::new(&format!("d_{i}"), Span::call_site()))
299                    .collect();
300                quote! {
301                    (Self::#vname(#(#s_ids),*), Self::#vname(#(#d_ids),*)) => {
302                        #(<#dtypes as deltapatcher::delta::Delta>::aggregate_owned(#s_ids, #d_ids);)*
303                    }
304                }
305            }
306            syn::Fields::Unit => quote! {
307                (Self::#vname, Self::#vname) => {}
308            },
309        }
310    });
311
312    Ok(quote! {
313        #[doc = #delta_doc]
314        #delta_derive
315        #[allow(dead_code)]
316        enum #delta_name {
317            #(#delta_variant_defs,)*
318            /// The enum variant changed entirely; replace with this value.
319            Replace(#ident),
320        }
321
322        impl deltapatcher::delta::Delta for #delta_name {
323            fn aggregate(&mut self, next: &Self) {
324                match (self, next) {
325                    (slf, Self::Replace(val)) => *slf = Self::Replace(val.clone()),
326                    (Self::Replace(current), next) => {
327                        <#ident as deltapatcher::Patch<#delta_name>>::patch(current, next);
328                    },
329                    #(#aggregate_arms,)*
330                    _ => unreachable!("delta variants disagree without a Replace — this is a bug"),
331                }
332            }
333
334            fn aggregate_owned(&mut self, next: Self) {
335                match (self, next) {
336                    (slf, Self::Replace(val)) => *slf = Self::Replace(val),
337                    (Self::Replace(current), next) => {
338                        <#ident as deltapatcher::Patch<#delta_name>>::patch(current, &next);
339                    },
340                    #(#aggregate_owned_arms,)*
341                    _ => unreachable!("delta variants disagree without a Replace — this is a bug"),
342                }
343            }
344        }
345
346        impl deltapatcher::Patch<#delta_name> for #ident {
347            fn patch(&mut self, delta: &#delta_name) {
348                match (self, delta) {
349                    #(#patch_arms,)*
350                    (slf, #delta_name::Replace(val)) => *slf = val.clone(),
351                    _ => unreachable!("mismatched non-Replace delta variant applied to wrong enum variant — this is a bug"),
352                }
353            }
354        }
355
356        #diff_impl
357    })
358}
359
360fn expand_struct(
361    vis: syn::Visibility,
362    ident: syn::Ident,
363    s: syn::DataStruct,
364    config: MacroOverallConfig,
365    generate_diff: bool,
366) -> syn::Result<TokenStream2> {
367    let mut delta_field_type = Vec::with_capacity(s.fields.len());
368    for field in &s.fields {
369        if let Ok(field_config) = MacroFieldConfig::from_attributes(&field.attrs) {
370            delta_field_type.push(field_config.delta_ty);
371        } else {
372            return Err(syn::Error::new_spanned(
373                field,
374                "Cannot infer delta type. Field must be annotated with #[deltapatcher(delta_ty = SomeDelta)].",
375            ));
376        }
377    }
378
379    let delta_name = config.delta_name_or_default(&ident);
380    let delta_derive = config
381        .delta_derive
382        .map(|vals| quote! { #[derive(#(#vals),*)] })
383        .unwrap_or(quote! {});
384    let delta_vis = config.delta_vis.unwrap_or(vis);
385    let delta_doc = format!("Autogenerated delta for struct [`{ident}`]");
386
387    Ok(match s.fields {
388        syn::Fields::Named(n) => {
389            let field_vis = n.named.iter().map(|field| field.vis.clone());
390            let field_name = n
391                .named
392                .iter()
393                .map(|f| f.ident.clone().unwrap())
394                .collect::<Vec<_>>();
395            let field_type = n.named.iter().map(|f| f.ty.clone()).collect::<Vec<_>>();
396
397            let diff_impl = if generate_diff {
398                quote! {
399                    impl deltapatcher::Diff<#delta_name> for #ident {
400                        fn diff(&self, initial: &Self) -> #delta_name {
401                            #delta_name {
402                                #(#field_name: <#field_type as deltapatcher::Diff<#delta_field_type>>::diff(&self.#field_name, &initial.#field_name)),*
403                            }
404                        }
405                    }
406                }
407            } else { quote! {} };
408
409            quote! {
410                #[doc = #delta_doc]
411                #delta_derive
412                #delta_vis struct #delta_name {
413                    #(#field_vis #field_name: #delta_field_type),*
414                }
415
416                impl deltapatcher::delta::Delta for #delta_name {
417                    fn aggregate(&mut self, next: &Self) {
418                        #(<#delta_field_type as deltapatcher::delta::Delta>::aggregate(&mut self.#field_name, &next.#field_name);)*
419                    }
420
421                    fn aggregate_owned(&mut self, next: Self) {
422                        #(<#delta_field_type as deltapatcher::delta::Delta>::aggregate_owned(&mut self.#field_name, next.#field_name);)*
423                    }
424                }
425
426                impl deltapatcher::Patch<#delta_name> for #ident {
427                    fn patch(&mut self, delta: &#delta_name) {
428                        #(<#field_type as deltapatcher::Patch<#delta_field_type>>::patch(&mut self.#field_name, &delta.#field_name);)*
429                    }
430                }
431
432                #diff_impl
433            }
434        }
435        syn::Fields::Unnamed(un) => {
436            let field_vis = un.unnamed.iter().map(|field| field.vis.clone());
437            let field_idx = (0..un.unnamed.len()).collect::<Vec<_>>();
438            let field_type = un.unnamed.iter().map(|f| f.ty.clone()).collect::<Vec<_>>();
439
440            let diff_impl = if generate_diff {
441                quote! {
442                    impl deltapatcher::Diff<#delta_name> for #ident {
443                        fn diff(&self, initial: &Self) -> #delta_name {
444                            #delta_name (#(<#field_type as deltapatcher::Diff<#delta_field_type>>::diff(&self.#field_idx, &initial.#field_idx)),*)
445                        }
446                    }
447                }
448            } else { quote! {} };
449
450            quote! {
451                #[doc = #delta_doc]
452                #delta_derive
453                #delta_vis struct #delta_name(#(#field_vis #delta_field_type),*);
454
455                impl deltapatcher::delta::Delta for #delta_name {
456                    fn aggregate(&mut self, next: &Self) {
457                        #(<#delta_field_type as deltapatcher::delta::Delta>::aggregate(&mut self.#field_idx, &next.#field_idx);)*
458                    }
459
460                    fn aggregate_owned(&mut self, next: Self) {
461                        #(<#delta_field_type as deltapatcher::delta::Delta>::aggregate_owned(&mut self.#field_idx, next.#field_idx);)*
462                    }
463                }
464
465                impl deltapatcher::Patch<#delta_name> for #ident {
466                    fn patch(&mut self, delta: &#delta_name) {
467                        #(<#field_type as deltapatcher::Patch<#delta_field_type>>::patch(&mut self.#field_idx, &delta.#field_idx);)*
468                    }
469                }
470
471                #diff_impl
472            }
473        }
474        syn::Fields::Unit => {
475            let diff_impl = if generate_diff {
476                quote! {
477                    impl deltapatcher::Diff<#delta_name> for #ident {
478                        fn diff(&self, _initial: &Self) -> #delta_name { #delta_name }
479                    }
480                }
481            } else { quote! {} };
482
483            quote! {
484                #[doc = #delta_doc]
485                #delta_derive
486                #delta_vis struct #delta_name;
487
488                impl deltapatcher::delta::Delta for #delta_name {
489                    fn aggregate(&mut self, _next: &Self) {}
490                    fn aggregate_owned(&mut self, _next: Self) {}
491                }
492
493                impl deltapatcher::Patch<#delta_name> for #ident {
494                    fn patch(&mut self, _delta: &#delta_name) {}
495                }
496
497                #diff_impl
498            }
499        }
500    })
501}