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}