mirror of
https://github.com/dtolnay/thiserror.git
synced 2025-04-05 13:57:38 +03:00
Merge pull request #28 from dtolnay/backtrace
Prefer the source's backtrace if it has one
This commit is contained in:
commit
c2865642b0
5 changed files with 96 additions and 34 deletions
|
@ -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;
|
||||||
|
|
|
@ -28,10 +28,22 @@ fn impl_struct(input: Struct) -> TokenStream {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let backtrace_method = input.backtrace_member().map(|backtrace| {
|
let backtrace_method = input.backtrace_field().map(|backtrace| {
|
||||||
|
let backtrace = &backtrace.member;
|
||||||
|
let body = if let Some(source) = input.source_member() {
|
||||||
|
let dyn_error = quote_spanned!(source.span()=> self.#source.as_dyn_error());
|
||||||
|
quote!({
|
||||||
|
use thiserror::private::AsDynError;
|
||||||
|
#dyn_error.backtrace().unwrap_or(&self.#backtrace)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
&self.#backtrace
|
||||||
|
}
|
||||||
|
};
|
||||||
quote! {
|
quote! {
|
||||||
fn backtrace(&self) -> std::option::Option<&std::backtrace::Backtrace> {
|
fn backtrace(&self) -> std::option::Option<&std::backtrace::Backtrace> {
|
||||||
std::option::Option::Some(&self.#backtrace)
|
std::option::Option::Some(#body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -67,10 +79,9 @@ fn impl_enum(input: Enum) -> TokenStream {
|
||||||
let ident = &variant.ident;
|
let ident = &variant.ident;
|
||||||
match variant.source_member() {
|
match variant.source_member() {
|
||||||
Some(source) => {
|
Some(source) => {
|
||||||
let var = quote_spanned!(source.span()=> source);
|
let dyn_error = quote_spanned!(source.span()=> source.as_dyn_error());
|
||||||
let dyn_error = quote_spanned!(source.span()=> #var.as_dyn_error());
|
|
||||||
quote! {
|
quote! {
|
||||||
#ty::#ident {#source: #var, ..} => std::option::Option::Some(#dyn_error),
|
#ty::#ident {#source: source, ..} => std::option::Option::Some(#dyn_error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => quote! {
|
None => quote! {
|
||||||
|
@ -93,11 +104,28 @@ fn impl_enum(input: Enum) -> TokenStream {
|
||||||
let backtrace_method = if input.has_backtrace() {
|
let backtrace_method = if input.has_backtrace() {
|
||||||
let arms = input.variants.iter().map(|variant| {
|
let arms = input.variants.iter().map(|variant| {
|
||||||
let ident = &variant.ident;
|
let ident = &variant.ident;
|
||||||
match variant.backtrace_member() {
|
match (variant.backtrace_field(), variant.source_member()) {
|
||||||
Some(backtrace) => quote! {
|
(Some(backtrace), Some(source)) if backtrace.attrs.backtrace.is_none() => {
|
||||||
#ty::#ident {#backtrace: backtrace, ..} => std::option::Option::Some(backtrace),
|
let backtrace = &backtrace.member;
|
||||||
},
|
let dyn_error = quote_spanned!(source.span()=> source.as_dyn_error());
|
||||||
None => quote! {
|
quote! {
|
||||||
|
#ty::#ident {
|
||||||
|
#backtrace: backtrace,
|
||||||
|
#source: source,
|
||||||
|
..
|
||||||
|
} => std::option::Option::Some({
|
||||||
|
use thiserror::private::AsDynError;
|
||||||
|
#dyn_error.backtrace().unwrap_or(backtrace)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Some(backtrace), _) => {
|
||||||
|
let backtrace = &backtrace.member;
|
||||||
|
quote! {
|
||||||
|
#ty::#ident {#backtrace: backtrace, ..} => std::option::Option::Some(backtrace),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(None, _) => quote! {
|
||||||
#ty::#ident {..} => std::option::Option::None,
|
#ty::#ident {..} => std::option::Option::None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -6,8 +6,8 @@ impl Struct<'_> {
|
||||||
source_member(&self.fields)
|
source_member(&self.fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn backtrace_member(&self) -> Option<&Member> {
|
pub(crate) fn backtrace_field(&self) -> Option<&Field> {
|
||||||
backtrace_member(&self.fields)
|
backtrace_field(&self.fields)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ impl Enum<'_> {
|
||||||
pub(crate) fn has_backtrace(&self) -> bool {
|
pub(crate) fn has_backtrace(&self) -> bool {
|
||||||
self.variants
|
self.variants
|
||||||
.iter()
|
.iter()
|
||||||
.any(|variant| variant.backtrace_member().is_some())
|
.any(|variant| variant.backtrace_field().is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn has_display(&self) -> bool {
|
pub(crate) fn has_display(&self) -> bool {
|
||||||
|
@ -38,14 +38,8 @@ impl Variant<'_> {
|
||||||
source_member(&self.fields)
|
source_member(&self.fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn backtrace_member(&self) -> Option<&Member> {
|
pub(crate) fn backtrace_field(&self) -> Option<&Field> {
|
||||||
backtrace_member(&self.fields)
|
backtrace_field(&self.fields)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Field<'_> {
|
|
||||||
fn is_backtrace(&self) -> bool {
|
|
||||||
type_is_backtrace(self.ty)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,11 +58,18 @@ fn source_member<'a>(fields: &'a [Field]) -> Option<&'a Member> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn backtrace_member<'a>(fields: &'a [Field]) -> Option<&'a Member> {
|
fn backtrace_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> {
|
||||||
fields
|
for field in fields {
|
||||||
.iter()
|
if field.attrs.backtrace.is_some() {
|
||||||
.find(|field| field.is_backtrace())
|
return Some(&field);
|
||||||
.map(|field| &field.member)
|
}
|
||||||
|
}
|
||||||
|
for field in fields {
|
||||||
|
if type_is_backtrace(field.ty) {
|
||||||
|
return Some(&field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn type_is_backtrace(ty: &Type) -> bool {
|
fn type_is_backtrace(ty: &Type) -> bool {
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue