Skip to main content

caretta_sync_macros/
lib.rs

1mod derive;
2
3use derive::*;
4use heck::ToUpperCamelCase;
5use proc_macro::{self, TokenStream};
6use proc_macro2::Span;
7use quote::{ToTokens, format_ident, quote};
8use syn::{
9    Data, DataStruct, DeriveInput, Expr, ExprTuple, Field, Fields, FieldsNamed, Ident,
10    parse_macro_input,
11};
12
13fn extract_unique_field_ident<'a>(
14    fields: &'a FieldsNamed,
15    attribute_arg: &'static str,
16) -> &'a Ident {
17    let mut fields = extract_field_idents(fields, attribute_arg);
18    if fields.len() == 1 {
19        return fields.pop().unwrap();
20    } else {
21        panic!("Model must need one {} field attribute", attribute_arg);
22    };
23}
24
25fn extract_field_idents<'a>(
26    fields: &'a FieldsNamed,
27    attribute_arg: &'static str,
28) -> Vec<&'a Ident> {
29    fields
30        .named
31        .iter()
32        .filter_map(|field| {
33            field.attrs.iter().find_map(|attr| {
34                if attr.path().is_ident("syncable") {
35                    let args: Expr = attr.parse_args().unwrap();
36
37                    match args {
38                        Expr::Tuple(arg_tupple) => arg_tupple.elems.iter().find_map(|arg| {
39                            if let Expr::Path(arg_path) = arg {
40                                if arg_path.path.is_ident(attribute_arg) {
41                                    Some(field.ident.as_ref().unwrap())
42                                } else {
43                                    None
44                                }
45                            } else {
46                                None
47                            }
48                        }),
49                        Expr::Path(arg_path) => {
50                            if arg_path.path.is_ident(attribute_arg) {
51                                Some(field.ident.as_ref().unwrap())
52                            } else {
53                                None
54                            }
55                        }
56                        _ => None,
57                    }
58                } else {
59                    None
60                }
61            })
62        })
63        .collect()
64}
65
66fn extract_fields(data: &Data) -> &FieldsNamed {
67    match *data {
68        Data::Struct(ref data) => match data.fields {
69            Fields::Named(ref fields) => fields,
70            _ => panic!("all fields must be named."),
71        },
72        _ => panic!("struct expected, but got other item."),
73    }
74}
75
76#[proc_macro_derive(Emptiable)]
77pub fn emptiable(input: TokenStream) -> TokenStream {
78    let input = parse_macro_input!(input as DeriveInput);
79    let type_ident = input.ident;
80    match input.data {
81        Data::Struct(ref data) => {
82            let field_idents = extract_idents_and_types_from_data_struct(data);
83            let is_empty_iter = field_idents.iter().map(|(ident, type_name)| {
84                quote! {
85                    <#type_name as Emptiable>::is_empty(&self.#ident)
86                }
87            });
88            let empty_iter = field_idents.iter().map(|(ident, type_name)| {
89                quote! {
90                    #ident: <#type_name as Emptiable>::empty(),
91                }
92            });
93            quote! {
94                impl Emptiable for #type_ident {
95                    fn empty() -> Self {
96                        Self {
97                            #(#empty_iter)*
98                        }
99                    }
100                    fn is_empty(&self) -> bool {
101                        #(#is_empty_iter)&&*
102                    }
103                }
104            }
105            .into()
106        }
107        _ => panic!("struct or expected, but got other type."),
108    }
109}
110
111#[proc_macro_derive(Mergeable)]
112pub fn mergeable(input: TokenStream) -> TokenStream {
113    let input = parse_macro_input!(input as DeriveInput);
114    let type_ident = input.ident;
115    match input.data {
116        Data::Struct(ref data) => {
117            let field_idents = extract_idents_and_types_from_data_struct(data);
118            let merge_iter = field_idents.iter().map(|(ident, type_name)| {
119                quote! {
120                    <#type_name as Mergeable>::merge(&mut self.#ident, other.#ident);
121                }
122            });
123            quote! {
124                impl Mergeable for #type_ident {
125                    fn merge(&mut self, mut other: Self){
126                        #(#merge_iter)*
127                    }
128                }
129            }
130            .into()
131        }
132        _ => panic!("struct expected, but got other type."),
133    }
134}
135
136#[proc_macro_derive(Runnable, attributes(runnable))]
137pub fn runnable(input: TokenStream) -> TokenStream {
138    let input = parse_macro_input!(input as DeriveInput);
139    let type_ident = input.ident;
140    match input.data {
141        Data::Struct(ref data) => {
142            let mut idents =
143                extract_idents_and_types_from_data_struct_with_attribute(data, "runnable");
144            let (field_ident, field_type) = unwrap_vec_or_panic(
145                idents,
146                "Runnable struct must have one field with runnable attribute",
147            );
148
149            quote! {
150                impl Runnable for #type_ident {
151                    fn run(self, app_name: &'static str) {
152                        <#field_type as Runnable>::run(self.#field_ident, app_name)
153                    }
154                }
155            }
156            .into()
157        }
158        Data::Enum(ref variants) => {
159            let quote_vec = extract_idents_and_types_from_enum_struct(&variants);
160            let quote_iter = quote_vec.iter().map(|(variant_ident, variant_type)| {
161                quote! {
162                    Self::#variant_ident(x) => <#variant_type as Runnable>::run(x, app_name),
163                }
164            });
165            quote! {
166                impl Runnable for #type_ident {
167                    fn run(self, app_name: &'static str) {
168                        match self {
169                            #(#quote_iter)*
170                        }
171                    }
172                }
173            }
174            .into()
175        }
176        _ => panic!("struct or enum expected, but got union."),
177    }
178}