]>
Commit | Line | Data |
---|---|---|
8faf50e0 | 1 | extern crate proc_macro2; |
94b46f34 XL |
2 | extern crate syn; |
3 | ||
8faf50e0 XL |
4 | #[macro_use] |
5 | extern crate synstructure; | |
6 | #[macro_use] | |
7 | extern crate quote; | |
8 | ||
9fa01778 XL |
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 { | |
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 | } | |
94b46f34 XL |
27 | |
28 | decl_derive!([Fail, attributes(fail, cause)] => fail_derive); | |
29 | ||
8faf50e0 | 30 | fn fail_derive(s: synstructure::Structure) -> TokenStream { |
9fa01778 XL |
31 | match fail_derive_impl(s) { |
32 | Err(err) => err.into_tokens(), | |
33 | Ok(tokens) => tokens, | |
34 | } | |
35 | } | |
36 | ||
37 | fn fail_derive_impl(s: synstructure::Structure) -> Result<TokenStream, Error> { | |
8faf50e0 XL |
38 | let make_dyn = if cfg!(has_dyn_trait) { |
39 | quote! { &dyn } | |
40 | } else { | |
41 | quote! { & } | |
42 | }; | |
43 | ||
9fa01778 XL |
44 | let ty_name = LitStr::new(&s.ast().ident.to_string(), Span::call_site()); |
45 | ||
94b46f34 XL |
46 | let cause_body = s.each_variant(|v| { |
47 | if let Some(cause) = v.bindings().iter().find(is_cause) { | |
0731742a | 48 | quote!(return Some(::failure::AsFail::as_fail(#cause))) |
94b46f34 XL |
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 | ||
8faf50e0 XL |
62 | let fail = s.unbound_impl( |
63 | quote!(::failure::Fail), | |
64 | quote! { | |
9fa01778 XL |
65 | fn name(&self) -> Option<&str> { |
66 | Some(concat!(module_path!(), "::", #ty_name)) | |
67 | } | |
68 | ||
94b46f34 | 69 | #[allow(unreachable_code)] |
8faf50e0 XL |
70 | fn cause(&self) -> ::failure::_core::option::Option<#make_dyn(::failure::Fail)> { |
71 | match *self { #cause_body } | |
72 | None | |
94b46f34 | 73 | } |
94b46f34 | 74 | |
94b46f34 | 75 | #[allow(unreachable_code)] |
8faf50e0 XL |
76 | fn backtrace(&self) -> ::failure::_core::option::Option<&::failure::Backtrace> { |
77 | match *self { #bt_body } | |
78 | None | |
94b46f34 | 79 | } |
8faf50e0 XL |
80 | }, |
81 | ); | |
9fa01778 | 82 | let display = display_body(&s)?.map(|display_body| { |
8faf50e0 XL |
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 | ) | |
94b46f34 XL |
93 | }); |
94 | ||
9fa01778 | 95 | Ok(quote! { |
94b46f34 XL |
96 | #fail |
97 | #display | |
9fa01778 | 98 | }) |
94b46f34 XL |
99 | } |
100 | ||
9fa01778 | 101 | fn display_body(s: &synstructure::Structure) -> Result<Option<quote::__rt::TokenStream>, Error> { |
94b46f34 | 102 | let mut msgs = s.variants().iter().map(|v| find_error_msg(&v.ast().attrs)); |
9fa01778 XL |
103 | if msgs.all(|msg| msg.map(|m| m.is_none()).unwrap_or(true)) { |
104 | return Ok(None); | |
8faf50e0 | 105 | } |
94b46f34 | 106 | |
9fa01778 XL |
107 | let mut tokens = TokenStream::new(); |
108 | for v in s.variants() { | |
8faf50e0 | 109 | let msg = |
9fa01778 XL |
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 | ))?; | |
8faf50e0 | 115 | if msg.nested.is_empty() { |
9fa01778 XL |
116 | return Err(Error::new( |
117 | msg.span(), | |
118 | "Expected at least one argument to fail attribute" | |
119 | )); | |
94b46f34 XL |
120 | } |
121 | ||
8faf50e0 XL |
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 | _ => { | |
9fa01778 XL |
127 | return Err(Error::new( |
128 | msg.span(), | |
129 | "Fail attribute must begin `display = \"\"` to control the Display message." | |
130 | )); | |
94b46f34 | 131 | } |
94b46f34 | 132 | }; |
8faf50e0 XL |
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]; | |
9fa01778 | 136 | Ok(quote!(#bi)) |
94b46f34 | 137 | } |
8faf50e0 XL |
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 => { | |
9fa01778 XL |
145 | return Err(Error::new( |
146 | arg.span(), | |
147 | &format!( | |
148 | "display attempted to access field `{}` in `{}::{}` which \ | |
8faf50e0 | 149 | does not exist (there are {} field{})", |
9fa01778 XL |
150 | idx, |
151 | s.ast().ident, | |
152 | v.ast().ident, | |
153 | v.bindings().len(), | |
154 | if v.bindings().len() != 1 { "s" } else { "" } | |
155 | ) | |
156 | )); | |
8faf50e0 XL |
157 | } |
158 | }; | |
9fa01778 | 159 | return Ok(quote!(#bi)); |
94b46f34 XL |
160 | } |
161 | } | |
162 | for bi in v.bindings() { | |
163 | if bi.ast().ident.as_ref() == Some(id) { | |
9fa01778 | 164 | return Ok(quote!(#bi)); |
94b46f34 XL |
165 | } |
166 | } | |
9fa01778 XL |
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 | )); | |
94b46f34 | 176 | } |
9fa01778 XL |
177 | ref arg => { |
178 | return Err(Error::new( | |
179 | arg.span(), | |
180 | "Invalid argument to fail attribute!" | |
181 | )); | |
182 | }, | |
94b46f34 | 183 | }); |
9fa01778 | 184 | let args = args.collect::<Result<Vec<_>, _>>()?; |
94b46f34 | 185 | |
9fa01778 XL |
186 | let pat = v.pat(); |
187 | tokens.extend(quote!(#pat => { return write!(f, #format_string #(, #args)*) })); | |
188 | } | |
189 | Ok(Some(tokens)) | |
94b46f34 XL |
190 | } |
191 | ||
9fa01778 | 192 | fn find_error_msg(attrs: &[syn::Attribute]) -> Result<Option<syn::MetaList>, Error> { |
94b46f34 XL |
193 | let mut error_msg = None; |
194 | for attr in attrs { | |
8faf50e0 XL |
195 | if let Some(meta) = attr.interpret_meta() { |
196 | if meta.name() == "fail" { | |
197 | if error_msg.is_some() { | |
9fa01778 XL |
198 | return Err(Error::new( |
199 | meta.span(), | |
200 | "Cannot have two display attributes" | |
201 | )); | |
94b46f34 | 202 | } else { |
8faf50e0 XL |
203 | if let syn::Meta::List(list) = meta { |
204 | error_msg = Some(list); | |
205 | } else { | |
9fa01778 XL |
206 | return Err(Error::new( |
207 | meta.span(), | |
208 | "fail attribute must take a list in parentheses" | |
209 | )); | |
8faf50e0 | 210 | } |
94b46f34 XL |
211 | } |
212 | } | |
213 | } | |
214 | } | |
9fa01778 | 215 | Ok(error_msg) |
94b46f34 XL |
216 | } |
217 | ||
218 | fn is_backtrace(bi: &&synstructure::BindingInfo) -> bool { | |
8faf50e0 XL |
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 | } | |
94b46f34 XL |
230 | } |
231 | ||
232 | fn is_cause(bi: &&synstructure::BindingInfo) -> bool { | |
8faf50e0 XL |
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 | |
94b46f34 | 259 | } |