Skip to content

Commit

Permalink
Merge pull request #87 from dtolnay/span
Browse files Browse the repository at this point in the history
Reconstruct span information lost by compiler
  • Loading branch information
dtolnay authored May 15, 2020
2 parents f79a85f + 3a49609 commit 72aaea5
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 19 deletions.
44 changes: 31 additions & 13 deletions impl/src/ast.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::attr::{self, Attrs};
use proc_macro2::Span;
use syn::{
Data, DataEnum, DataStruct, DeriveInput, Error, Fields, Generics, Ident, Index, Member, Result,
Type,
Expand Down Expand Up @@ -55,7 +56,8 @@ impl<'a> Input<'a> {
impl<'a> Struct<'a> {
fn from_syn(node: &'a DeriveInput, data: &'a DataStruct) -> Result<Self> {
let mut attrs = attr::get(&node.attrs)?;
let fields = Field::multiple_from_syn(&data.fields)?;
let span = attrs.span().unwrap_or_else(Span::call_site);
let fields = Field::multiple_from_syn(&data.fields, span)?;
if let Some(display) = &mut attrs.display {
display.expand_shorthand(&fields);
}
Expand All @@ -72,11 +74,12 @@ impl<'a> Struct<'a> {
impl<'a> Enum<'a> {
fn from_syn(node: &'a DeriveInput, data: &'a DataEnum) -> Result<Self> {
let attrs = attr::get(&node.attrs)?;
let span = attrs.span().unwrap_or_else(Span::call_site);
let variants = data
.variants
.iter()
.map(|node| {
let mut variant = Variant::from_syn(node)?;
let mut variant = Variant::from_syn(node, span)?;
if let display @ None = &mut variant.attrs.display {
*display = attrs.display.clone();
}
Expand All @@ -99,35 +102,50 @@ impl<'a> Enum<'a> {
}

impl<'a> Variant<'a> {
fn from_syn(node: &'a syn::Variant) -> Result<Self> {
fn from_syn(node: &'a syn::Variant, span: Span) -> Result<Self> {
let attrs = attr::get(&node.attrs)?;
let span = attrs.span().unwrap_or(span);
Ok(Variant {
original: node,
attrs: attr::get(&node.attrs)?,
attrs,
ident: node.ident.clone(),
fields: Field::multiple_from_syn(&node.fields)?,
fields: Field::multiple_from_syn(&node.fields, span)?,
})
}
}

impl<'a> Field<'a> {
fn multiple_from_syn(fields: &'a Fields) -> Result<Vec<Self>> {
fn multiple_from_syn(fields: &'a Fields, span: Span) -> Result<Vec<Self>> {
fields
.iter()
.enumerate()
.map(|(i, field)| Field::from_syn(i, field))
.map(|(i, field)| Field::from_syn(i, field, span))
.collect()
}

fn from_syn(i: usize, node: &'a syn::Field) -> Result<Self> {
fn from_syn(i: usize, node: &'a syn::Field, span: Span) -> Result<Self> {
Ok(Field {
original: node,
attrs: attr::get(&node.attrs)?,
member: node
.ident
.clone()
.map(Member::Named)
.unwrap_or_else(|| Member::Unnamed(Index::from(i))),
member: node.ident.clone().map(Member::Named).unwrap_or_else(|| {
Member::Unnamed(Index {
index: i as u32,
span,
})
}),
ty: &node.ty,
})
}
}

impl Attrs<'_> {
pub fn span(&self) -> Option<Span> {
if let Some(display) = &self.display {
Some(display.fmt.span())
} else if let Some(transparent) = &self.transparent {
Some(transparent.span)
} else {
None
}
}
}
17 changes: 13 additions & 4 deletions impl/src/attr.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
use proc_macro2::{Delimiter, Group, Span, TokenStream, TokenTree};
use quote::{format_ident, quote, ToTokens};
use std::iter::FromIterator;
use syn::parse::{Nothing, ParseStream};
Expand All @@ -12,7 +12,7 @@ pub struct Attrs<'a> {
pub source: Option<&'a Attribute>,
pub backtrace: Option<&'a Attribute>,
pub from: Option<&'a Attribute>,
pub transparent: Option<&'a Attribute>,
pub transparent: Option<Transparent<'a>>,
}

#[derive(Clone)]
Expand All @@ -23,6 +23,12 @@ pub struct Display<'a> {
pub has_bonus_display: bool,
}

#[derive(Copy, Clone)]
pub struct Transparent<'a> {
pub original: &'a Attribute,
pub span: Span,
}

pub fn get(input: &[Attribute]) -> Result<Attrs> {
let mut attrs = Attrs {
display: None,
Expand Down Expand Up @@ -66,14 +72,17 @@ fn parse_error_attribute<'a>(attrs: &mut Attrs<'a>, attr: &'a Attribute) -> Resu
syn::custom_keyword!(transparent);

attr.parse_args_with(|input: ParseStream| {
if input.parse::<Option<transparent>>()?.is_some() {
if let Some(kw) = input.parse::<Option<transparent>>()? {
if attrs.transparent.is_some() {
return Err(Error::new_spanned(
attr,
"duplicate #[error(transparent)] attribute",
));
}
attrs.transparent = Some(attr);
attrs.transparent = Some(Transparent {
original: attr,
span: kw.span,
});
return Ok(());
}

Expand Down
4 changes: 2 additions & 2 deletions impl/src/valid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ impl Struct<'_> {
if let Some(transparent) = self.attrs.transparent {
if self.fields.len() != 1 {
return Err(Error::new_spanned(
transparent,
transparent.original,
"#[error(transparent)] requires exactly one field",
));
}
Expand Down Expand Up @@ -165,7 +165,7 @@ fn check_field_attrs(fields: &[Field]) -> Result<()> {
}
if let Some(transparent) = field.attrs.transparent {
return Err(Error::new_spanned(
transparent,
transparent.original,
"#[error(transparent)] needs to go outside the enum or struct, not on an individual field",
));
}
Expand Down
26 changes: 26 additions & 0 deletions tests/test_display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,29 @@ fn test_field() {

assert("0", Error(Inner { data: 0 }));
}

#[test]
fn test_macro_rules() {
// Regression test for https://github.com/dtolnay/thiserror/issues/86

macro_rules! decl_error {
($variant:ident($value:ident)) => {
#[derive(Debug, Error)]
pub enum Error0 {
#[error("{0:?}")]
$variant($value),
}

#[derive(Debug, Error)]
#[error("{0:?}")]
pub enum Error1 {
$variant($value),
}
};
}

decl_error!(Repro(u8));

assert("0", Error0::Repro(0));
assert("0", Error1::Repro(0));
}

0 comments on commit 72aaea5

Please sign in to comment.