merge_rs_derive/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::quote;
4use syn::{parse_macro_input, Ident};
5use darling::{ast, util, FromDeriveInput, FromField};
6
7#[derive(Debug, FromField)]
8#[darling(attributes(merge_field))]
9struct MergeField {
10    ident: Option<Ident>,
11    strategy: Option<String>,
12    #[darling(default)]
13    skip: bool,
14}
15
16#[derive(Debug, FromDeriveInput)]
17#[darling(attributes(merge_field), supports(struct_any))]
18struct MergeTarget {
19    ident: Ident,
20    data: ast::Data<util::Ignored, MergeField>,
21}
22
23type FieldGenFn = fn(MergeField, proc_macro2::TokenStream) -> Option<proc_macro2::TokenStream>;
24type TraitGenFn = fn(Ident, Vec<proc_macro2::TokenStream>) -> proc_macro2::TokenStream;
25
26fn gen_field_lines(
27    fields: Option<ast::Fields<MergeField>>,
28    gen_field: FieldGenFn,
29) -> Vec<proc_macro2::TokenStream> {
30    let mut res = vec![];
31    if let Some(fields) = fields {
32        for (idx, field) in fields.into_iter().enumerate() {
33            let field_token = match &field.ident {
34                Some(field_name) => quote! { #field_name },
35                None => {
36                    let idx = syn::Index::from(idx);
37                    quote! { #idx }
38                }
39            };
40            if let Some(field) = gen_field(field, field_token) {
41                res.push(field);
42            }
43        }
44    }
45    res
46}
47
48fn do_derive(
49    input: TokenStream,
50    gen_field: FieldGenFn,
51    gen_trait: TraitGenFn,
52) -> TokenStream {
53    let parsed = parse_macro_input!(input);
54    match MergeTarget::from_derive_input(&parsed) {
55        Err(e) => e.write_errors(),
56        Ok(target) => {
57            let target_ident = target.ident;
58            let fields = gen_field_lines(target.data.take_struct(), gen_field);
59            gen_trait(target_ident, fields)
60        }
61    }.into()
62}
63
64#[proc_macro_derive(MergeMut, attributes(merge_field))]
65pub fn derive_merge_mut(input: TokenStream) -> TokenStream {
66    do_derive(
67        input,
68        |field, field_token| {
69            match field.strategy {
70                _ if field.skip => None,
71                None => Some(quote! {
72                    self.#field_token.merge_mut(&other.#field_token)?;
73                }),
74                Some(strategy) => {
75                    let strategy_fn = Ident::new(&strategy, Span::call_site());
76                    Some(quote! {
77                        #strategy_fn(&mut self.#field_token, &other.#field_token); // TODO: bug here?
78                    })
79                }
80            }
81        },
82        |target, fields| {
83            quote! {
84                impl MergeMut for #target {
85                    fn merge_mut(&mut self, other: &Self) -> Result<(), Box<dyn std::error::Error>> {
86                        #(#fields)*
87                        Ok(())
88                    }
89                }
90            }
91        })
92}
93
94#[proc_macro_derive(Merge, attributes(merge_field))]
95pub fn derive_merge(input: TokenStream) -> TokenStream {
96    do_derive(
97        input,
98        |field, field_token| {
99            match field.strategy {
100                _ if field.skip => Some(quote! {
101                    #field_token: self.#field_token.clone(),
102                }),
103                None => Some(quote! {
104                    #field_token: self.#field_token.merge(&other.#field_token)?,
105                }),
106                Some(strategy) => {
107                    let strategy_fn = Ident::new(&strategy, Span::call_site());
108                    Some(quote! {
109                        #field_token: #strategy_fn(&self.#field_token, &other.#field_token)?,
110                    })
111                }
112            }
113        },
114        |target, fields| {
115            quote! {
116                impl Merge for #target {
117                    fn merge(&self, other: &Self) -> Result<Self, Box<dyn std::error::Error>> where Self: Sized {
118                        Ok(Self {
119                            #(#fields)*
120                        })
121                    }
122                }
123            }
124        },
125    )
126}