Skip to content

Commit

Permalink
Merge pull request #146 from dtolnay/backtrace
Browse files Browse the repository at this point in the history
Fix up PR 137 for merge
  • Loading branch information
dtolnay committed Aug 28, 2021
2 parents 031fea6 + 71c7ebe commit 2e2c126
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 5 deletions.
14 changes: 14 additions & 0 deletions README.md
Expand Up @@ -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.
Expand Down
29 changes: 26 additions & 3 deletions impl/src/expand.rs
Expand Up @@ -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())
}
Expand Down Expand Up @@ -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! {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
24 changes: 24 additions & 0 deletions impl/src/prop.rs
Expand Up @@ -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<'_> {
Expand Down Expand Up @@ -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<'_> {
Expand Down Expand Up @@ -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,
Expand Down
16 changes: 16 additions & 0 deletions src/lib.rs
Expand Up @@ -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
Expand Down
39 changes: 37 additions & 2 deletions tests/test_backtrace.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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());

Expand All @@ -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;
Expand Down Expand Up @@ -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("...")]
Expand Down Expand Up @@ -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());

Expand Down

0 comments on commit 2e2c126

Please sign in to comment.