From db067003d805fccbd2b59f47ee2a97165c867dc1 Mon Sep 17 00:00:00 2001 From: Michael Pfaff Date: Sat, 1 Jul 2023 12:35:14 -0400 Subject: [PATCH] Expose Detail - Add some accessor methods on `Context` - Wrap some variants in private structs to prevent people from depending on them - Add some #[inline] --- Cargo.toml | 1 + src/context.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++------ src/explain.rs | 20 +++++------------ src/how.rs | 44 ++++++++++++++++++++++++++++++++++++-- src/lib.rs | 3 +-- 5 files changed, 100 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b4867e0..355e5ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [features] default = [] backtrace = [] +extra-backtrace = ["backtrace"] clone-with-caveats = [] termination = ["dep:ansee"] diff --git a/src/context.rs b/src/context.rs index d79641a..8d75dad 100644 --- a/src/context.rs +++ b/src/context.rs @@ -20,6 +20,26 @@ impl Context { pub(crate) fn new(detail: Detail) -> Self { Self { detail, extra: Vec::new() } } + + pub fn detail(&self) -> &Detail { + &self.detail + } + + pub fn extra(&self) -> &[Detail] { + &self.extra + } + + pub fn detail_mut(&mut self) -> &mut Detail { + &mut self.detail + } + + pub fn extra_mut(&mut self) -> &mut [Detail] { + &mut self.extra + } + + pub fn pop_extra(&mut self) -> Option { + self.extra.pop() + } } impl fmt::Display for Context { @@ -37,15 +57,39 @@ impl fmt::Display for Context { #[derive(Debug)] #[cfg_attr(feature = "clone-with-caveats", derive(Clone))] -pub(crate) enum Detail { +#[non_exhaustive] +pub enum Detail { Str(&'static str), String(String), Location(Location<'static>), #[cfg(feature = "backtrace")] - Backtrace(Backtrace), - Error(Arc), + Backtrace(PrivateBacktrace), + Error(PrivateError), } +impl Detail { + #[cfg(feature = "backtrace")] + #[track_caller] + pub fn backtrace() -> Self { + use std::backtrace::BacktraceStatus::*; + let bt = std::backtrace::Backtrace::capture(); + let bt = match bt.status() { + Disabled => Backtrace::Disabled, + Unsupported => Backtrace::Unsupported, + status => Backtrace::Other(status, bt.to_string()), + }; + Self::Backtrace(PrivateBacktrace(bt)) + } +} + +#[derive(Debug, Clone)] +pub struct PrivateError(pub(crate) Arc); + +#[cfg(feature = "backtrace")] +#[derive(Debug)] +#[cfg_attr(feature = "clone-with-caveats", derive(Clone))] +pub struct PrivateBacktrace(pub(crate) Backtrace); + // will be replaced with std::backtrace::Backtrace if and when it is Clone #[cfg(feature = "backtrace")] #[derive(Debug)] @@ -69,11 +113,11 @@ impl fmt::Display for Detail { Self::String(s) => f.write_str(s), Self::Location(l) => write!(f, "At {l}"), #[cfg(feature = "backtrace")] - Self::Backtrace(Backtrace::Unsupported) => f.write_str("I'd like to show you a backtrace,\n but it's not supported on your platform"), + Self::Backtrace(PrivateBacktrace(Backtrace::Unsupported)) => f.write_str("I'd like to show you a backtrace,\n but it's not supported on your platform"), #[cfg(feature = "backtrace")] - Self::Backtrace(Backtrace::Disabled) => f.write_str("If you'd like a backtrace,\n try again with RUST_BACKTRACE=1"), + Self::Backtrace(PrivateBacktrace(Backtrace::Disabled)) => f.write_str("If you'd like a backtrace,\n try again with RUST_BACKTRACE=1"), #[cfg(feature = "backtrace")] - Self::Backtrace(Backtrace::Other(status, bt)) => { + Self::Backtrace(PrivateBacktrace(Backtrace::Other(status, bt))) => { f.write_str(if *status == BacktraceStatus::Captured { "Here is the backtrace:\n" } else { @@ -81,7 +125,7 @@ impl fmt::Display for Detail { })?; write!(f, "{}", bt) }, - Self::Error(e) => e.fmt(f), + Self::Error(PrivateError(e)) => e.fmt(f), } } } diff --git a/src/explain.rs b/src/explain.rs index 3acdeab..ee11195 100644 --- a/src/explain.rs +++ b/src/explain.rs @@ -2,8 +2,7 @@ use std::panic::Location; use std::sync::Arc; use crate::{How, IntoContext, Context, Detail}; -#[cfg(feature = "backtrace")] -use crate::context::Backtrace; +use crate::context::PrivateError; pub trait Explain { type Output; @@ -21,23 +20,14 @@ impl Explain for How { fn context(mut self, context: impl IntoContext) -> Self { let mut context = context.into_context(); - context.extra.reserve(if cfg!(feature = "backtrace") { + context.extra.reserve(if cfg!(feature = "extra-backtrace") { 2 } else { 1 }); context.extra.push(Detail::Location(*Location::caller())); - #[cfg(feature = "backtrace")] - context.extra.push({ - use std::backtrace::BacktraceStatus::*; - let bt = std::backtrace::Backtrace::capture(); - let bt = match bt.status() { - Disabled => Backtrace::Disabled, - Unsupported => Backtrace::Unsupported, - status => Backtrace::Other(status, bt.to_string()), - }; - Detail::Backtrace(bt) - }); + #[cfg(feature = "extra-backtrace")] + context.extra.push(Detail::backtrace()); self.push_context(context); self } @@ -83,7 +73,7 @@ where #[track_caller] fn context(self, context: impl IntoContext) -> Self::Output { - How::new(Context::new(Detail::Error(self))) + How::new(Context::new(Detail::Error(PrivateError(self)))) .context(context) } } diff --git a/src/how.rs b/src/how.rs index e49b6a7..f0febfc 100644 --- a/src/how.rs +++ b/src/how.rs @@ -25,17 +25,23 @@ impl How { #[track_caller] pub fn new(context: impl IntoContext) -> Self { let location = Location::caller(); - Self(Box::new(HowInner { + #[allow(unused_mut)] + let mut how = Self(Box::new(HowInner { location, context: Vec::with_capacity(4), })) - .context(context) + .context(context); + #[cfg(all(feature = "backtrace", not(feature = "extra-backtrace")))] + how.top_mut().extra.push(Detail::backtrace()); + how } + #[inline] pub fn location(&self) -> &'static Location { self.0.location } + #[inline] 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. @@ -50,6 +56,7 @@ impl How { } } + #[inline] 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. @@ -64,6 +71,37 @@ impl How { } } + #[inline] + pub fn top_mut(&mut self) -> &mut 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_mut().next(); + if cfg!(debug_assertions) { + o.unwrap() + } else { + #[allow(unsafe_code)] + unsafe { + o.unwrap_unchecked() + } + } + } + + #[inline] + pub fn bottom_mut(&mut self) -> &mut 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_mut().next_back(); + if cfg!(debug_assertions) { + o.unwrap() + } else { + #[allow(unsafe_code)] + unsafe { + o.unwrap_unchecked() + } + } + } + + #[inline] pub fn into_context(self) -> impl Iterator { self.0.context.into_iter() } @@ -75,6 +113,7 @@ impl How { b.finish() } + #[inline] pub(crate) fn push_context(&mut self, context: Context) { self.0.context.push(context); } @@ -90,6 +129,7 @@ impl Clone for HowInner { } } + #[inline] fn clone_from(&mut self, source: &Self) { self.location = source.location; self.context.clone_from(&source.context); diff --git a/src/lib.rs b/src/lib.rs index bdc4cfb..7c1d31a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,8 +3,7 @@ #![feature(doc_auto_cfg)] mod context; -pub(crate) use context::Detail; -pub use context::{Context, IntoContext}; +pub use context::{Context, Detail, IntoContext}; mod report; pub use report::{Report, ReportOpts};