ast2str_derive/
lib.rs

1//! A proc macro for pretty-printing ASTs (and nested structures in general).
2#![feature(proc_macro_diagnostic)]
3extern crate syn;
4#[macro_use]
5extern crate quote;
6
7mod gen;
8
9use proc_macro::TokenStream;
10use proc_macro2::TokenStream as TokenStream2;
11use quote::ToTokens;
12use syn::{spanned::Spanned, Ident, ItemEnum, ItemStruct};
13
14/// Automatically implements the [`AstToStr`] trait for the given struct or enum.
15/// Every field of the given item must implement [`AstToStr`] or be annotated with one of the
16/// the attributes.
17#[proc_macro_derive(
18    AstToStr,
19    attributes(
20        skip, skip_if, forward, debug, display, quoted, callback, default, list, rename, delegate
21    )
22)]
23pub fn derive_ast2str(input: TokenStream) -> TokenStream {
24    let item: syn::Item =
25        syn::parse(input).expect("This macro can only be used with structs and enums");
26
27    let output = match item {
28        syn::Item::Struct(i) => generate_struct_impl(i),
29        syn::Item::Enum(i) => generate_enum_impl(i),
30        _ => {
31            item.span()
32                .unwrap()
33                .error("This macro can only be used with structs and enums");
34            return item.into_token_stream().into();
35        }
36    };
37
38    match output {
39        Ok(ok) => ok.into(),
40        Err(e) => e.into_compile_error().into(),
41    }
42}
43
44/// Generates an [`AstToStr`] impl for the given struct.
45fn generate_struct_impl(i: ItemStruct) -> Result<TokenStream2, syn::Error> {
46    let name = i.ident;
47    let generics = i.generics;
48
49    let fields = match gen::generate_builder_methods(&i.fields, false)? {
50        gen::FieldsToBuild::Fields(fields) => fields,
51        gen::FieldsToBuild::Forward(fwd) => {
52            return Ok(generate_impl_for_stream(
53                &name,
54                &generics,
55                quote! {
56                    fn ast_to_str_impl(&self, __symbols: &dyn ::ast2str::ast2str_lib::Symbols) -> String {
57                        self.#fwd
58                    }
59                },
60            ));
61        }
62    };
63    let rename_as = gen::extract_rename_ident(&i.attrs).unwrap_or_else(|| name.to_string());
64
65    Ok(generate_impl_for_stream(
66        &name,
67        &generics,
68        quote! {
69            #[allow(unused_parens)]
70            #[allow(unused_variables)]
71            fn ast_to_str_impl(&self, __symbols: &dyn ::ast2str::ast2str_lib::Symbols) -> String {
72                use ::ast2str::ast2str_lib::TreeBuilder;
73                let mut builder = TreeBuilder::new(#rename_as, __symbols);
74
75                #(builder = #fields;)*
76
77                builder.build()
78            }
79        },
80    ))
81}
82
83/// Generates an [`AstToStr`] impl for the given enum.
84fn generate_enum_impl(e: ItemEnum) -> Result<TokenStream2, syn::Error> {
85    let enum_name = e.ident;
86    let rename_as = gen::extract_rename_ident(&e.attrs).unwrap_or_else(|| enum_name.to_string());
87
88    let mut arms = Vec::with_capacity(e.variants.len());
89    for var in &e.variants {
90        let name = &var.ident;
91        let fields = gen::generate_builder_methods(&var.fields, true)?;
92        let body = match fields {
93            gen::FieldsToBuild::Fields(fields) => {
94                quote! {{
95                    let mut builder = TreeBuilder::new(concat!(#rename_as, "::", stringify!(#name)), __symbols);
96
97                    #(builder = #fields;)*
98
99                    builder.build()
100                }}
101            }
102            gen::FieldsToBuild::Forward(fwd) => fwd,
103        };
104        let pattern = {
105            let mut field_bindings = vec![];
106            let mut index = 0;
107
108            for f in &var.fields {
109                match &f.ident {
110                    Some(name) => field_bindings.push(quote! { #name }),
111                    None => {
112                        let name = Ident::new(&format!("operand{}", index), f.span());
113                        let syn_index = syn::Index::from(index);
114                        field_bindings.push(quote! { #syn_index: #name });
115                        index += 1;
116                    }
117                }
118            }
119
120            quote! {
121                #enum_name::#name { #(#field_bindings),* }
122            }
123        };
124        arms.push(quote! {
125            #pattern => #body
126        });
127    }
128
129    let impl_body = if !arms.is_empty() {
130        quote! {
131            match &self {
132                #(#arms),*
133            }
134        }
135    } else {
136        quote! {
137            String::from(stringify!(#enum_name))
138        }
139    };
140
141    Ok(generate_impl_for_stream(
142        &enum_name,
143        &e.generics,
144        quote! {
145            #[allow(unused_parens)]
146            #[allow(unused_variables)]
147            fn ast_to_str_impl(&self, __symbols: &dyn ::ast2str::ast2str_lib::Symbols) -> String {
148                use ::ast2str::ast2str_lib::TreeBuilder;
149                #impl_body
150            }
151        },
152    ))
153}
154
155fn generate_impl_for_stream(
156    name: &syn::Ident,
157    generics: &syn::Generics,
158    body: TokenStream2,
159) -> TokenStream2 {
160    let parametrized_name = generics.parameterized_name(name);
161    let original_generics = generics.provided_generics_without_defaults();
162    let where_clauses = generics.provided_where_clauses();
163
164    quote! {
165        impl <#(#original_generics),*> ::ast2str::ast2str_lib::AstToStr for #parametrized_name
166        where
167            #(#where_clauses),*
168        {
169            #body
170        }
171    }
172}
173
174/// The following trait was adapted from snafu (https://github.com/shepmaster/snafu/blob/main/snafu-derive/src/lib.rs#L1126) by Jake Goulding.
175trait GenericAwareness {
176    fn generics(&self) -> &syn::Generics;
177
178    fn parameterized_name(&self, name: &syn::Ident) -> TokenStream2 {
179        let original_generics = self.provided_generic_names();
180
181        quote! { #name<#(#original_generics,)*> }
182    }
183
184    fn provided_generic_types_without_defaults(&self) -> Vec<proc_macro2::TokenStream> {
185        use syn::TypeParam;
186        self.generics()
187            .type_params()
188            .map(|t: &TypeParam| {
189                let TypeParam {
190                    attrs,
191                    ident,
192                    colon_token,
193                    bounds,
194                    ..
195                } = t;
196                quote! {
197                    #(#attrs)*
198                    #ident
199                    #colon_token
200                    #bounds
201                }
202            })
203            .collect()
204    }
205
206    fn provided_generics_without_defaults(&self) -> Vec<proc_macro2::TokenStream> {
207        self.provided_generic_lifetimes()
208            .into_iter()
209            .chain(self.provided_generic_types_without_defaults().into_iter())
210            .collect()
211    }
212
213    fn provided_generic_lifetimes(&self) -> Vec<proc_macro2::TokenStream> {
214        use syn::{GenericParam, LifetimeDef};
215
216        self.generics()
217            .params
218            .iter()
219            .flat_map(|p| match p {
220                GenericParam::Lifetime(LifetimeDef { lifetime, .. }) => Some(quote! { #lifetime }),
221                _ => None,
222            })
223            .collect()
224    }
225
226    fn provided_generic_names(&self) -> Vec<proc_macro2::TokenStream> {
227        use syn::{ConstParam, GenericParam, LifetimeDef, TypeParam};
228
229        self.generics()
230            .params
231            .iter()
232            .map(|p| match p {
233                GenericParam::Type(TypeParam { ident, .. }) => quote! { #ident },
234                GenericParam::Lifetime(LifetimeDef { lifetime, .. }) => quote! { #lifetime },
235                GenericParam::Const(ConstParam { ident, .. }) => quote! { #ident },
236            })
237            .collect()
238    }
239
240    fn provided_where_clauses(&self) -> Vec<proc_macro2::TokenStream> {
241        self.generics()
242            .where_clause
243            .iter()
244            .flat_map(|c| c.predicates.iter().map(|p| quote! { #p }))
245            .collect()
246    }
247}
248
249impl GenericAwareness for syn::Generics {
250    fn generics(&self) -> &syn::Generics {
251        self
252    }
253}