Skip to content
This repository was archived by the owner on Aug 16, 2021. It is now read-only.

Commit 15b6798

Browse files
authored
Improving procmacro error reporting
This Improves the error reporting of procmacro by using compile_error! and quote_spanned!.
1 parent 22bfd31 commit 15b6798

File tree

3 files changed

+225
-37
lines changed

3 files changed

+225
-37
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ rust:
66
- nightly
77
cache: cargo
88
script:
9+
- if [ "$TRAVIS_RUST_VERSION" == "1.18.0" ]; then cp Cargo.lock.ci Cargo.lock; fi
910
- cargo test
1011
- cargo test --features backtrace
1112
- cargo check --no-default-features

Cargo.lock.ci

Lines changed: 136 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

failure_derive/src/lib.rs

Lines changed: 88 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,34 @@ extern crate synstructure;
66
#[macro_use]
77
extern crate quote;
88

9-
use proc_macro2::TokenStream;
9+
use proc_macro2::{TokenStream, Span};
10+
use syn::spanned::Spanned;
11+
12+
#[derive(Debug)]
13+
struct Error(TokenStream);
14+
15+
impl Error {
16+
fn new(span: Span, message: &str) -> Error {
17+
Error(quote_spanned! { span =>
18+
compile_error!(#message);
19+
})
20+
}
21+
22+
fn into_tokens(self) -> TokenStream {
23+
self.0
24+
}
25+
}
1026

1127
decl_derive!([Fail, attributes(fail, cause)] => fail_derive);
1228

1329
fn fail_derive(s: synstructure::Structure) -> TokenStream {
30+
match fail_derive_impl(s) {
31+
Err(err) => err.into_tokens(),
32+
Ok(tokens) => tokens,
33+
}
34+
}
35+
36+
fn fail_derive_impl(s: synstructure::Structure) -> Result<TokenStream, Error> {
1437
let make_dyn = if cfg!(has_dyn_trait) {
1538
quote! { &dyn }
1639
} else {
@@ -49,7 +72,7 @@ fn fail_derive(s: synstructure::Structure) -> TokenStream {
4972
}
5073
},
5174
);
52-
let display = display_body(&s).map(|display_body| {
75+
let display = display_body(&s)?.map(|display_body| {
5376
s.unbound_impl(
5477
quote!(::failure::_core::fmt::Display),
5578
quote! {
@@ -62,38 +85,48 @@ fn fail_derive(s: synstructure::Structure) -> TokenStream {
6285
)
6386
});
6487

65-
(quote! {
88+
Ok(quote! {
6689
#fail
6790
#display
6891
})
69-
.into()
7092
}
7193

72-
fn display_body(s: &synstructure::Structure) -> Option<quote::__rt::TokenStream> {
94+
fn display_body(s: &synstructure::Structure) -> Result<Option<quote::__rt::TokenStream>, Error> {
7395
let mut msgs = s.variants().iter().map(|v| find_error_msg(&v.ast().attrs));
74-
if msgs.all(|msg| msg.is_none()) {
75-
return None;
96+
if msgs.all(|msg| msg.map(|m| m.is_none()).unwrap_or(true)) {
97+
return Ok(None);
7698
}
7799

78-
Some(s.each_variant(|v| {
100+
let mut tokens = TokenStream::new();
101+
for v in s.variants() {
79102
let msg =
80-
find_error_msg(&v.ast().attrs).expect("All variants must have display attribute.");
103+
find_error_msg(&v.ast().attrs)?
104+
.ok_or_else(|| Error::new(
105+
v.ast().ident.span(),
106+
"All variants must have display attribute."
107+
))?;
81108
if msg.nested.is_empty() {
82-
panic!("Expected at least one argument to fail attribute");
109+
return Err(Error::new(
110+
msg.span(),
111+
"Expected at least one argument to fail attribute"
112+
));
83113
}
84114

85115
let format_string = match msg.nested[0] {
86116
syn::NestedMeta::Meta(syn::Meta::NameValue(ref nv)) if nv.ident == "display" => {
87117
nv.lit.clone()
88118
}
89119
_ => {
90-
panic!("Fail attribute must begin `display = \"\"` to control the Display message.")
120+
return Err(Error::new(
121+
msg.span(),
122+
"Fail attribute must begin `display = \"\"` to control the Display message."
123+
));
91124
}
92125
};
93126
let args = msg.nested.iter().skip(1).map(|arg| match *arg {
94127
syn::NestedMeta::Literal(syn::Lit::Int(ref i)) => {
95128
let bi = &v.bindings()[i.value() as usize];
96-
quote!(#bi)
129+
Ok(quote!(#bi))
97130
}
98131
syn::NestedMeta::Meta(syn::Meta::Word(ref id)) => {
99132
let id_s = id.to_string();
@@ -102,59 +135,77 @@ fn display_body(s: &synstructure::Structure) -> Option<quote::__rt::TokenStream>
102135
let bi = match v.bindings().get(idx) {
103136
Some(bi) => bi,
104137
None => {
105-
panic!(
106-
"display attempted to access field `{}` in `{}::{}` which \
138+
return Err(Error::new(
139+
arg.span(),
140+
&format!(
141+
"display attempted to access field `{}` in `{}::{}` which \
107142
does not exist (there are {} field{})",
108-
idx,
109-
s.ast().ident,
110-
v.ast().ident,
111-
v.bindings().len(),
112-
if v.bindings().len() != 1 { "s" } else { "" }
113-
);
143+
idx,
144+
s.ast().ident,
145+
v.ast().ident,
146+
v.bindings().len(),
147+
if v.bindings().len() != 1 { "s" } else { "" }
148+
)
149+
));
114150
}
115151
};
116-
return quote!(#bi);
152+
return Ok(quote!(#bi));
117153
}
118154
}
119155
for bi in v.bindings() {
120156
if bi.ast().ident.as_ref() == Some(id) {
121-
return quote!(#bi);
157+
return Ok(quote!(#bi));
122158
}
123159
}
124-
panic!(
125-
"Couldn't find field `{}` in `{}::{}`",
126-
id,
127-
s.ast().ident,
128-
v.ast().ident
129-
);
160+
return Err(Error::new(
161+
arg.span(),
162+
&format!(
163+
"Couldn't find field `{}` in `{}::{}`",
164+
id,
165+
s.ast().ident,
166+
v.ast().ident
167+
)
168+
));
130169
}
131-
_ => panic!("Invalid argument to fail attribute!"),
170+
ref arg => {
171+
return Err(Error::new(
172+
arg.span(),
173+
"Invalid argument to fail attribute!"
174+
));
175+
},
132176
});
177+
let args = args.collect::<Result<Vec<_>, _>>()?;
133178

134-
quote! {
135-
return write!(f, #format_string #(, #args)*)
136-
}
137-
}))
179+
let pat = v.pat();
180+
tokens.extend(quote!(#pat => { return write!(f, #format_string #(, #args)*) }));
181+
}
182+
Ok(Some(tokens))
138183
}
139184

140-
fn find_error_msg(attrs: &[syn::Attribute]) -> Option<syn::MetaList> {
185+
fn find_error_msg(attrs: &[syn::Attribute]) -> Result<Option<syn::MetaList>, Error> {
141186
let mut error_msg = None;
142187
for attr in attrs {
143188
if let Some(meta) = attr.interpret_meta() {
144189
if meta.name() == "fail" {
145190
if error_msg.is_some() {
146-
panic!("Cannot have two display attributes")
191+
return Err(Error::new(
192+
meta.span(),
193+
"Cannot have two display attributes"
194+
));
147195
} else {
148196
if let syn::Meta::List(list) = meta {
149197
error_msg = Some(list);
150198
} else {
151-
panic!("fail attribute must take a list in parentheses")
199+
return Err(Error::new(
200+
meta.span(),
201+
"fail attribute must take a list in parentheses"
202+
));
152203
}
153204
}
154205
}
155206
}
156207
}
157-
error_msg
208+
Ok(error_msg)
158209
}
159210

160211
fn is_backtrace(bi: &&synstructure::BindingInfo) -> bool {

0 commit comments

Comments
 (0)