diff --git a/src/explain.rs b/src/explain.rs index 33632ef..4d31d79 100644 --- a/src/explain.rs +++ b/src/explain.rs @@ -10,6 +10,7 @@ pub trait Explain: Sealed { } impl Sealed for Result where Result: IntoResultHow {} +impl Sealed for Option where Option: IntoResultHow {} impl Explain for Result where @@ -19,6 +20,18 @@ where #[inline(always)] fn context(self, context: impl IntoContext) -> Self::Output { - self.into_result_how().map_err(|e| e.context(context)) + self.into_result_how().map_err(#[inline(never)] move |e| e.context(context)) + } +} + +impl Explain for Option +where + Option: IntoResultHow, +{ + type Output = Result<::T, How>; + + #[inline(always)] + fn context(self, context: impl IntoContext) -> Self::Output { + self.into_result_how().map_err(#[inline(never)] move |e| e.context(context)) } } diff --git a/src/how.rs b/src/how.rs new file mode 100644 index 0000000..6dbbd4d --- /dev/null +++ b/src/how.rs @@ -0,0 +1,101 @@ +use crate::*; + +use crate::report::report_write; + +/// Does not implement [`std::error::Error`] to allow a [`From`] implementation for all other error types. +pub struct How(Box); + +struct HowInner { + /// When true, the error will cause branchers to abort. + classified: bool, + #[cfg(feature = "backtrace")] + backtrace: std::backtrace::Backtrace, + // TODO: consider storing this vec inline (sharing the allocation with rest of the struct. + // Probably move after `backtrace`) + context: Vec, +} + +impl std::fmt::Debug for How { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut b = f.debug_struct(std::any::type_name::()); + let b = b + .field("classified", &self.0.classified) + .field("context", &(&self.0.context)); + #[cfg(feature = "backtrace")] + let b = b.field("backtrace", &self.0.backtrace); + b.finish() + } +} + +impl How { + #[must_use] + #[inline(never)] + pub fn new(context: impl IntoContext) -> Self { + Self(Box::new(HowInner { + classified: false, + context: { + let mut vec = Vec::with_capacity(4); + vec[0] = context.into_context(); + vec + }, + #[cfg(feature = "backtrace")] + backtrace: std::backtrace::Backtrace::capture(), + })) + } + + #[must_use] + pub fn clone_without_backtrace(&self) -> Self { + Self(Box::new(HowInner { + classified: self.0.classified, + context: self.0.context.clone(), + #[cfg(feature = "backtrace")] + backtrace: std::backtrace::Backtrace::disabled(), + })) + } + + #[inline] + #[must_use] + pub fn classified(mut self) -> Self { + self.0.classified = true; + self + } + + #[inline] + #[must_use] + pub const fn is_classified(&self) -> bool { + self.0.classified + } +} + +impl explain::Sealed for How {} + +impl Explain for How { + type Output = Self; + + #[inline(always)] + #[must_use] + fn context(mut self, context: impl IntoContext) -> Self { + self.0.context.push(context.into_context()); + self + } +} + +impl std::fmt::Display for How { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut opts = ReportOpts::default(); + let mut ctxs = self.0.context.iter().rev(); + let ctx = ctxs.next().expect("`How` created with no context."); + report_write!(f, &opts, "{ctx}")?; + for ctx in ctxs { + report_write!(f, &opts, "\n└ ")?; + report_write!(f, &opts.indent().next(), "{ctx}")?; + opts = opts.indent(); + } + #[cfg(feature = "backtrace")] + { + opts = opts.indent(); + report_write!(f, &opts, "\n{}", self.0.backtrace)?; + } + Ok(()) + } +} diff --git a/src/into.rs b/src/into.rs index d10e564..e01f6d5 100644 --- a/src/into.rs +++ b/src/into.rs @@ -19,9 +19,13 @@ where { type T = T; - #[inline] + #[inline(always)] fn into_result_how(self) -> Result { - self.map_err(|e| How::new(e.to_string())) + #[inline(never)] + fn into(e: E) -> How { + How::new(e.to_string()) + } + self.map_err(into) } } @@ -33,3 +37,16 @@ impl IntoResultHow for Result { self } } + +impl IntoResultHow for Option { + type T = T; + + #[inline(always)] + fn into_result_how(self) -> Result { + #[inline(never)] + fn into() -> How { + How::new("None") + } + self.ok_or_else(into) + } +} diff --git a/src/lib.rs b/src/lib.rs index 3ec808f..25a524d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,6 @@ mod context; pub use context::{Context, IntoContext}; mod report; -use report::report_write; pub use report::{Report, ReportOpts}; mod into; @@ -22,101 +21,7 @@ mod termination; #[cfg(feature = "termination")] pub use termination::TerminationResult; +mod how; +pub use how::How; + pub type Result = std::result::Result; - -/// Does not implement [`std::error::Error`] to allow a [`From`] implementation for all other error types. -pub struct How(Box); - -struct HowInner { - /// When true, the error will cause branchers to abort. - classified: bool, - // TODO: consider storing this vec inline (sharing the allocation with rest of the struct. - // Probably move after `backtrace`) - context: Vec, - #[cfg(feature = "backtrace")] - backtrace: std::backtrace::Backtrace, -} - -impl std::fmt::Debug for How { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut b = f.debug_struct(std::any::type_name::()); - let b = b - .field("classified", &self.0.classified) - .field("context", &self.0.context); - #[cfg(feature = "backtrace")] - let b = b.field("backtrace", &self.0.backtrace); - b.finish() - } -} - -impl How { - #[must_use] - pub fn new(context: impl IntoContext) -> Self { - Self(Box::new(HowInner { - classified: false, - context: { - let mut vec = Vec::with_capacity(2); - vec.push(context.into_context()); - vec - }, - #[cfg(feature = "backtrace")] - backtrace: std::backtrace::Backtrace::capture(), - })) - } - - #[must_use] - pub fn clone_without_backtrace(&self) -> Self { - Self(Box::new(HowInner { - classified: self.0.classified, - context: self.0.context.clone(), - #[cfg(feature = "backtrace")] - backtrace: std::backtrace::Backtrace::disabled(), - })) - } - - #[inline] - #[must_use] - pub fn classified(mut self) -> Self { - self.0.classified = true; - self - } - - #[inline] - #[must_use] - pub const fn is_classified(&self) -> bool { - self.0.classified - } -} - -impl explain::Sealed for How {} - -impl Explain for How { - type Output = Self; - - #[inline] - #[must_use] - fn context(mut self, context: impl IntoContext) -> Self { - self.0.context.push(context.into_context()); - self - } -} - -impl std::fmt::Display for How { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut opts = ReportOpts::default(); - let mut ctxs = self.0.context.iter().rev(); - let ctx = ctxs.next().expect("`How` created with no context."); - report_write!(f, &opts, "{ctx}")?; - for ctx in ctxs { - report_write!(f, &opts, "\n└ ")?; - report_write!(f, &opts.indent().next(), "{ctx}")?; - opts = opts.indent(); - } - #[cfg(feature = "backtrace")] - { - opts = opts.indent(); - report_write!(f, &opts, "\n{}", self.0.backtrace)?; - } - Ok(()) - } -}