#[backtrace] attribute for explicitly selecting the backtrace field

This commit is contained in:
David Tolnay 2019-10-12 14:38:45 -07:00
parent de6719e923
commit 72cf49ccd4
No known key found for this signature in database
GPG key ID: F9BA143B95FF6D82
4 changed files with 52 additions and 18 deletions

View file

@ -10,6 +10,7 @@ use syn::{
pub struct Attrs<'a> { pub struct Attrs<'a> {
pub display: Option<Display<'a>>, pub display: Option<Display<'a>>,
pub source: Option<Source<'a>>, pub source: Option<Source<'a>>,
pub backtrace: Option<Backtrace<'a>>,
} }
#[derive(Clone)] #[derive(Clone)]
@ -24,10 +25,15 @@ pub struct Source<'a> {
pub original: &'a Attribute, pub original: &'a Attribute,
} }
pub struct Backtrace<'a> {
pub original: &'a Attribute,
}
pub fn get(input: &[Attribute]) -> Result<Attrs> { pub fn get(input: &[Attribute]) -> Result<Attrs> {
let mut attrs = Attrs { let mut attrs = Attrs {
display: None, display: None,
source: None, source: None,
backtrace: None,
}; };
for attr in input { for attr in input {
@ -46,6 +52,12 @@ pub fn get(input: &[Attribute]) -> Result<Attrs> {
return Err(Error::new_spanned(attr, "duplicate #[source] attribute")); return Err(Error::new_spanned(attr, "duplicate #[source] attribute"));
} }
attrs.source = Some(source); attrs.source = Some(source);
} else if attr.path.is_ident("backtrace") {
let backtrace = parse_backtrace(attr)?;
if attrs.backtrace.is_some() {
return Err(Error::new_spanned(attr, "duplicate #[backtrace] attribute"));
}
attrs.backtrace = Some(backtrace);
} }
} }
@ -118,6 +130,11 @@ fn parse_source(attr: &Attribute) -> Result<Source> {
Ok(Source { original: attr }) Ok(Source { original: attr })
} }
fn parse_backtrace(attr: &Attribute) -> Result<Backtrace> {
syn::parse2::<Nothing>(attr.tokens.clone())?;
Ok(Backtrace { original: attr })
}
impl ToTokens for Display<'_> { impl ToTokens for Display<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) { fn to_tokens(&self, tokens: &mut TokenStream) {
let fmt = &self.fmt; let fmt = &self.fmt;

View file

@ -10,7 +10,7 @@ mod valid;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput}; use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(Error, attributes(error, source))] #[proc_macro_derive(Error, attributes(backtrace, error, source))]
pub fn derive_error(input: TokenStream) -> TokenStream { pub fn derive_error(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput); let input = parse_macro_input!(input as DeriveInput);
expand::derive(&input) expand::derive(&input)

View file

@ -43,12 +43,6 @@ impl Variant<'_> {
} }
} }
impl Field<'_> {
fn is_backtrace(&self) -> bool {
type_is_backtrace(self.ty)
}
}
fn source_member<'a>(fields: &'a [Field]) -> Option<&'a Member> { fn source_member<'a>(fields: &'a [Field]) -> Option<&'a Member> {
for field in fields { for field in fields {
if field.attrs.source.is_some() { if field.attrs.source.is_some() {
@ -65,10 +59,17 @@ fn source_member<'a>(fields: &'a [Field]) -> Option<&'a Member> {
} }
fn backtrace_member<'a>(fields: &'a [Field]) -> Option<&'a Member> { fn backtrace_member<'a>(fields: &'a [Field]) -> Option<&'a Member> {
fields for field in fields {
.iter() if field.attrs.backtrace.is_some() {
.find(|field| field.is_backtrace()) return Some(&field.member);
.map(|field| &field.member) }
}
for field in fields {
if type_is_backtrace(field.ty) {
return Some(&field.member);
}
}
None
} }
fn type_is_backtrace(ty: &Type) -> bool { fn type_is_backtrace(ty: &Type) -> bool {

View file

@ -15,8 +15,8 @@ impl Input<'_> {
impl Struct<'_> { impl Struct<'_> {
fn validate(&self) -> Result<()> { fn validate(&self) -> Result<()> {
check_no_source(&self.attrs)?; check_no_source_or_backtrace(&self.attrs)?;
find_duplicate_source(&self.fields)?; check_no_duplicate_source_or_backtrace(&self.fields)?;
for field in &self.fields { for field in &self.fields {
field.validate()?; field.validate()?;
} }
@ -26,7 +26,7 @@ impl Struct<'_> {
impl Enum<'_> { impl Enum<'_> {
fn validate(&self) -> Result<()> { fn validate(&self) -> Result<()> {
check_no_source(&self.attrs)?; check_no_source_or_backtrace(&self.attrs)?;
let has_display = self.has_display(); let has_display = self.has_display();
for variant in &self.variants { for variant in &self.variants {
variant.validate()?; variant.validate()?;
@ -43,8 +43,8 @@ impl Enum<'_> {
impl Variant<'_> { impl Variant<'_> {
fn validate(&self) -> Result<()> { fn validate(&self) -> Result<()> {
check_no_source(&self.attrs)?; check_no_source_or_backtrace(&self.attrs)?;
find_duplicate_source(&self.fields)?; check_no_duplicate_source_or_backtrace(&self.fields)?;
for field in &self.fields { for field in &self.fields {
field.validate()?; field.validate()?;
} }
@ -64,18 +64,25 @@ impl Field<'_> {
} }
} }
fn check_no_source(attrs: &Attrs) -> Result<()> { fn check_no_source_or_backtrace(attrs: &Attrs) -> Result<()> {
if let Some(source) = &attrs.source { if let Some(source) = &attrs.source {
return Err(Error::new_spanned( return Err(Error::new_spanned(
source.original, source.original,
"not expected here; the #[source] attribute belongs on a specific field", "not expected here; the #[source] attribute belongs on a specific field",
)); ));
} }
if let Some(backtrace) = &attrs.backtrace {
return Err(Error::new_spanned(
backtrace.original,
"not expected here; the #[backtrace] attribute belongs on a specific field",
));
}
Ok(()) Ok(())
} }
fn find_duplicate_source(fields: &[Field]) -> Result<()> { fn check_no_duplicate_source_or_backtrace(fields: &[Field]) -> Result<()> {
let mut has_source = false; let mut has_source = false;
let mut has_backtrace = false;
for field in fields { for field in fields {
if let Some(source) = &field.attrs.source { if let Some(source) = &field.attrs.source {
if has_source { if has_source {
@ -86,6 +93,15 @@ fn find_duplicate_source(fields: &[Field]) -> Result<()> {
} }
has_source = true; has_source = true;
} }
if let Some(backtrace) = &field.attrs.backtrace {
if has_backtrace {
return Err(Error::new_spanned(
backtrace.original,
"duplicate #[backtrace] attribute",
));
}
has_backtrace = true;
}
} }
Ok(()) Ok(())
} }