From 8ac5835d05ec21b605fcc02d86ab9547a439aed0 Mon Sep 17 00:00:00 2001 From: Bugen Zhao Date: Tue, 27 Feb 2024 18:25:41 +0800 Subject: [PATCH 1/4] initial impl Signed-off-by: Bugen Zhao --- derive/src/expand.rs | 15 +++++++++++++++ derive/src/lib.rs | 9 +++++++++ tests/report_debug.rs | 19 +++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 tests/report_debug.rs diff --git a/derive/src/expand.rs b/derive/src/expand.rs index e352190..d0c7158 100644 --- a/derive/src/expand.rs +++ b/derive/src/expand.rs @@ -694,6 +694,21 @@ pub fn derive_macro(input: &DeriveInput) -> Result { Ok(generated) } +pub fn derive_report_debug(input: &DeriveInput) -> Result { + let input_type = input.ident.clone(); + + let generated = quote!( + impl ::std::fmt::Debug for #input_type { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + use ::thiserror_ext::AsReport; + write!(f, "{:#}", self.as_report()) + } + } + ); + + Ok(generated) +} + fn big_camel_case_to_snake_case(input: &str) -> String { let mut output = String::new(); diff --git a/derive/src/lib.rs b/derive/src/lib.rs index da210f2..1577232 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -298,3 +298,12 @@ pub fn derive_arc(input: TokenStream) -> TokenStream { .unwrap_or_else(|err| err.to_compile_error()) .into() } + +#[proc_macro_derive(ReportDebug)] +pub fn derive_report_debug(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + expand::derive_report_debug(&input) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} diff --git a/tests/report_debug.rs b/tests/report_debug.rs new file mode 100644 index 0000000..351ea02 --- /dev/null +++ b/tests/report_debug.rs @@ -0,0 +1,19 @@ +use thiserror::Error; +use thiserror_ext::ReportDebug; + +#[derive(Error, ReportDebug, Default)] +#[error("inner")] +struct Inner; + +#[derive(Error, ReportDebug, Default)] +#[error("outer")] +struct Outer { + #[source] + inner: Inner, +} + +#[test] +#[should_panic] +fn test() { + Err::<(), _>(Outer::default()).unwrap(); +} From c0248a8ca816f8c3a5e66006501f8b3c211b4479 Mon Sep 17 00:00:00 2001 From: Bugen Zhao Date: Wed, 6 Mar 2024 16:29:40 +0800 Subject: [PATCH 2/4] update test Signed-off-by: Bugen Zhao --- tests/report_debug.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/report_debug.rs b/tests/report_debug.rs index 351ea02..a71cbc0 100644 --- a/tests/report_debug.rs +++ b/tests/report_debug.rs @@ -13,7 +13,13 @@ struct Outer { } #[test] -#[should_panic] -fn test() { - Err::<(), _>(Outer::default()).unwrap(); +fn test_report_debug() { + let debug = format!("{:?}", Outer::default()); + expect_test::expect![[r#" + outer + + Caused by: + inner + "#]] + .assert_eq(&debug); } From 9186e58e6079e50d2e437571229d2700fb17d41a Mon Sep 17 00:00:00 2001 From: Bugen Zhao Date: Wed, 6 Mar 2024 16:45:20 +0800 Subject: [PATCH 3/4] refine doc Signed-off-by: Bugen Zhao --- derive/src/lib.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 1577232..f9c82cd 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -299,6 +299,41 @@ pub fn derive_arc(input: TokenStream) -> TokenStream { .into() } +/// Generates the [`Debug`] implementation that delegates to the [`Report`] of +/// an error. +/// +/// Generally, the [`Debug`] representation of an error should not be used in +/// user-facing scenarios. However, if [`Result::unwrap`] or [`Result::expect`] +/// is called, or an error is used as [`Termination`], the standard library +/// will format the error with [`Debug`]. By delegating to [`Report`], we ensure +/// that the error is still formatted in a user-friendly way and the source +/// chain can be kept in these cases. +/// +/// # Example +/// ```no_run +/// #[derive(thiserror::Error, thiserror_ext::ReportDebug)] +/// #[error("inner")] +/// struct Inner; +/// +/// #[derive(thiserror::Error, thiserror_ext::ReportDebug)] +/// #[error("outer")] +/// struct Outer { +/// #[source] +/// inner: Inner, +/// } +/// +/// let error = Outer { inner: Inner }; +/// println!("{:?}", error); +/// ``` +/// +/// [`Report`]: thiserror_ext::Report +/// [`Termination`]: std::process::Termination +/// +/// # New type +/// +/// Since the new type delegates its [`Debug`] implementation to the original +/// error type, if the original error type derives [`ReportDebug`], the new type +/// will also behave the same. #[proc_macro_derive(ReportDebug)] pub fn derive_report_debug(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); From a1bbbea964f1e4f1a0d2093570e5a8590109301e Mon Sep 17 00:00:00 2001 From: Bugen Zhao Date: Wed, 6 Mar 2024 16:55:45 +0800 Subject: [PATCH 4/4] use debug impl and update test Signed-off-by: Bugen Zhao --- derive/src/expand.rs | 5 ++++- derive/src/lib.rs | 2 ++ tests/report_debug.rs | 29 +++++++++++++++++++++++------ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/derive/src/expand.rs b/derive/src/expand.rs index d0c7158..c8fb767 100644 --- a/derive/src/expand.rs +++ b/derive/src/expand.rs @@ -697,11 +697,14 @@ pub fn derive_macro(input: &DeriveInput) -> Result { pub fn derive_report_debug(input: &DeriveInput) -> Result { let input_type = input.ident.clone(); + // 1. Delegate to `Debug` impl as the backtrace provided by the error + // could be different than where panic happens. + // 2. Passthrough the `alternate` flag. let generated = quote!( impl ::std::fmt::Debug for #input_type { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { use ::thiserror_ext::AsReport; - write!(f, "{:#}", self.as_report()) + ::std::fmt::Debug::fmt(&self.as_report(), f) } } ); diff --git a/derive/src/lib.rs b/derive/src/lib.rs index f9c82cd..a7432f7 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(rustdoc::broken_intra_doc_links)] + //! Procedural macros for `thiserror_ext`. use expand::{DeriveCtorType, DeriveNewType}; diff --git a/tests/report_debug.rs b/tests/report_debug.rs index a71cbc0..0ef20ef 100644 --- a/tests/report_debug.rs +++ b/tests/report_debug.rs @@ -14,12 +14,29 @@ struct Outer { #[test] fn test_report_debug() { - let debug = format!("{:?}", Outer::default()); + let error = Outer::default(); + + expect_test::expect!["outer: inner"].assert_eq(&format!("{:?}", error)); + expect_test::expect![[r#" - outer + outer + + Caused by: + inner +"#]] + .assert_eq(&format!("{:#?}", error)); +} - Caused by: - inner - "#]] - .assert_eq(&debug); +#[test] +#[should_panic] +fn test_unwrap() { + let error = Outer::default(); + let _ = Err::<(), _>(error).unwrap(); +} + +#[test] +#[should_panic] +fn test_expect() { + let error = Outer::default(); + let _ = Err::<(), _>(error).expect("intentional panic"); }