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