diff --git a/src/context.rs b/src/context.rs index 14f308a..41c08bf 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,19 +1,40 @@ use std::borrow::Cow; +use std::fmt; + /// Provides context furthering the explanation of *how* you got to an error. #[derive(Debug, Clone)] pub struct Context(ContextInner); -impl std::fmt::Display for Context { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.0 { - ContextInner::String(ref s) => f.write_str(s), - ContextInner::Compound(ref ctxs) => { - let mut ctxs = ctxs.iter(); - if let Some(ctx) = ctxs.next() { - write!(f, "{ctx}")?; - for ctx in ctxs { - write!(f, "\n{ctx}")?; +impl fmt::Display for Context { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +#[derive(Debug, Clone)] +enum ContextInner { + Elem(ContextElem), + Compound(Vec), +} + +#[derive(Debug, Clone)] +enum ContextElem { + Str(&'static str), + String(String), +} + +impl fmt::Display for ContextInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Elem(elem) => fmt::Display::fmt(elem, f), + Self::Compound(elems) => { + let mut elems = elems.iter(); + if let Some(elem) = elems.next() { + fmt::Display::fmt(elem, f)?; + for elem in elems { + write!(f, "\n{elem}")?; } } Ok(()) @@ -22,10 +43,13 @@ impl std::fmt::Display for Context { } } -#[derive(Debug, Clone)] -enum ContextInner { - String(Cow<'static, str>), - Compound(Vec), +impl fmt::Display for ContextElem { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Str(s) => f.write_str(s), + Self::String(s) => f.write_str(s), + } + } } pub trait IntoContext { @@ -49,34 +73,48 @@ impl IntoContext for Context { /// Chains another piece of context that is equal from a hierarchical perspective. #[inline] fn chain(self, other: impl IntoContext) -> Self { - Context(ContextInner::Compound(match self { - Context(ContextInner::Compound(mut items)) => { - items.push(other.into_context()); - items + Context(ContextInner::Compound(match self.0 { + ContextInner::Compound(mut elems) => { + match other.into_context().0 { + ContextInner::Elem(elem) => elems.push(elem), + ContextInner::Compound(mut elems1) => elems.append(&mut elems1), + }; + elems } - _ => vec![self, other.into_context()], + ContextInner::Elem(elem) => { + match other.into_context().0 { + ContextInner::Elem(elem1) => vec![elem, elem1], + ContextInner::Compound(mut elems) => { + elems.insert(0, elem); + elems + } + } + }, })) } } impl IntoContext for String { - #[inline] + #[inline(always)] fn into_context(self) -> Context { - Context(ContextInner::String(self.into())) + Context(ContextInner::Elem(ContextElem::String(self))) } } impl IntoContext for &'static str { - #[inline] + #[inline(always)] fn into_context(self) -> Context { - Context(ContextInner::String(self.into())) + Context(ContextInner::Elem(ContextElem::Str(self))) } } impl IntoContext for Cow<'static, str> { - #[inline(always)] + #[inline] fn into_context(self) -> Context { - Context(ContextInner::String(self)) + match self { + Cow::Borrowed(s) => s.into_context(), + Cow::Owned(s) => s.into_context(), + } } } diff --git a/src/explain.rs b/src/explain.rs index 4d31d79..b9cdc4c 100644 --- a/src/explain.rs +++ b/src/explain.rs @@ -32,6 +32,8 @@ where #[inline(always)] fn context(self, context: impl IntoContext) -> Self::Output { + // TODO: maybe add a toggle for the extra "Option::None" context + //Err(How::new(context)) self.into_result_how().map_err(#[inline(never)] move |e| e.context(context)) - } } + } diff --git a/src/how.rs b/src/how.rs index 33ebd80..14108ec 100644 --- a/src/how.rs +++ b/src/how.rs @@ -15,18 +15,6 @@ struct HowInner { 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)] @@ -65,6 +53,16 @@ impl How { pub const fn is_classified(&self) -> bool { self.0.classified } + + fn fmt_debug_alternate(&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 explain::Sealed for How {} @@ -99,3 +97,14 @@ impl std::fmt::Display for How { Ok(()) } } + +impl std::fmt::Debug for How { + #[inline(always)] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if f.alternate() { + self.fmt_debug_alternate(f) + } else { + std::fmt::Display::fmt(self, f) + } + } +} diff --git a/src/into.rs b/src/into.rs index e01f6d5..8899f8b 100644 --- a/src/into.rs +++ b/src/into.rs @@ -45,7 +45,7 @@ impl IntoResultHow for Option { fn into_result_how(self) -> Result { #[inline(never)] fn into() -> How { - How::new("None") + How::new("Option::None") } self.ok_or_else(into) } diff --git a/src/lib.rs b/src/lib.rs index b812f33..a6b7fb2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![doc = include_str!("../README.md")] #![forbid(unsafe_code)] +#![feature(backtrace)] mod sealed; pub(crate) use sealed::seal;