diff --git a/src/compat/anyhow1.rs b/src/compat/anyhow1.rs index 0b632ff..db09414 100644 --- a/src/compat/anyhow1.rs +++ b/src/compat/anyhow1.rs @@ -199,7 +199,7 @@ impl IntoRootcause for anyhow::Error { #[inline(always)] fn into_rootcause(self) -> Self::Output { - Report::new_custom::(self).into_dynamic() + Report::new_sendsync_custom::(self).into_dynamic() } } diff --git a/src/compat/boxed_error.rs b/src/compat/boxed_error.rs index 00206ca..a319fd9 100644 --- a/src/compat/boxed_error.rs +++ b/src/compat/boxed_error.rs @@ -360,7 +360,7 @@ impl IntoRootcause for Box { #[inline(always)] fn into_rootcause(self) -> Self::Output { - Report::new_custom::(self).into_dynamic() + Report::new_sendsync_custom::(self).into_dynamic() } } @@ -369,7 +369,7 @@ impl IntoRootcause for Box { #[inline(always)] fn into_rootcause(self) -> Self::Output { - Report::new_custom::(self).into_dynamic() + Report::new_local_custom::(self).into_dynamic() } } diff --git a/src/compat/eyre06.rs b/src/compat/eyre06.rs index f9862dd..e4107da 100644 --- a/src/compat/eyre06.rs +++ b/src/compat/eyre06.rs @@ -199,7 +199,7 @@ impl IntoRootcause for eyre::Report { #[inline(always)] fn into_rootcause(self) -> Self::Output { - Report::new_custom::(self).into_dynamic() + Report::new_sendsync_custom::(self).into_dynamic() } } diff --git a/src/report/mut_.rs b/src/report/mut_.rs index f54ff5d..54647dc 100644 --- a/src/report/mut_.rs +++ b/src/report/mut_.rs @@ -1175,6 +1175,13 @@ impl<'a, C: ?Sized, T> core::ops::Deref for ReportMut<'a, C, T> { } } +impl<'a, C: ?Sized, T> AsRef for ReportMut<'a, C, T> { + #[inline(always)] + fn as_ref(&self) -> &(dyn core::error::Error + 'a) { + ErrorNoSourceWrapper::new(self) + } +} + impl<'a, C: ?Sized, T> Unpin for ReportMut<'a, C, T> {} impl<'a, C: Sized> From> for ReportMut<'a, Dynamic, SendSync> { @@ -1191,7 +1198,10 @@ impl<'a, C: Sized> From> for ReportMut<'a, Dynamic, Loca #[cfg(test)] mod tests { - use alloc::string::String; + use alloc::string::{String, ToString}; + use core::error::Error as StdError; + use core::ops::Deref; + use thiserror::Error; use super::*; @@ -1236,4 +1246,35 @@ mod tests { static_assertions::assert_not_impl_any!(ReportMut<'static, Dynamic, SendSync>: Copy, Clone); static_assertions::assert_not_impl_any!(ReportMut<'static, Dynamic, Local>: Copy, Clone); } + + #[derive(Debug, Error)] + #[error("boom")] + struct Boom; + + fn make_report() -> Report { + Report::new(Boom) + } + + #[test] + fn report_mut_derefs_to_dyn_error() { + let mut report = make_report(); + let report_mut = report.as_mut(); + + let err: &dyn StdError = report_mut.deref(); + + assert!(err.to_string().contains("boom")); + assert!(report.source().is_none()); + } + + #[test] + fn report_mut_asrefs_to_dyn_error() { + let mut report = make_report(); + let report_mut = report.as_mut(); + + fn takes_asref<'a>(err: impl AsRef) { + assert!(err.as_ref().to_string().contains("boom")); + } + + takes_asref(report_mut); + } } diff --git a/src/report/owned.rs b/src/report/owned.rs index 7ee3264..5a0dc7c 100644 --- a/src/report/owned.rs +++ b/src/report/owned.rs @@ -1803,14 +1803,36 @@ impl core::fmt::Debug for Report { } } -impl core::ops::Deref for Report { - type Target = dyn core::error::Error; +impl core::ops::Deref for Report { + type Target = dyn core::error::Error + Send + Sync + 'static; fn deref(&self) -> &Self::Target { ErrorNoSourceWrapper::new(self) } } +impl core::ops::Deref for Report { + type Target = dyn core::error::Error + 'static; + + fn deref(&self) -> &Self::Target { + ErrorNoSourceWrapper::new(self) + } +} + +impl AsRef for Report { + #[inline(always)] + fn as_ref(&self) -> &(dyn core::error::Error + Send + Sync + 'static) { + ErrorNoSourceWrapper::new(self) + } +} + +impl AsRef for Report { + #[inline(always)] + fn as_ref(&self) -> &(dyn core::error::Error + 'static) { + ErrorNoSourceWrapper::new(self) + } +} + impl Unpin for Report {} macro_rules! from_impls { @@ -1860,7 +1882,10 @@ from_impls!( #[cfg(test)] mod tests { - use alloc::string::String; + use alloc::string::{String, ToString}; + use core::error::Error as StdError; + use core::ops::Deref; + use thiserror::Error; use super::*; @@ -1939,4 +1964,163 @@ mod tests { static_assertions::assert_not_impl_any!(Report: Copy); static_assertions::assert_not_impl_any!(Report: Copy); } + + #[derive(Debug, Error)] + #[error("boom")] + struct Boom; + + #[derive(Debug, Error)] + enum Outer { + #[error(transparent)] + Inner(#[from] Report), + } + + fn make_report() -> Report { + Report::new(Boom) + } + + fn make_dynamic_report() -> Report { + make_report().into_dynamic() + } + + fn make_cloneable_report() -> Report { + make_report().into_cloneable() + } + + fn make_local_report() -> Report { + make_report().into_local() + } + + #[test] + fn report_derefs_to_dyn_error() { + let report = make_report(); + + let err: &(dyn StdError + Send + Sync) = report.deref(); + + assert!(err.to_string().contains("boom")); + } + + #[test] + fn report_asrefs_to_dyn_error() { + let report = make_report(); + + fn takes_asref<'a>(err: impl AsRef) { + assert!(err.as_ref().to_string().contains("boom")); + } + + takes_asref(&report); + } + + #[test] + fn dynamic_report_derefs_to_dyn_error() { + let report = make_dynamic_report(); + + let err: &dyn StdError = report.deref(); + + assert!(err.to_string().contains("boom")); + } + + #[test] + fn dynamic_report_asrefs_to_dyn_error() { + let report = make_dynamic_report(); + + fn takes_asref<'a>(err: impl AsRef) { + assert!(err.as_ref().to_string().contains("boom")); + } + + takes_asref(&report); + } + + #[test] + fn cloneable_report_derefs_to_dyn_error() { + let report = make_cloneable_report(); + + let err: &dyn StdError = report.deref(); + + assert!(err.to_string().contains("boom")); + } + + #[test] + fn cloneable_report_asrefs_to_dyn_error() { + let report = make_cloneable_report(); + + fn takes_asref<'a>(err: impl AsRef) { + assert!(err.as_ref().to_string().contains("boom")); + } + + takes_asref(&report); + } + + #[test] + fn local_report_derefs_to_dyn_error() { + let report = make_local_report(); + + let err: &dyn StdError = report.deref(); + + assert!(err.to_string().contains("boom")); + } + + #[test] + fn local_report_asrefs_to_dyn_error() { + let report = make_local_report(); + + fn takes_asref<'a>(err: impl AsRef) { + assert!(err.as_ref().to_string().contains("boom")); + } + + takes_asref(&report); + } + + #[test] + fn report_deref_supports_error_methods() { + let report = make_report(); + + assert!(report.source().is_none()); + + #[expect(deprecated)] + { + assert_eq!( + report.description(), + "description() is deprecated; use Display" + ); + } + } + + #[test] + fn thiserror_from_works_for_report() { + let report = make_report(); + + let outer: Outer = report.into(); + + match outer { + Outer::Inner(inner) => { + assert!(inner.to_string().contains("boom")); + } + } + } + + #[test] + fn thiserror_question_mark_works_for_report() { + fn inner() -> Result<(), Report> { + Err(make_report()) + } + + fn outer() -> Result<(), Outer> { + inner()?; + Ok(()) + } + + let err = outer().unwrap_err(); + assert!(err.to_string().contains("boom")); + } + + #[test] + fn report_is_usable_where_dyn_error_is_expected() { + fn takes_error(err: &dyn StdError) -> String { + err.to_string() + } + + let report = make_report(); + assert!(takes_error(report.deref()).contains("boom")); + } } diff --git a/src/report/ref_.rs b/src/report/ref_.rs index 9bface1..afadc96 100644 --- a/src/report/ref_.rs +++ b/src/report/ref_.rs @@ -1036,6 +1036,13 @@ impl<'a, C: ?Sized, O, T> core::ops::Deref for ReportRef<'a, C, O, T> { } } +impl<'a, C: ?Sized, O, T> AsRef for ReportRef<'a, C, O, T> { + #[inline(always)] + fn as_ref(&self) -> &(dyn core::error::Error + 'a) { + ErrorNoSourceWrapper::new(self) + } +} + impl<'a, C: ?Sized, O, T> Unpin for ReportRef<'a, C, O, T> {} macro_rules! from_impls { @@ -1086,7 +1093,10 @@ from_impls!( #[cfg(test)] mod tests { - use alloc::string::String; + use alloc::string::{String, ToString}; + use core::error::Error as StdError; + use core::ops::Deref; + use thiserror::Error; use super::*; use crate::markers::{Mutable, Uncloneable}; @@ -1194,4 +1204,35 @@ mod tests { let preformatted: Report = report_ref.preformat(); assert_eq!(alloc::format!("{report}"), alloc::format!("{preformatted}")); } + + #[derive(Debug, Error)] + #[error("boom")] + struct Boom; + + fn make_report() -> Report { + Report::new(Boom) + } + + #[test] + fn report_ref_derefs_to_dyn_error() { + let report = make_report(); + let report_ref = report.as_ref(); + + let err: &dyn StdError = report_ref.deref(); + + assert!(err.to_string().contains("boom")); + assert!(report.source().is_none()); + } + + #[test] + fn report_ref_asrefs_to_dyn_error() { + let report = make_report(); + let report_ref = report.as_ref(); + + fn takes_asref<'a>(err: impl AsRef) { + assert!(err.as_ref().to_string().contains("boom")); + } + + takes_asref(report_ref); + } }