rtti_derive/
lib.rs

1#![recursion_limit="256"]
2
3/*!
4 * Procedural macro to derive RTTI trait.
5 *
6 * See crate [`rtti`](https://crates.io/crates/rtti) for [documentation](https://docs.rs/rtti/).
7 */
8
9extern crate proc_macro;
10extern crate proc_macro2;
11extern crate syn;
12#[macro_use]
13extern crate quote;
14use proc_macro::TokenStream;
15use proc_macro2::Span;
16use syn::Meta::{List, NameValue, Word};
17use syn::NestedMeta::Meta;
18
19#[proc_macro_derive(RTTI, attributes(rtti))]
20pub fn macro_rtti(input: TokenStream) -> TokenStream {
21    let ast: syn::DeriveInput = syn::parse(input).unwrap();
22    let gen = impl_rtti(&ast);
23    gen.into()
24}
25
26fn impl_rtti(ast: &syn::DeriveInput) -> quote::Tokens {
27
28    let ident = ast.ident;
29    let name = ast.ident.to_string();
30    let visibility = translate_visibility(&ast.vis);
31    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
32    let dummy_const = syn::Ident::new(&format!("_IMPL_RTTI_FOR_{}", ident), Span::def_site());
33    let dummy_type = dummy_type();
34
35    let body = if let syn::Data::Struct(ref data) = ast.data {
36        if let syn::Fields::Named(ref fields) = data.fields {
37
38            // handle structs with named members
39
40            let idents: Vec<_> = fields.named.iter().map(|field| field.ident.unwrap()).collect();
41            let names: Vec<_> = idents.iter().map(|ident| ident.to_string()).collect();
42            let visibilities: Vec<_> = fields.named.iter().map(|field| translate_visibility(&field.vis)).collect();
43
44            let types: Vec<_> = fields.named.iter().map(|field| {
45                if parse_attr_ignore(&field.attrs) {
46                    &dummy_type
47                } else {
48                    &field.ty
49                }
50            }).collect();
51
52            let hints: Vec<_> = fields.named.iter().map(|field| {
53                parse_attr_hint(&field.attrs)
54            }).collect();
55
56            quote! {
57                Type::Struct(Struct {
58                    name: #name,
59                    vis: Visibility::#visibility,
60                    size: ::std::mem::size_of::<#ident #impl_generics>(),
61                    fields: {
62                        let mut fields = Vec::new();
63                        let dummy: #ident #impl_generics = unsafe { ::std::mem::uninitialized() };
64                        #(
65                            fields.push((#names, Field {
66                                vis: Visibility::#visibilities,
67                                offset: {
68                                    let dummy_ref = &dummy;
69                                    let field_ref = &dummy.#idents;
70                                    (field_ref as *const _ as usize) - (dummy_ref as *const _ as usize)
71                                },
72                                ty: <#types>::ctti(),
73                                hints: {
74                                    let mut hints = Vec::new();
75                                    #(
76                                        hints.push(#hints);
77                                    )*
78                                    hints
79                                }
80                            }));
81                        )*
82                        std::mem::forget(dummy);
83                        fields
84                    }
85                })
86            }
87
88        } else if let syn::Fields::Unnamed(ref fields) = data.fields {
89
90            // handle structs with unnamed members
91
92            let visibilities: Vec<_> = fields.unnamed.iter().map(|field| translate_visibility(&field.vis)).collect();
93            let indices: Vec<_> = (0..visibilities.len()).map(|x| syn::Index::from(x)).collect();
94
95            let types: Vec<_> = fields.unnamed.iter().map(|field| {
96                if parse_attr_ignore(&field.attrs) {
97                    &dummy_type
98                } else {
99                    &field.ty
100                }
101            }).collect();
102
103            let hints: Vec<_> = fields.unnamed.iter().map(|field| {
104                parse_attr_hint(&field.attrs)
105            }).collect();
106
107            quote! {
108                Type::Tuple(Tuple {
109                    name: #name,
110                    vis: Visibility::#visibility,
111                    size: ::std::mem::size_of::<#ident #impl_generics>(),
112                    fields: {
113                        let mut fields = Vec::new();
114                        let dummy: #ident #impl_generics = unsafe { ::std::mem::uninitialized() };
115                        #(
116                            fields.push(Field {
117                                vis: Visibility::#visibilities,
118                                offset: {
119                                    let dummy_ref = &dummy;
120                                    let field_ref = &(dummy.#indices);
121                                    (field_ref as *const _ as usize) - (dummy_ref as *const _ as usize)
122                                },
123                                ty: <#types>::ctti(),
124                                hints: {
125                                    let mut hints = Vec::new();
126                                    #(
127                                        hints.push(#hints);
128                                    )*
129                                    hints
130                                }
131                            });
132                        )*
133                        std::mem::forget(dummy);
134                        fields
135                    }
136                })
137            }
138        } else {
139            panic!("#[derive(RTTI)] NYI unit struct.");
140        }
141    } else if let syn::Data::Enum(ref data) = ast.data {
142
143        let variants = &data.variants;
144        let idents: Vec<_> = variants.iter().map(|variant| variant.ident).collect();
145        let names: Vec<_> = idents.iter().map(|ident| ident.to_string()).collect();
146
147        let variant_hints: Vec<_> = variants.iter().map(|variant| {
148            parse_attr_hint(&variant.attrs)
149        }).collect();
150
151        let field_types: Vec<Vec<_>> = variants.iter().map(|variant| {
152            variant.fields.iter().map(|field| {
153                if parse_attr_ignore(&field.attrs) {
154                    &dummy_type
155                } else {
156                    &field.ty
157                }
158            }).collect()
159        }).collect();
160
161        let field_hints: Vec<Vec<_>> = variants.iter().map(|variant| {
162            variant.fields.iter().map(|field| parse_attr_hint(&field.attrs)).collect()
163        }).collect();
164
165        quote! {
166            Type::Enum(Enum {
167                name: #name,
168                vis: Visibility::#visibility,
169                size: ::std::mem::size_of::<#ident #impl_generics>(),
170                variants: {
171                    let mut variants = Vec::new();
172                    //let dummy: #ident #impl_generics = unsafe { ::std::mem::uninitialized() };
173                    #(
174                        variants.push((#names, Variant {
175                            fields: {
176                                let mut fields = Vec::new();
177                                #(
178                                    fields.push(Field {
179                                        vis: Visibility::Public,
180                                        offset: 0, /*{
181                                            let dummy_ref = &dummy;
182                                            let field_ref = &dummy.#idents;
183                                            (field_ref as *const _ as usize) - (dummy_ref as *const _ as usize)
184                                        },*/
185                                        ty: <#field_types>::ctti(),
186                                        hints: {
187                                            let mut hints = Vec::new();
188                                            #(
189                                                hints.push(#field_hints);
190                                            )*
191                                            hints
192                                        }
193                                    });
194                                )*
195                                fields
196                            },
197                            hints: {
198                                let mut hints = Vec::new();
199                                #(
200                                    hints.push(#variant_hints);
201                                )*
202                                hints
203                            }
204                        }));
205                    )*
206                    //std::mem::forget(dummy);
207                    variants
208                }
209            })
210        }
211    } else {
212        panic!("#[derive(RTTI)] NYI union");
213    };
214
215    quote! {
216        #[allow(non_upper_case_globals,unused_mut)]
217        const #dummy_const: () = {
218            extern crate rtti;
219            use rtti::*;
220            impl #impl_generics RTTI for #ident #ty_generics #where_clause  {
221                fn ctti() -> Type {
222                    #body
223                }
224            }
225        };
226    }
227}
228
229fn translate_visibility(vis: &syn::Visibility) -> syn::Ident {
230    #[allow(unreachable_patterns)]
231    match vis {
232        &syn::Visibility::Public(_) => syn::Ident::from("Public"),
233        &syn::Visibility::Crate(_) => syn::Ident::from("Crate"),
234        &syn::Visibility::Restricted(_) => syn::Ident::from("Restricted"),
235        &syn::Visibility::Inherited => syn::Ident::from("Inherited"),
236        _ => syn::Ident::from("Unknown"),
237    }
238}
239
240fn parse_attr_hint(attrs: &Vec<syn::Attribute>) -> Vec<String> {
241    let mut hints = Vec::new();
242    for meta_items in attrs.iter().filter_map(filter_attr_rtti) {
243        for meta_item in meta_items {
244            match meta_item {
245                // Parse `#[rtti(hint = "foo")]`
246                Meta(NameValue(ref m)) if m.ident == "hint" => {
247                    if let Some(s) = parse_lit(&m.lit) {
248                        hints.push(s.value().to_string());
249                    }
250                },
251                _ => {}
252            }
253        }
254    }
255    hints
256}
257
258fn parse_attr_ignore(attrs: &Vec<syn::Attribute>) -> bool {
259    for meta_items in attrs.iter().filter_map(filter_attr_rtti) {
260        for meta_item in meta_items {
261            match meta_item {
262                // Parse `#[rtti(ignore)]`
263                Meta(Word(ref ident)) if ident == "ignore" => {
264                    return true;
265                },
266                _ => {}
267            }
268        }
269    }
270    false
271}
272
273fn parse_lit(lit: &syn::Lit) -> Option<&syn::LitStr> {
274    if let syn::Lit::Str(ref lit) = *lit {
275        Some(lit)
276    } else {
277        None
278    }
279}
280
281fn filter_attr_rtti(attr: &syn::Attribute) -> Option<Vec<syn::NestedMeta>> {
282    if attr.path.segments.len() == 1 && attr.path.segments[0].ident == "rtti" {
283        match attr.interpret_meta() {
284            Some(List(ref meta)) => Some(meta.nested.iter().cloned().collect()),
285            _ => {
286                // TODO: produce an error
287                None
288            }
289        }
290    } else {
291        None
292    }
293}
294
295fn dummy_type() -> &'static syn::Type {
296    // TODO: pull in lazy_static instead?
297    static mut DUMMY: Option<syn::Type> = None;
298    unsafe {
299        if DUMMY.is_none() {
300            DUMMY = Some(syn::Type::Path(syn::TypePath {
301                qself: None,
302                path: {
303                    // there must be a shorter way than this.
304                    let p1: syn::PathSegment = "rtti".into();
305                    let p2: syn::PathSegment = "Ignored".into();
306                    syn::Path {
307                        leading_colon: None,
308                        segments: {
309                            let mut punc = syn::punctuated::Punctuated::new();
310                            punc.push(p1);
311                            punc.push(p2);
312                            punc // rain dance ceremony completed
313                        }
314                    }
315                }
316            }));
317        }
318        DUMMY.as_ref().unwrap()
319    }
320}