Skip to content

Commit

Permalink
Merge pull request #182 from dtolnay/provider
Browse files Browse the repository at this point in the history
Expose backtrace via generic member access
  • Loading branch information
dtolnay committed Aug 31, 2022
2 parents 034c6ec + e11c97b commit 905680e
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 59 deletions.
94 changes: 59 additions & 35 deletions impl/src/expand.rs
Expand Up @@ -59,45 +59,53 @@ fn impl_struct(input: Struct) -> TokenStream {
}
});

let backtrace_method = input.backtrace_field().map(|backtrace_field| {
let provide_method = input.backtrace_field().map(|backtrace_field| {
let demand = quote!(demand);
let backtrace = &backtrace_field.member;
let body = if let Some(source_field) = input.source_field() {
let source = &source_field.member;
let source_backtrace = if type_is_option(source_field.ty) {
let source_provide = if type_is_option(source_field.ty) {
quote_spanned! {source.span()=>
self.#source.as_ref().and_then(|source| source.as_dyn_error().backtrace())
if let std::option::Option::Some(source) = &self.#source {
source.as_dyn_error().provide(#demand);
}
}
} else {
quote_spanned! {source.span()=>
self.#source.as_dyn_error().backtrace()
self.#source.as_dyn_error().provide(#demand);
}
};
let combinator = if source == backtrace {
source_backtrace
let self_provide = if source == backtrace {
None
} else if type_is_option(backtrace_field.ty) {
quote! {
#source_backtrace.or(self.#backtrace.as_ref())
}
Some(quote! {
if let std::option::Option::Some(backtrace) = &self.#backtrace {
#demand.provide_ref::<std::backtrace::Backtrace>(backtrace);
}
})
} else {
quote! {
std::option::Option::Some(#source_backtrace.unwrap_or(&self.#backtrace))
}
Some(quote! {
#demand.provide_ref::<std::backtrace::Backtrace>(&self.#backtrace);
})
};
quote! {
use thiserror::__private::AsDynError;
#combinator
#source_provide
#self_provide
}
} else if type_is_option(backtrace_field.ty) {
quote! {
self.#backtrace.as_ref()
if let std::option::Option::Some(backtrace) = &self.#backtrace {
#demand.provide_ref::<std::backtrace::Backtrace>(backtrace);
}
}
} else {
quote! {
std::option::Option::Some(&self.#backtrace)
#demand.provide_ref::<std::backtrace::Backtrace>(&self.#backtrace);
}
};
quote! {
fn backtrace(&self) -> std::option::Option<&std::backtrace::Backtrace> {
fn provide<'_demand>(&'_demand self, #demand: &mut std::any::Demand<'_demand>) {
#body
}
}
Expand Down Expand Up @@ -177,7 +185,7 @@ fn impl_struct(input: Struct) -> TokenStream {
#[allow(unused_qualifications)]
impl #impl_generics #error_trait for #ty #ty_generics #error_where_clause {
#source_method
#backtrace_method
#provide_method
}
#display_impl
#from_impl
Expand Down Expand Up @@ -237,7 +245,8 @@ fn impl_enum(input: Enum) -> TokenStream {
None
};

let backtrace_method = if input.has_backtrace() {
let provide_method = if input.has_backtrace() {
let demand = quote!(demand);
let arms = input.variants.iter().map(|variant| {
let ident = &variant.ident;
match (variant.backtrace_field(), variant.source_field()) {
Expand All @@ -247,22 +256,26 @@ fn impl_enum(input: Enum) -> TokenStream {
let backtrace = &backtrace_field.member;
let source = &source_field.member;
let varsource = quote!(source);
let source_backtrace = if type_is_option(source_field.ty) {
let source_provide = if type_is_option(source_field.ty) {
quote_spanned! {source.span()=>
#varsource.as_ref().and_then(|source| source.as_dyn_error().backtrace())
if let std::option::Option::Some(source) = #varsource {
source.as_dyn_error().provide(#demand);
}
}
} else {
quote_spanned! {source.span()=>
#varsource.as_dyn_error().backtrace()
#varsource.as_dyn_error().provide(#demand);
}
};
let combinator = if type_is_option(backtrace_field.ty) {
let self_provide = if type_is_option(backtrace_field.ty) {
quote! {
#source_backtrace.or(backtrace.as_ref())
if let std::option::Option::Some(backtrace) = backtrace {
#demand.provide_ref::<std::backtrace::Backtrace>(backtrace);
}
}
} else {
quote! {
std::option::Option::Some(#source_backtrace.unwrap_or(backtrace))
#demand.provide_ref::<std::backtrace::Backtrace>(backtrace);
}
};
quote! {
Expand All @@ -272,7 +285,8 @@ fn impl_enum(input: Enum) -> TokenStream {
..
} => {
use thiserror::__private::AsDynError;
#combinator
#source_provide
#self_provide
}
}
}
Expand All @@ -281,40 +295,50 @@ fn impl_enum(input: Enum) -> TokenStream {
{
let backtrace = &backtrace_field.member;
let varsource = quote!(source);
let source_backtrace = if type_is_option(source_field.ty) {
let source_provide = if type_is_option(source_field.ty) {
quote_spanned! {backtrace.span()=>
#varsource.as_ref().and_then(|source| source.as_dyn_error().backtrace())
if let std::option::Option::Some(source) = #varsource {
source.as_dyn_error().provide(#demand);
}
}
} else {
quote_spanned! {backtrace.span()=>
#varsource.as_dyn_error().backtrace()
#varsource.as_dyn_error().provide(#demand);
}
};
quote! {
#ty::#ident {#backtrace: #varsource, ..} => {
use thiserror::__private::AsDynError;
#source_backtrace
#source_provide
}
}
}
(Some(backtrace_field), _) => {
let backtrace = &backtrace_field.member;
let body = if type_is_option(backtrace_field.ty) {
quote!(backtrace.as_ref())
quote! {
if let std::option::Option::Some(backtrace) = backtrace {
#demand.provide_ref::<std::backtrace::Backtrace>(backtrace);
}
}
} else {
quote!(std::option::Option::Some(backtrace))
quote! {
#demand.provide_ref::<std::backtrace::Backtrace>(backtrace);
}
};
quote! {
#ty::#ident {#backtrace: backtrace, ..} => #body,
#ty::#ident {#backtrace: backtrace, ..} => {
#body
}
}
}
(None, _) => quote! {
#ty::#ident {..} => std::option::Option::None,
#ty::#ident {..} => {}
},
}
});
Some(quote! {
fn backtrace(&self) -> std::option::Option<&std::backtrace::Backtrace> {
fn provide<'_demand>(&'_demand self, #demand: &mut std::any::Demand<'_demand>) {
#[allow(deprecated)]
match self {
#(#arms)*
Expand Down Expand Up @@ -420,7 +444,7 @@ fn impl_enum(input: Enum) -> TokenStream {
#[allow(unused_qualifications)]
impl #impl_generics #error_trait for #ty #ty_generics #error_where_clause {
#source_method
#backtrace_method
#provide_method
}
#display_impl
#(#from_impls)*
Expand Down
43 changes: 22 additions & 21 deletions tests/test_backtrace.rs
@@ -1,6 +1,7 @@
// FIXME: needs to be updated to provide_ref/request_ref API.
#![cfg(any())]
#![cfg_attr(thiserror_nightly_testing, feature(backtrace))]
#![cfg_attr(
thiserror_nightly_testing,
feature(error_generic_member_access, provide_any)
)]

use thiserror::Error;

Expand All @@ -18,8 +19,8 @@ pub struct InnerBacktrace {
#[cfg(thiserror_nightly_testing)]
pub mod structs {
use super::{Inner, InnerBacktrace};
use std::any;
use std::backtrace::Backtrace;
use std::error::Error;
use std::sync::Arc;
use thiserror::Error;

Expand Down Expand Up @@ -90,44 +91,44 @@ pub mod structs {
let error = PlainBacktrace {
backtrace: Backtrace::capture(),
};
assert!(error.backtrace().is_some());
assert!(any::request_ref::<Backtrace>(&error).is_some());

let error = ExplicitBacktrace {
backtrace: Backtrace::capture(),
};
assert!(error.backtrace().is_some());
assert!(any::request_ref::<Backtrace>(&error).is_some());

let error = OptBacktrace {
backtrace: Some(Backtrace::capture()),
};
assert!(error.backtrace().is_some());
assert!(any::request_ref::<Backtrace>(&error).is_some());

let error = ArcBacktrace {
backtrace: Arc::new(Backtrace::capture()),
};
assert!(error.backtrace().is_some());
assert!(any::request_ref::<Backtrace>(&error).is_some());

let error = BacktraceFrom::from(Inner);
assert!(error.backtrace().is_some());
assert!(any::request_ref::<Backtrace>(&error).is_some());

let error = CombinedBacktraceFrom::from(InnerBacktrace {
backtrace: Backtrace::capture(),
});
assert!(error.backtrace().is_some());
assert!(any::request_ref::<Backtrace>(&error).is_some());

let error = OptBacktraceFrom::from(Inner);
assert!(error.backtrace().is_some());
assert!(any::request_ref::<Backtrace>(&error).is_some());

let error = ArcBacktraceFrom::from(Inner);
assert!(error.backtrace().is_some());
assert!(any::request_ref::<Backtrace>(&error).is_some());
}
}

#[cfg(thiserror_nightly_testing)]
pub mod enums {
use super::{Inner, InnerBacktrace};
use std::any;
use std::backtrace::Backtrace;
use std::error::Error;
use std::sync::Arc;
use thiserror::Error;

Expand Down Expand Up @@ -212,36 +213,36 @@ pub mod enums {
let error = PlainBacktrace::Test {
backtrace: Backtrace::capture(),
};
assert!(error.backtrace().is_some());
assert!(any::request_ref::<Backtrace>(&error).is_some());

let error = ExplicitBacktrace::Test {
backtrace: Backtrace::capture(),
};
assert!(error.backtrace().is_some());
assert!(any::request_ref::<Backtrace>(&error).is_some());

let error = OptBacktrace::Test {
backtrace: Some(Backtrace::capture()),
};
assert!(error.backtrace().is_some());
assert!(any::request_ref::<Backtrace>(&error).is_some());

let error = ArcBacktrace::Test {
backtrace: Arc::new(Backtrace::capture()),
};
assert!(error.backtrace().is_some());
assert!(any::request_ref::<Backtrace>(&error).is_some());

let error = BacktraceFrom::from(Inner);
assert!(error.backtrace().is_some());
assert!(any::request_ref::<Backtrace>(&error).is_some());

let error = CombinedBacktraceFrom::from(InnerBacktrace {
backtrace: Backtrace::capture(),
});
assert!(error.backtrace().is_some());
assert!(any::request_ref::<Backtrace>(&error).is_some());

let error = OptBacktraceFrom::from(Inner);
assert!(error.backtrace().is_some());
assert!(any::request_ref::<Backtrace>(&error).is_some());

let error = ArcBacktraceFrom::from(Inner);
assert!(error.backtrace().is_some());
assert!(any::request_ref::<Backtrace>(&error).is_some());
}
}

Expand Down
7 changes: 4 additions & 3 deletions tests/test_option.rs
@@ -1,6 +1,7 @@
// FIXME: needs to be updated to provide_ref/request_ref API.
#![cfg(any())]
#![cfg_attr(thiserror_nightly_testing, feature(backtrace))]
#![cfg_attr(
thiserror_nightly_testing,
feature(error_generic_member_access, provide_any)
)]

#[cfg(thiserror_nightly_testing)]
pub mod structs {
Expand Down

0 comments on commit 905680e

Please sign in to comment.