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); })
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}