Skip to main content

pathfinder_tagged_debug_derive/
lib.rs

1use std::convert::From;
2
3use proc_macro2::Ident;
4use quote::{format_ident, quote, quote_spanned};
5use syn::spanned::Spanned;
6use syn::{parse_macro_input, Data, DeriveInput};
7
8#[proc_macro_derive(TaggedDebug)]
9pub fn derive_tagged_debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
10    let input = parse_macro_input!(input as DeriveInput);
11
12    derive_tagged_debug_impl(input)
13        .unwrap_or_else(|e| e.to_compile_error())
14        .into()
15}
16
17fn derive_tagged_debug_impl(input: DeriveInput) -> Result<proc_macro2::TokenStream, syn::Error> {
18    let name = input.ident;
19    let fmt_body = iterate_tagged_debug(&name, &input.data)?;
20
21    Ok(quote! {
22        impl std::fmt::Debug for #name {
23            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
24                #fmt_body
25            }
26        }
27    })
28}
29
30fn iterate_tagged_debug(name: &Ident, data: &Data) -> Result<proc_macro2::TokenStream, syn::Error> {
31    match *data {
32        Data::Struct(ref data) => Ok(iterate_struct_fields(name, &data.fields)),
33        Data::Enum(ref e) => Ok(iterate_enum_variants(name, &e.variants)),
34        Data::Union(ref u) => Err(syn::Error::new(
35            u.union_token.span(),
36            "union is not supported",
37        )),
38    }
39}
40
41fn iterate_struct_fields(struct_name: &Ident, fields: &syn::Fields) -> proc_macro2::TokenStream {
42    match fields {
43        syn::Fields::Named(ref fields) => {
44            let fields = fields.named.iter().map(|f| {
45                let name = &f.ident;
46                quote_spanned! {f.span()=>
47                    .field(stringify!(#name), &self.#name)
48                }
49            });
50            let fields_clone = fields.clone();
51            quote! {
52                match Tagged::<#struct_name>::tag(self) {
53                    Ok(tag) => {
54                        f.debug_struct(stringify!(#struct_name))
55                            .field("TAG", &tag)
56                            #(#fields)*
57                            .finish()
58                    }
59                    Err(_) => {
60                        f.debug_struct(stringify!(#struct_name))
61                            #(#fields_clone)*
62                            .finish()
63                    }
64                }
65            }
66        }
67        syn::Fields::Unit => quote! {
68            match Tagged::<#struct_name>::tag(self) {
69                Ok(tag) => {
70                    f.debug_struct(stringify!(#struct_name))
71                        .field("TAG", &tag)
72                        .finish()
73                }
74                Err(_) => {
75                    f.debug_struct(stringify!(#struct_name))
76                        .finish()
77                }
78            }
79        },
80        syn::Fields::Unnamed(ref fields) => {
81            let fields = fields.unnamed.iter().enumerate().map(|(i, f)| {
82                let i = syn::Index::from(i);
83                quote_spanned! {f.span()=>
84                    .field(&self.#i)
85                }
86            });
87            let fields_clone = fields.clone();
88            quote! {
89                match Tagged::<#struct_name>::tag(self) {
90                    Ok(tag) => {
91                        f.debug_tuple(stringify!(#struct_name))
92                            .field(&format!("TAG: {}", &tag))
93                            #(#fields)*
94                            .finish()
95                    }
96                    Err(_) => {
97                        f.debug_tuple(stringify!(#struct_name))
98                            #(#fields_clone)*
99                            .finish()
100                    }
101                }
102            }
103        }
104    }
105}
106
107fn iterate_enum_variants(
108    enum_name: &Ident,
109    variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
110) -> proc_macro2::TokenStream {
111    let variant_impls = variants.iter().map(|v| {
112        let variant_name = &v.ident;
113
114        match v.fields {
115            syn::Fields::Named(ref fields) => {
116                let fields_pattern = fields.named.iter().map(|f| {
117                    let name = &f.ident;
118                    quote_spanned! {f.span()=>
119                        #name,
120                    }
121                });
122
123                let fields_impl = fields.named.iter().map(|f| {
124                    let name = &f.ident;
125                    quote_spanned! {f.span()=>
126                        .field(stringify!(#name), &#name)
127                    }
128                });
129                let fields_impl_clone = fields_impl.clone();
130
131                quote! {
132                    #enum_name::#variant_name {
133                        #(#fields_pattern)*
134                    } => {
135                    match Tagged::<#enum_name>::tag(self) {
136                        Ok(tag) => {
137                            f.debug_struct(stringify!(#variant_name))
138                            .field("TAG", &tag)
139                            #(#fields_impl)*
140                            .finish()
141                        }
142                        Err(_) => {
143                            f.debug_struct(stringify!(#variant_name))
144                            #(#fields_impl_clone)*
145                            .finish()
146                        }
147                    }}
148                }
149            }
150            syn::Fields::Unit => quote! {
151                #enum_name::#variant_name => {
152                match Tagged::<#enum_name>::tag(self) {
153                    Ok(tag) => {
154                        f.debug_struct(stringify!(#variant_name))
155                        .field("TAG", &tag)
156                        .finish()
157                    }
158                    Err(_) => {
159                        f.debug_struct(stringify!(#variant_name))
160                        .finish()
161                    }
162                }}
163            },
164            syn::Fields::Unnamed(ref fields) => {
165                let fields_pattern = fields.unnamed.iter().enumerate().map(|(i, f)| {
166                    let i = syn::Index::from(i);
167                    let field_name = format_ident!("field_{}", i);
168
169                    quote_spanned! {f.span()=>
170                        #field_name,
171                    }
172                });
173
174                let fields_impl = fields.unnamed.iter().enumerate().map(|(i, f)| {
175                    let i = syn::Index::from(i);
176                    let field_name = format_ident!("field_{}", i);
177
178                    quote_spanned! {f.span()=>
179                        .field(&#field_name)
180                    }
181                });
182                let fields_impl_clone = fields_impl.clone();
183
184                quote! {
185                    #enum_name::#variant_name (
186                        #(#fields_pattern)*
187                    ) => {
188                    match Tagged::<#enum_name>::tag(self) {
189                        Ok(tag) => {
190                            f.debug_tuple(stringify!(#variant_name))
191                            .field(&format!("TAG: {}", &tag))
192                            #(#fields_impl)*
193                            .finish()
194                        }
195                        Err(_) => {
196                            f.debug_tuple(stringify!(#variant_name))
197                            #(#fields_impl_clone)*
198                            .finish()
199                        }
200                    }}
201                }
202            }
203        }
204    });
205    quote! {
206        match self {
207            #(#variant_impls)*
208        }
209    }
210}