yade/
lib.rs

1extern crate proc_macro;
2extern crate syn;
3
4#[macro_use] extern crate synstructure;
5#[macro_use] extern crate quote;
6
7const DISPLAY_ATTR: &'static str = "display";
8const DISPLAY_MSG: &'static str = "msg";
9const CAUSE_ATTR: &'static str = "cause";
10
11decl_derive!([YadeError, attributes(display, cause)] => error_derive);
12fn error_derive(s: synstructure::Structure) -> quote::Tokens {
13    let display_impl = generate_display_impl(&s);
14    let error_impl = generate_error_impl(&s);
15
16    quote! {
17        #display_impl
18        #error_impl
19    }
20}
21
22decl_derive!([YadeKind, attributes(display)] => error_kind_derive);
23fn error_kind_derive(s: synstructure::Structure) -> quote::Tokens {
24    generate_display_impl(&s)
25}
26
27const INVALID_CAUSE: &'static str = "Cause must be a type implementing Error, a Box<Error>, or an Option<Box<Error>>";
28
29fn generate_error_impl(s: &synstructure::Structure) -> quote::Tokens {
30    let cause_matches = s.each_variant(|v| {
31        if let Some(cause) = v.bindings().iter().find(is_cause) {
32            let path = match cause.ast().ty {
33                syn::Ty::Path(_, ref path) => path,
34                _ => panic!(INVALID_CAUSE),
35            };
36            let parent_type = path.segments.last().unwrap();
37
38            match parent_type.ident.as_ref() {
39                "Option" => {
40                    let child_path = match parent_type.parameters {
41                        syn::PathParameters::AngleBracketed(ref data) => {
42                            match data.types[0] {
43                                syn::Ty::Path(_, ref path) => path,
44                                _ => panic!(INVALID_CAUSE),
45                            }
46                        },
47                        _ => panic!(INVALID_CAUSE),
48                    };
49                    let child_type = child_path.segments.last().unwrap();
50                    match child_type.ident.as_ref() {
51                        "Box" => {
52                            quote!(return #cause.as_ref().map(|c|c.as_ref()))
53                        },
54                        _ => panic!(INVALID_CAUSE),
55                    }
56
57                },
58                "Box" => quote!(return Some(#cause.as_ref())),
59                _ => quote!(return Some(#cause)),
60            }
61        } else {
62            quote!(return None)
63        }
64    });
65
66    s.bound_impl("::std::error::Error", quote! {
67        #[allow(unreachable_code)]
68        fn cause(&self) -> Option<&::std::error::Error> {
69            match *self { #cause_matches }
70            None
71        }
72
73        #[allow(unreachable_code)]
74        fn description(&self) -> &str {
75            "For a description please use the Display trait implementation of this Error"
76        }
77    })
78}
79
80fn generate_display_impl(s: &synstructure::Structure) -> quote::Tokens {
81    let display_matches = s.each_variant(|v| {
82        let ast = &v.ast();
83        let name = ast.ident.to_string();
84
85        let msg = match find_error_msg(&ast.attrs) {
86            Some(msg) => msg,
87            None => {
88                return quote! {
89                    return write!(f, "{}", #name);
90                }
91            }
92        };
93
94        if msg.is_empty() {
95            panic!("Attribute '{}' must contain `{} = \"\"`", DISPLAY_ATTR, DISPLAY_MSG);
96        }
97
98        let s = match msg[0] {
99            syn::NestedMetaItem::MetaItem(syn::MetaItem::NameValue(ref i, ref lit)) if i == DISPLAY_MSG => {
100                lit.clone()
101            }
102            _ => panic!("Attribute '{}' must contain `{} = \"\"`", DISPLAY_ATTR, DISPLAY_MSG),
103        };
104        use quote::ToTokens;
105        let args = msg[1..].iter().map(|arg| match *arg {
106            syn::NestedMetaItem::Literal(syn::Lit::Int(i, _)) => {
107                let bi = &v.bindings()[i as usize];
108                quote!(#bi)
109            }
110            syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ref id)) => {
111                if id.as_ref().starts_with("_") {
112                    if let Ok(idx) = id.as_ref()[1..].parse::<usize>() {
113                        let bi = &v.bindings()[idx];
114                        return quote!(#bi)
115                    }
116                }
117                for bi in v.bindings() {
118                    if bi.ast().ident.as_ref() == Some(id) {
119                        return quote!(#bi);
120                    }
121                }
122                panic!("Couldn't find field '{}' of '{}'", id, name);
123            }
124            _ => {
125                let mut tokens = quote::Tokens::new();
126                arg.to_tokens(&mut tokens);
127                panic!("Invalid '{}' attribute argument `{}` for '{}'", DISPLAY_ATTR, tokens, name);
128            },
129        });
130
131        quote! {
132            return write!(f, #s #(, #args)*)
133        }
134    });
135
136    s.bound_impl("::std::fmt::Display", quote! {
137        #[allow(unreachable_code)]
138        fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
139            match *self { #display_matches }
140            write!(f, "There was an unknown error.")
141        }
142    })
143}
144
145fn find_error_msg(attrs: &[syn::Attribute]) -> Option<&[syn::NestedMetaItem]> {
146    let mut error_msg = None;
147
148    for attr in attrs {
149        if attr.name() == DISPLAY_ATTR {
150            if error_msg.is_some() {
151                panic!("Attribute '{}' specified multiple times.", DISPLAY_ATTR)
152            } else {
153                if let syn::MetaItem::List(_, ref list)  = attr.value {
154                    error_msg = Some(&list[..]);
155                } else {
156                    panic!("Attribute '{}' requires parens", DISPLAY_ATTR)
157                }
158            }
159        }
160    }
161
162    error_msg
163}
164
165fn is_cause(bi: &&synstructure::BindingInfo) -> bool {
166    bi.ast().attrs.iter().any(|attr| attr.name() == CAUSE_ATTR)
167}