failure_derive/
lib.rs

1extern crate proc_macro2;
2extern crate syn;
3
4#[macro_use]
5extern crate synstructure;
6#[macro_use]
7extern crate quote;
8
9use proc_macro2::{TokenStream, Span};
10use syn::LitStr;
11use syn::spanned::Spanned;
12
13#[derive(Debug)]
14struct Error(TokenStream);
15
16impl Error {
17    fn new(span: Span, message: &str) -> Error {
18        Error(quote_spanned! { span =>
19            compile_error!(#message);
20        })
21    }
22
23    fn into_tokens(self) -> TokenStream {
24        self.0
25    }
26}
27
28impl From<syn::Error> for Error {
29    fn from(e: syn::Error) -> Error {
30        Error(e.to_compile_error())
31    }
32}
33
34decl_derive!([Fail, attributes(fail, cause)] => fail_derive);
35
36fn fail_derive(s: synstructure::Structure) -> TokenStream {
37    match fail_derive_impl(s) {
38        Err(err) => err.into_tokens(),
39        Ok(tokens) => tokens,
40    }
41}
42
43fn fail_derive_impl(s: synstructure::Structure) -> Result<TokenStream, Error> {
44    let make_dyn = if cfg!(has_dyn_trait) {
45        quote! { &dyn }
46    } else {
47        quote! { & }
48    };
49
50    let ty_name = LitStr::new(&s.ast().ident.to_string(), Span::call_site());
51
52    let cause_body = s.each_variant(|v| {
53        if let Some(cause) = v.bindings().iter().find(is_cause) {
54            quote!(return Some(::failure::AsFail::as_fail(#cause)))
55        } else {
56            quote!(return None)
57        }
58    });
59
60    let bt_body = s.each_variant(|v| {
61        if let Some(bi) = v.bindings().iter().find(is_backtrace) {
62            quote!(return Some(#bi))
63        } else {
64            quote!(return None)
65        }
66    });
67
68    let fail = s.unbound_impl(
69        quote!(::failure::Fail),
70        quote! {
71            fn name(&self) -> Option<&str> {
72                Some(concat!(module_path!(), "::", #ty_name))
73            }
74
75            #[allow(unreachable_code)]
76            fn cause(&self) -> ::failure::_core::option::Option<#make_dyn(::failure::Fail)> {
77                match *self { #cause_body }
78                None
79            }
80
81            #[allow(unreachable_code)]
82            fn backtrace(&self) -> ::failure::_core::option::Option<&::failure::Backtrace> {
83                match *self { #bt_body }
84                None
85            }
86        },
87    );
88    let display = display_body(&s)?.map(|display_body| {
89        s.unbound_impl(
90            quote!(::failure::_core::fmt::Display),
91            quote! {
92                #[allow(unreachable_code)]
93                fn fmt(&self, f: &mut ::failure::_core::fmt::Formatter) -> ::failure::_core::fmt::Result {
94                    match *self { #display_body }
95                    write!(f, "An error has occurred.")
96                }
97            },
98        )
99    });
100
101    Ok(quote! {
102        #fail
103        #display
104    })
105}
106
107fn display_body(s: &synstructure::Structure) -> Result<Option<TokenStream>, Error> {
108    let mut msgs = s.variants().iter().map(|v| find_error_msg(&v.ast().attrs));
109    if msgs.all(|msg| msg.map(|m| m.is_none()).unwrap_or(true)) {
110        return Ok(None);
111    }
112
113    let mut tokens = TokenStream::new();
114    for v in s.variants() {
115        let msg =
116            find_error_msg(&v.ast().attrs)?
117              .ok_or_else(|| Error::new(
118                  v.ast().ident.span(),
119                  "All variants must have display attribute."
120              ))?;
121        if msg.nested.is_empty() {
122            return Err(Error::new(
123                msg.span(),
124                "Expected at least one argument to fail attribute"
125            ));
126        }
127
128        let format_string = match msg.nested[0] {
129            syn::NestedMeta::Meta(syn::Meta::NameValue(ref nv)) if nv.path.is_ident("display") => {
130                nv.lit.clone()
131            }
132            _ => {
133                return Err(Error::new(
134                    msg.span(),
135                    "Fail attribute must begin `display = \"\"` to control the Display message."
136                ));
137            }
138        };
139        let args = msg.nested.iter().skip(1).map(|arg| match *arg {
140            syn::NestedMeta::Lit(syn::Lit::Int(ref i)) => {
141                let bi = &v.bindings()[i.base10_parse::<usize>()?];
142                Ok(quote!(#bi))
143            }
144            syn::NestedMeta::Meta(syn::Meta::Path(ref path)) => {
145                let id_s = path.get_ident().map(syn::Ident::to_string).unwrap_or("".to_string());
146                if id_s.starts_with("_") {
147                    if let Ok(idx) = id_s[1..].parse::<usize>() {
148                        let bi = match v.bindings().get(idx) {
149                            Some(bi) => bi,
150                            None => {
151                                return Err(Error::new(
152                                    arg.span(),
153                                    &format!(
154                                        "display attempted to access field `{}` in `{}::{}` which \
155                                     does not exist (there are {} field{})",
156                                        idx,
157                                        s.ast().ident,
158                                        v.ast().ident,
159                                        v.bindings().len(),
160                                        if v.bindings().len() != 1 { "s" } else { "" }
161                                    )
162                                ));
163                            }
164                        };
165                        return Ok(quote!(#bi));
166                    }
167                }
168                for bi in v.bindings() {
169                    let id = bi.ast().ident.as_ref();
170                    if id.is_some() && path.is_ident(id.unwrap()) {
171                        return Ok(quote!(#bi));
172                    }
173                }
174                return Err(Error::new(
175                    arg.span(),
176                    &format!(
177                        "Couldn't find field `{:?}` in `{}::{}`",
178                        path,
179                        s.ast().ident,
180                        v.ast().ident
181                    )
182                ));
183            }
184            ref arg => {
185                return Err(Error::new(
186                    arg.span(),
187                    "Invalid argument to fail attribute!"
188                ));
189            },
190        });
191        let args = args.collect::<Result<Vec<_>, _>>()?;
192
193        let pat = v.pat();
194        tokens.extend(quote!(#pat => { return write!(f, #format_string #(, #args)*) }));
195    }
196    Ok(Some(tokens))
197}
198
199fn find_error_msg(attrs: &[syn::Attribute]) -> Result<Option<syn::MetaList>, Error> {
200    let mut error_msg = None;
201    for attr in attrs {
202        if let Ok(meta) = attr.parse_meta() {
203            if meta.path().is_ident("fail") {
204                if error_msg.is_some() {
205                    return Err(Error::new(
206                        meta.span(),
207                        "Cannot have two display attributes"
208                    ));
209                } else {
210                    if let syn::Meta::List(list) = meta {
211                        error_msg = Some(list);
212                    } else {
213                        return Err(Error::new(
214                            meta.span(),
215                            "fail attribute must take a list in parentheses"
216                        ));
217                    }
218                }
219            }
220        }
221    }
222    Ok(error_msg)
223}
224
225fn is_backtrace(bi: &&synstructure::BindingInfo) -> bool {
226    match bi.ast().ty {
227        syn::Type::Path(syn::TypePath {
228            qself: None,
229            path: syn::Path {
230                segments: ref path, ..
231            },
232        }) => path.last().map_or(false, |s| {
233            s.ident == "Backtrace" && s.arguments.is_empty()
234        }),
235        _ => false,
236    }
237}
238
239fn is_cause(bi: &&synstructure::BindingInfo) -> bool {
240    let mut found_cause = false;
241    for attr in &bi.ast().attrs {
242        if let Ok(meta) = attr.parse_meta() {
243            if meta.path().is_ident("cause") {
244                if found_cause {
245                    panic!("Cannot have two `cause` attributes");
246                }
247                found_cause = true;
248            }
249            if meta.path().is_ident("fail") {
250                if let syn::Meta::List(ref list) = meta {
251                    if let Some(ref pair) = list.nested.first() {
252                        if let &&syn::NestedMeta::Meta(syn::Meta::Path(ref path)) = pair {
253                            if path.is_ident("cause") {
254                                if found_cause {
255                                    panic!("Cannot have two `cause` attributes");
256                                }
257                                found_cause = true;
258                            }
259                        }
260                    }
261                }
262            }
263        }
264    }
265    found_cause
266}