diff --git a/README.md b/README.md index 76c436a..e12b693 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,20 @@ pub enum DataStoreError { } ``` +- If a field is both a source (named `source`, or has `#[source]` or `#[from]` + attribute) *and* is marked `#[backtrace]`, then the Error trait's + `backtrace()` method is forwarded to the source's backtrace. + + ```rust + #[derive(Error, Debug)] + pub enum MyError { + Io { + #[backtrace] + source: io::Error, + }, + } + ``` + - Errors may use `error(transparent)` to forward the source and Display methods straight through to an underlying error without adding an additional message. This would be appropriate for enums that need an "anything else" variant. diff --git a/impl/src/expand.rs b/impl/src/expand.rs index 789eee6..5f761b8 100644 --- a/impl/src/expand.rs +++ b/impl/src/expand.rs @@ -58,7 +58,9 @@ fn impl_struct(input: Struct) -> TokenStream { self.#source.as_dyn_error().backtrace() } }; - let combinator = if type_is_option(backtrace_field.ty) { + let combinator = if source == backtrace { + source_backtrace + } else if type_is_option(backtrace_field.ty) { quote! { #source_backtrace.or(self.#backtrace.as_ref()) } @@ -128,7 +130,7 @@ fn impl_struct(input: Struct) -> TokenStream { }); let from_impl = input.from_field().map(|from_field| { - let backtrace_field = input.backtrace_field(); + let backtrace_field = input.distinct_backtrace_field(); let from = from_field.ty; let body = from_initializer(from_field, backtrace_field); quote! { @@ -238,6 +240,27 @@ fn impl_enum(input: Enum) -> TokenStream { } } } + (Some(backtrace_field), Some(source_field)) + if backtrace_field.member == source_field.member => + { + let backtrace = &backtrace_field.member; + let varsource = quote!(source); + let source_backtrace = if type_is_option(source_field.ty) { + quote_spanned! {backtrace.span()=> + #varsource.as_ref().and_then(|source| source.as_dyn_error().backtrace()) + } + } else { + quote_spanned! {backtrace.span()=> + #varsource.as_dyn_error().backtrace() + } + }; + quote! { + #ty::#ident {#backtrace: #varsource, ..} => { + use thiserror::private::AsDynError; + #source_backtrace + } + } + } (Some(backtrace_field), _) => { let backtrace = &backtrace_field.member; let body = if type_is_option(backtrace_field.ty) { @@ -326,7 +349,7 @@ fn impl_enum(input: Enum) -> TokenStream { let from_impls = input.variants.iter().filter_map(|variant| { let from_field = variant.from_field()?; - let backtrace_field = variant.backtrace_field(); + let backtrace_field = variant.distinct_backtrace_field(); let variant = &variant.ident; let from = from_field.ty; let body = from_initializer(from_field, backtrace_field); diff --git a/impl/src/prop.rs b/impl/src/prop.rs index 059b74b..6d8a924 100644 --- a/impl/src/prop.rs +++ b/impl/src/prop.rs @@ -13,6 +13,11 @@ impl Struct<'_> { pub(crate) fn backtrace_field(&self) -> Option<&Field> { backtrace_field(&self.fields) } + + pub(crate) fn distinct_backtrace_field(&self) -> Option<&Field> { + let backtrace_field = self.backtrace_field()?; + distinct_backtrace_field(backtrace_field, self.from_field()) + } } impl Enum<'_> { @@ -54,6 +59,11 @@ impl Variant<'_> { pub(crate) fn backtrace_field(&self) -> Option<&Field> { backtrace_field(&self.fields) } + + pub(crate) fn distinct_backtrace_field(&self) -> Option<&Field> { + let backtrace_field = self.backtrace_field()?; + distinct_backtrace_field(backtrace_field, self.from_field()) + } } impl Field<'_> { @@ -100,6 +110,20 @@ fn backtrace_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> { None } +// The #[backtrace] field, if it is not the same as the #[from] field. +fn distinct_backtrace_field<'a, 'b>( + backtrace_field: &'a Field<'b>, + from_field: Option<&Field>, +) -> Option<&'a Field<'b>> { + if from_field.map_or(false, |from_field| { + from_field.member == backtrace_field.member + }) { + None + } else { + Some(backtrace_field) + } +} + fn type_is_backtrace(ty: &Type) -> bool { let path = match ty { Type::Path(ty) => &ty.path, diff --git a/src/lib.rs b/src/lib.rs index 11df494..2fae25c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -161,6 +161,22 @@ //! # }; //! ``` //! +//! - If a field is both a source (named `source`, or has `#[source]` or +//! `#[from]` attribute) *and* is marked `#[backtrace]`, then the Error +//! trait's `backtrace()` method is forwarded to the source's backtrace. +//! +//! ```rust +//! # const IGNORE: &str = stringify! { +//! #[derive(Error, Debug)] +//! pub enum MyError { +//! Io { +//! #[backtrace] +//! source: io::Error, +//! }, +//! } +//! # }; +//! ``` +//! //! - Errors may use `error(transparent)` to forward the source and Display //! methods straight through to an underlying error without adding an //! additional message. This would be appropriate for enums that need an diff --git a/tests/test_backtrace.rs b/tests/test_backtrace.rs index c7ba3c4..86c01c7 100644 --- a/tests/test_backtrace.rs +++ b/tests/test_backtrace.rs @@ -10,9 +10,16 @@ use thiserror::Error; #[error("...")] pub struct Inner; +#[cfg(thiserror_nightly_testing)] +#[derive(Error, Debug)] +#[error("...")] +pub struct InnerBacktrace { + backtrace: std::backtrace::Backtrace, +} + #[cfg(thiserror_nightly_testing)] pub mod structs { - use super::Inner; + use super::{Inner, InnerBacktrace}; use std::backtrace::Backtrace; use std::error::Error; use std::sync::Arc; @@ -54,6 +61,14 @@ pub mod structs { backtrace: Backtrace, } + #[derive(Error, Debug)] + #[error("...")] + pub struct CombinedBacktraceFrom { + #[from] + #[backtrace] + source: InnerBacktrace, + } + #[derive(Error, Debug)] #[error("...")] pub struct OptBacktraceFrom { @@ -97,6 +112,11 @@ pub mod structs { let error = BacktraceFrom::from(Inner); assert!(error.backtrace().is_some()); + let error = CombinedBacktraceFrom::from(InnerBacktrace { + backtrace: Backtrace::capture(), + }); + assert!(error.backtrace().is_some()); + let error = OptBacktraceFrom::from(Inner); assert!(error.backtrace().is_some()); @@ -107,7 +127,7 @@ pub mod structs { #[cfg(thiserror_nightly_testing)] pub mod enums { - use super::Inner; + use super::{Inner, InnerBacktrace}; use std::backtrace::Backtrace; use std::error::Error; use std::sync::Arc; @@ -157,6 +177,16 @@ pub mod enums { }, } + #[derive(Error, Debug)] + pub enum CombinedBacktraceFrom { + #[error("...")] + Test { + #[from] + #[backtrace] + source: InnerBacktrace, + }, + } + #[derive(Error, Debug)] pub enum OptBacktraceFrom { #[error("...")] @@ -204,6 +234,11 @@ pub mod enums { let error = BacktraceFrom::from(Inner); assert!(error.backtrace().is_some()); + let error = CombinedBacktraceFrom::from(InnerBacktrace { + backtrace: Backtrace::capture(), + }); + assert!(error.backtrace().is_some()); + let error = OptBacktraceFrom::from(Inner); assert!(error.backtrace().is_some());