use core::panic::Location; #[cfg(feature = "backtrace")] use std::backtrace::BacktraceStatus; use crate::*; use crate::report::report_write; /// The error type. /// /// By default, does not implement [`Clone`] because [`std::backtrace::Backtrace`] does not /// implement [`Clone`]. However, the `clone-with-caveats` feature may be used to enable a /// [`Clone`] impl that sets the cloned `backtrace` to [`std::backtrace::Backtrace::disabled`]. pub struct How(Box); struct HowInner { location: &'static Location<'static>, #[cfg(feature = "backtrace")] backtrace: Backtrace, // TODO: consider storing this vec inline (sharing the allocation with rest of the struct. // Probably move after `backtrace`) context: Vec, } // will be replaced with std::backtrace::Backtrace if and when it is Clone #[cfg(feature = "backtrace")] #[derive(Debug)] enum Backtrace { Disabled, Unsupported, Other(BacktraceStatus, String), } impl How { #[must_use] #[inline(never)] #[track_caller] pub fn new(context: impl IntoContext) -> Self { let location = Location::caller(); Self(Box::new(HowInner { location, context: Vec::with_capacity(4), #[cfg(feature = "backtrace")] backtrace: { let bt = std::backtrace::Backtrace::capture(); match bt.status() { BacktraceStatus::Disabled => Backtrace::Disabled, BacktraceStatus::Unsupported => Backtrace::Unsupported, status => Backtrace::Other(status, bt.to_string()), } }, })) .context(context) } #[must_use] pub fn clone_without_backtrace(&self) -> Self { Self(Box::new(HowInner { location: self.0.location, context: self.0.context.clone(), #[cfg(feature = "backtrace")] backtrace: Backtrace::Disabled, })) } pub fn location(&self) -> &'static Location { self.0.location } pub fn top(&self) -> &Context { // SAFETY: we only ever push values into context, and the constructor ensures that there // is at least 1 value in context. let o = self.0.context.iter().next(); if cfg!(debug_assertions) { o.unwrap() } else { #[allow(unsafe_code)] unsafe { o.unwrap_unchecked() } } } pub fn bottom(&self) -> &Context { // SAFETY: we only ever push values into context, and the constructor ensures that there // is at least 1 value in context. let o = self.0.context.iter().next_back(); if cfg!(debug_assertions) { o.unwrap() } else { #[allow(unsafe_code)] unsafe { o.unwrap_unchecked() } } } pub fn into_context(self) -> impl Iterator { self.0.context.into_iter() } fn fmt_debug_alternate(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let mut b = f.debug_struct(std::any::type_name::()); b.field("location", &(&self.0.location)); b.field("context", &(&self.0.context)); #[cfg(feature = "backtrace")] let b = b.field("backtrace", &self.0.backtrace); b.finish() } pub(crate) fn push_context(&mut self, context: Context) { self.0.context.push(context); } } #[cfg(feature = "clone-with-caveats")] impl Clone for How { #[inline] fn clone(&self) -> Self { self.clone_without_backtrace() } fn clone_from(&mut self, source: &Self) { self.0.location = source.0.location; self.0.context.clone_from(&source.0.context); #[cfg(feature = "backtrace")] { self.0.backtrace = Backtrace::Disabled; } } } impl std::error::Error for How {} impl std::fmt::Display for How { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let opts = ReportOpts::default(); for (i, ctx) in self.0.context.iter().enumerate() { if i != 0 { f.write_str("\n")?; } report_write!(f, &opts.indent().next(), "{ctx}")?; } #[cfg(feature = "backtrace")] { use std::backtrace::BacktraceStatus::*; let bt = &self.0.backtrace; match bt { Backtrace::Unsupported => f.write_str("\nI'd like to show you a backtrace,\n but it's not supported on your platform")?, Backtrace::Disabled => f.write_str("\nIf you'd like a backtrace,\n try again with RUST_BACKTRACE=1")?, Backtrace::Other(status, bt) => { f.write_str(if *status == Captured { "\nHere is the backtrace:" } else { "\nI can't tell if backtraces are working,\n but I'll give it a go:" })?; report_write!(f, &opts, "\n{}", bt)?; } } } 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) } } }