Skip to main content

const_gen_derive/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::quote;
4
5const DOC_ATTR: &str = "inherit_doc";
6const INNER_DOC_ATTR: &str = "inherit_docs";
7
8/// Derives the CompileConst trait for structs and enums. Requires that all
9/// fields also implement the CompileConst trait.
10#[proc_macro_derive(CompileConst, attributes(inherit_doc, inherit_docs))]
11pub fn const_gen_derive(input: TokenStream) -> TokenStream {
12    impl_macro(&syn::parse(input).unwrap())
13}
14
15fn impl_macro(ast: &syn::DeriveInput) -> TokenStream {
16    let name = &ast.ident;
17    let generics = &ast.generics;
18    let val_impl: proc_macro2::TokenStream = match &ast.data {
19        syn::Data::Struct(data) => struct_val_handler(name, &data.fields),
20        syn::Data::Enum(data) => {
21            let arms: Vec<proc_macro2::TokenStream> = data
22                .variants
23                .iter()
24                .map(|v| enum_val_handler(name, &v.ident, &v.fields))
25                .collect();
26            quote! {
27                format!("{}", match self
28                {
29                    #( #arms, )*
30                })
31            }
32        }
33        syn::Data::Union(data) => {
34            let vis = get_field_visibilities(&data.fields.named)
35                .into_iter()
36                .next()
37                .unwrap();
38            let ident = get_field_idents(&data.fields.named)
39                .into_iter()
40                .next()
41                .unwrap();
42            quote! {
43                format!
44                (
45                    "{} {} {{ {}: {}}}",
46                    stringify!(#vis),
47                    stringify!(#name),
48                    stringify!(#ident),
49                    self.#ident.const_val()
50                )
51            }
52        }
53    };
54    let (doc_attr, inner_doc_attr) = get_docs(&ast.attrs);
55    let def_impl: proc_macro2::TokenStream = match &ast.data {
56        syn::Data::Struct(data) => struct_def_handler(name, generics, &data.fields, inner_doc_attr),
57        syn::Data::Enum(data) => enum_def_handler(
58            name,
59            generics,
60            data.variants.iter().collect(),
61            inner_doc_attr,
62        ),
63        syn::Data::Union(data) => {
64            let docs = get_field_docs(&data.fields.named, inner_doc_attr);
65            let vis = get_field_visibilities(&data.fields.named);
66            let idents = get_field_idents(&data.fields.named);
67            let types = get_field_types(&data.fields.named);
68            quote! {
69                let mut f = String::new();
70                #( f.push_str(&format!("{} {} {}: {}, ", stringify!(#docs), stringify!(#vis), stringify!(#idents), <#types>::const_type())); )*
71                format!
72                (
73                    "union {}{}{{ {}}}",
74                    stringify!(#name),
75                    stringify!(#generics),
76                    f
77                )
78            }
79        }
80    };
81    let doc_attr = doc_attr.map_or(String::new(), |attr| quote!(#attr).to_string());
82    let gen = quote! {
83        impl const_gen::CompileConst for #name #generics
84        {
85            fn const_type() -> String
86            {
87                String::from(stringify!(#name))
88            }
89
90            fn const_val(&self) -> String
91            {
92                #val_impl
93            }
94
95            fn const_definition(attrs: &str, vis: &str) -> String
96            {
97                let mut definition = String::from(attrs);
98                definition += #doc_attr;
99                definition += " ";
100                definition += vis;
101                definition += " ";
102                definition += &{#def_impl};
103                definition
104            }
105        }
106    };
107    gen.into()
108}
109
110/// Generate a struct definition
111fn struct_def_handler(
112    name: &syn::Ident,
113    generics: &syn::Generics,
114    fields: &syn::Fields,
115    inner_doc_attr: bool,
116) -> proc_macro2::TokenStream {
117    match fields {
118        syn::Fields::Named(f) => {
119            let vis = get_field_visibilities(&f.named);
120            let idents = get_field_idents(&f.named);
121            let types = get_field_types(&f.named);
122            let docs = get_field_docs(&f.named, inner_doc_attr);
123            quote! {
124                let mut f = String::new();
125                #( f.push_str(&format!("{} {} {}: {}, ", stringify!(#docs), stringify!(#vis), stringify!(#idents), <#types>::const_type())); )*
126                format!
127                (
128                    "struct {}{}{{ {}}}",
129                    stringify!(#name),
130                    stringify!(#generics),
131                    f
132                )
133            }
134        }
135        syn::Fields::Unnamed(f) => {
136            let types = get_field_types(&f.unnamed);
137            quote! {
138                let mut f = String::new();
139                #( f.push_str(&format!("{},", <#types>::const_type())); )*
140                format!
141                (
142                    "struct {}{}({});",
143                    stringify!(#name),
144                    stringify!(#generics),
145                    f
146                )
147            }
148        }
149        syn::Fields::Unit => quote!(format!(
150            "struct {}{};",
151            stringify!(#name),
152            stringify!(#generics)
153        )),
154    }
155}
156
157/// Generate a struct constructor
158fn struct_val_handler(name: &syn::Ident, fields: &syn::Fields) -> proc_macro2::TokenStream {
159    match fields {
160        syn::Fields::Named(f) => {
161            let idents = get_field_idents(&f.named);
162            quote! {
163                let mut f = String::new();
164                #( f.push_str(&format!("{}: {}, ", stringify!(#idents), self.#idents.const_val())); )*
165                format!
166                (
167                    "{} {{ {}}}",
168                    stringify!(#name),
169                    f
170                )
171            }
172        }
173        syn::Fields::Unnamed(f) => {
174            let mut counter = 0;
175            let vals: Vec<_> = f
176                .unnamed
177                .iter()
178                .map(|_| {
179                    let next = counter;
180                    counter += 1;
181                    next
182                })
183                .map(syn::Index::from)
184                .collect();
185            quote! {
186                let mut f = String::new();
187                #( f.push_str(&format!("{},", self.#vals.const_val())); )*
188                format!
189                (
190                    "{}({})",
191                    stringify!(#name),
192                    f
193                )
194            }
195        }
196        syn::Fields::Unit => quote!(stringify!(#name)),
197    }
198}
199
200/// Generate an enum constructor
201fn enum_val_handler(
202    name: &syn::Ident,
203    var_name: &syn::Ident,
204    fields: &syn::Fields,
205) -> proc_macro2::TokenStream {
206    let constructor = match fields {
207        syn::Fields::Named(f) => {
208            let idents = get_field_idents(&f.named);
209            quote! {{
210                let mut f = String::new();
211                #( f.push_str(&format!("{}:{},", stringify!(#idents), #idents.const_val())); )*
212                format!
213                (
214                    "{}::{}{{{}}}",
215                    stringify!(#name),
216                    stringify!(#var_name),
217                    f
218                )
219            }}
220        }
221        syn::Fields::Unnamed(f) => {
222            let mut counter = 0;
223            let idents: Vec<syn::Ident> = f
224                .unnamed
225                .iter()
226                .map(|_| {
227                    let new_ident = syn::Ident::new(&format!("idnt{}", counter), Span::call_site());
228                    counter += 1;
229                    new_ident
230                })
231                .collect();
232            quote! {{
233                let mut f = String::new();
234                #( f.push_str(&format!("{},", #idents.const_val())); )*
235                format!
236                (
237                    "{}::{}({})",
238                    stringify!(#name),
239                    stringify!(#var_name),
240                    f
241                )
242            }}
243        }
244        syn::Fields::Unit => quote!(format!("{}::{}", stringify!(#name), stringify!(#var_name))),
245    };
246    match fields {
247        syn::Fields::Named(f) => {
248            let new_idents = get_field_idents(&f.named);
249            quote!(Self::#var_name {#(#new_idents, )*} => #constructor)
250        }
251        syn::Fields::Unnamed(f) => {
252            let mut counter = 0;
253            let new_idents: Vec<syn::Ident> = f
254                .unnamed
255                .iter()
256                .map(|_| {
257                    let new_ident = syn::Ident::new(&format!("idnt{}", counter), Span::call_site());
258                    counter += 1;
259                    new_ident
260                })
261                .collect();
262            quote!(Self::#var_name (#(#new_idents, )*) => #constructor)
263        }
264        syn::Fields::Unit => quote!(Self::#var_name => #constructor),
265    }
266}
267
268/// Generate an enum definition
269fn enum_def_handler(
270    name: &syn::Ident,
271    generics: &syn::Generics,
272    variants: Vec<&syn::Variant>,
273    inner_doc_attr: bool,
274) -> proc_macro2::TokenStream {
275    let arms: Vec<proc_macro2::TokenStream> = variants
276        .into_iter()
277        .map(|v| enum_variant_def_handler(&v.attrs, &v.ident, &v.fields, inner_doc_attr))
278        .collect();
279    quote! {
280        format!("enum {}{}{{ {} }}", stringify!(#name), stringify!(#generics), #(#arms+)&* "")
281    }
282}
283
284/// Generate an enum variant definition
285fn enum_variant_def_handler(
286    attributes: &[syn::Attribute],
287    var_name: &syn::Ident,
288    fields: &syn::Fields,
289    inner_doc_attr: bool,
290) -> proc_macro2::TokenStream {
291    let doc_attr = get_inner_docs(attributes, inner_doc_attr);
292    match fields {
293        syn::Fields::Named(f) => {
294            let idents = get_field_idents(&f.named);
295            let types = get_field_types(&f.named);
296            quote! {{
297                let mut f = String::new();
298                #( f.push_str(&format!("{}:{},", stringify!(#idents), <#types>::const_type())); )*
299                format!
300                (
301                    "{} {}{{{}}},",
302                    stringify!(#doc_attr),
303                    stringify!(#var_name),
304                    f
305                )
306            }}
307        }
308        syn::Fields::Unnamed(f) => {
309            let types = get_field_types(&f.unnamed);
310            quote! {{
311                let mut f = String::new();
312                #( f.push_str(&format!("{},", <#types>::const_type())); )*
313                format!
314                (
315                    "{} {}({}),",
316                    stringify!(#doc_attr),
317                    stringify!(#var_name),
318                    f
319                )
320            }}
321        }
322        syn::Fields::Unit => quote!(format!(
323            "{} {},",
324            stringify!(#doc_attr),
325            stringify!(#var_name)
326        )),
327    }
328}
329
330/// Get visibility of named fields
331fn get_field_visibilities(
332    fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
333) -> Vec<&syn::Visibility> {
334    fields.iter().map(|field| &field.vis).collect()
335}
336
337/// Get identifiers of named fields
338fn get_field_idents(
339    fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
340) -> Vec<&Option<syn::Ident>> {
341    fields.iter().map(|field| &field.ident).collect()
342}
343
344/// Get types of fields
345fn get_field_types(
346    fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
347) -> Vec<&syn::Type> {
348    fields.iter().map(|field| &field.ty).collect()
349}
350
351/// Get docs for fields
352fn get_field_docs(
353    fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
354    inner_doc_attr: bool,
355) -> Vec<Option<syn::Attribute>> {
356    fields
357        .iter()
358        .map(|field| get_inner_docs(&field.attrs, inner_doc_attr))
359        .collect()
360}
361
362/// Parse DOC_ATTR to inherit docs
363fn get_docs(attrs: &[syn::Attribute]) -> (Option<syn::Attribute>, bool) {
364    let mut inner_doc_attr = false;
365    if attrs.iter().any(|assoc_attr| {
366        if assoc_attr.path.is_ident(INNER_DOC_ATTR) {
367            inner_doc_attr = true;
368        }
369        assoc_attr.path.is_ident(DOC_ATTR) || inner_doc_attr
370    }) {
371        (
372            attrs
373                .iter()
374                .find(|assoc_attr| assoc_attr.path.is_ident("doc"))
375                .cloned(),
376            inner_doc_attr,
377        )
378    } else {
379        (None, inner_doc_attr)
380    }
381}
382
383/// Parse INNER_DOC_ATTR to inherit docs for fields or variants
384fn get_inner_docs(attrs: &[syn::Attribute], inner_doc_attr: bool) -> Option<syn::Attribute> {
385    attrs
386        .iter()
387        .find(|assoc_attr| assoc_attr.path.is_ident("doc"))
388        .filter(|_| inner_doc_attr || attrs.iter().any(|attr| attr.path.is_ident("inherit_doc")))
389        .cloned()
390}