use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use core::panic::Location;
use crate::AtCrateInfo;
use crate::context::{AtContext, AtContextRef};
type ContextEntry = (u16, AtContext);
pub const AT_MAX_FRAMES: usize = 128;
pub const AT_MAX_CONTEXTS: usize = 128;
type LocationElem = Option<&'static Location<'static>>;
#[cfg(all(
feature = "_tinyvec-64-bytes",
not(any(feature = "_tinyvec-128-bytes", feature = "_tinyvec-256-bytes"))
))]
type LocationVec = crate::inline_vec::InlineVec<LocationElem, 4>;
#[cfg(all(feature = "_tinyvec-128-bytes", not(feature = "_tinyvec-256-bytes")))]
type LocationVec = crate::inline_vec::InlineVec<LocationElem, 12>;
#[cfg(all(feature = "_tinyvec-256-bytes", not(feature = "_tinyvec-512-bytes")))]
type LocationVec = crate::inline_vec::InlineVec<LocationElem, 28>;
#[cfg(all(feature = "_tinyvec-512-bytes", not(feature = "_smallvec-128-bytes")))]
type LocationVec = crate::inline_vec::InlineVec<LocationElem, 60>;
#[cfg(all(feature = "_smallvec-128-bytes", not(feature = "_smallvec-256-bytes")))]
type LocationVec = crate::inline_vec::InlineVec<LocationElem, 12>;
#[cfg(feature = "_smallvec-256-bytes")]
type LocationVec = crate::inline_vec::InlineVec<LocationElem, 28>;
#[cfg(not(any(
feature = "_tinyvec-64-bytes",
feature = "_tinyvec-128-bytes",
feature = "_tinyvec-256-bytes",
feature = "_tinyvec-512-bytes",
feature = "_smallvec-128-bytes",
feature = "_smallvec-256-bytes"
)))]
type LocationVec = crate::inline_vec::InlineVec<LocationElem, 4>;
#[inline]
fn location_vec_new() -> LocationVec {
LocationVec::new()
}
type ContextVec = Option<Box<Vec<ContextEntry>>>;
#[inline]
pub(crate) fn try_box<T>(value: T) -> Option<Box<T>> {
Some(Box::new(value))
}
#[inline]
fn try_push_location(vec: &mut LocationVec, elem: LocationElem) -> bool {
if vec.len() >= AT_MAX_FRAMES {
return false;
}
vec.try_push(elem)
}
#[inline]
fn context_vec_new() -> ContextVec {
None
}
#[inline]
fn try_push_context(vec: &mut ContextVec, entry: ContextEntry) -> bool {
let inner = vec.get_or_insert_with(|| Box::new(Vec::new()));
if inner.len() >= AT_MAX_CONTEXTS {
return false;
}
if inner.try_reserve(1).is_err() {
return false;
}
inner.push(entry);
true
}
#[inline]
fn context_iter(vec: &ContextVec) -> impl DoubleEndedIterator<Item = &ContextEntry> {
vec.iter().flat_map(|v| v.iter())
}
#[derive(Debug)]
pub struct AtTrace {
locations: LocationVec,
crate_info: Option<&'static AtCrateInfo>,
contexts: ContextVec,
}
impl AtTrace {
#[inline]
pub fn new() -> Self {
Self {
locations: location_vec_new(),
crate_info: None,
contexts: context_vec_new(),
}
}
#[track_caller]
#[inline]
pub fn capture() -> Self {
let mut trace = Self::new();
let _ = trace.try_push(Location::caller());
trace
}
#[inline]
pub fn set_crate_info(&mut self, info: &'static AtCrateInfo) {
self.crate_info = Some(info);
}
#[inline]
pub fn crate_info(&self) -> Option<&'static AtCrateInfo> {
self.crate_info
}
#[inline]
pub(crate) fn try_add_crate_boundary(
&mut self,
loc: &'static Location<'static>,
info: &'static AtCrateInfo,
) {
match self.crate_info {
None => {
self.crate_info = Some(info);
}
Some(existing) if core::ptr::eq(existing, info) => {
}
Some(_) => {
self.try_add_context(loc, AtContext::Crate(info));
}
}
}
#[inline]
pub(crate) fn try_push(&mut self, loc: &'static Location<'static>) -> bool {
try_push_location(&mut self.locations, Some(loc))
}
#[inline]
pub(crate) fn try_push_skipped(&mut self) -> bool {
try_push_location(&mut self.locations, None)
}
#[inline]
pub(crate) fn try_add_context(&mut self, loc: &'static Location<'static>, context: AtContext) {
let idx = if self.locations.is_empty() {
if !try_push_location(&mut self.locations, Some(loc)) {
return;
}
0u16
} else {
(self.locations.len() - 1).min(u16::MAX as usize) as u16
};
let _ = try_push_context(&mut self.contexts, (idx, context));
}
#[inline]
pub(crate) fn iter(&self) -> impl Iterator<Item = Option<&'static Location<'static>>> + '_ {
self.locations.iter()
}
#[inline]
pub(crate) fn contexts(&self) -> impl Iterator<Item = AtContextRef<'_>> {
context_iter(&self.contexts)
.rev()
.map(|(_, ctx)| AtContextRef { inner: ctx })
}
#[inline]
pub(crate) fn contexts_at(&self, idx: usize) -> impl Iterator<Item = &AtContext> {
context_iter(&self.contexts)
.filter(move |(i, _)| *i as usize == idx)
.map(|(_, ctx)| ctx)
}
#[inline]
pub fn frames(&self) -> impl Iterator<Item = AtFrame<'_>> {
self.locations.iter().enumerate().map(|(idx, loc)| AtFrame {
location: loc,
trace: self,
index: idx,
})
}
#[inline]
pub fn frame_count(&self) -> usize {
self.locations.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.locations.is_empty()
}
#[inline]
pub fn take(&mut self) -> AtTrace {
AtTrace {
locations: core::mem::take(&mut self.locations),
crate_info: self.crate_info, contexts: core::mem::take(&mut self.contexts),
}
}
#[inline]
pub fn pop(&mut self) -> Option<AtFrameOwned> {
if self.locations.is_empty() {
return None;
}
let last_idx = (self.locations.len() - 1) as u16;
let location = self.locations.pop()?;
let mut contexts = Vec::new();
if let Some(ref mut ctx_vec) = self.contexts {
let count = ctx_vec
.iter()
.rev()
.take_while(|(idx, _)| *idx == last_idx)
.count();
let _ = contexts.try_reserve(count);
while let Some(&(idx, _)) = ctx_vec.last() {
if idx == last_idx {
contexts.push(ctx_vec.pop().unwrap().1);
} else {
break;
}
}
}
contexts.reverse();
Some(AtFrameOwned { location, contexts })
}
#[inline]
pub fn push(&mut self, segment: AtFrameOwned) {
let idx = self.locations.len() as u16;
if !try_push_location(&mut self.locations, segment.location) {
return;
}
for ctx in segment.contexts {
let _ = try_push_context(&mut self.contexts, (idx, ctx));
}
}
#[inline]
pub fn pop_first(&mut self) -> Option<AtFrameOwned> {
if self.locations.is_empty() {
return None;
}
let location = self.locations.remove(0);
let mut contexts = Vec::new();
if let Some(ref mut ctx_vec) = self.contexts {
let count = ctx_vec.iter().filter(|(idx, _)| *idx == 0).count();
let _ = contexts.try_reserve(count);
let mut i = 0;
while i < ctx_vec.len() {
if ctx_vec[i].0 == 0 {
contexts.push(ctx_vec.remove(i).1);
} else {
ctx_vec[i].0 -= 1;
i += 1;
}
}
}
Some(AtFrameOwned { location, contexts })
}
#[inline]
pub fn push_first(&mut self, segment: AtFrameOwned) {
if let Some(ref mut ctx_vec) = self.contexts {
for (idx, _) in ctx_vec.iter_mut() {
*idx = idx.saturating_add(1);
}
}
if !self.locations.insert_first(segment.location) {
return;
}
if !segment.contexts.is_empty() {
let ctx_vec = self.contexts.get_or_insert_with(|| Box::new(Vec::new()));
if ctx_vec.try_reserve(segment.contexts.len()).is_err() {
return;
}
for (i, ctx) in segment.contexts.into_iter().enumerate() {
ctx_vec.insert(i, (0, ctx));
}
}
}
#[inline]
pub fn append(&mut self, mut other: AtTrace) {
while let Some(seg) = other.pop_first() {
self.push(seg);
}
}
#[inline]
pub fn prepend(&mut self, mut other: AtTrace) {
let count = other.frame_count();
let mut segments = Vec::new();
if segments.try_reserve(count).is_err() {
return; }
while let Some(seg) = other.pop() {
segments.push(seg);
}
for seg in segments {
self.push_first(seg);
}
}
}
impl Default for AtTrace {
fn default() -> Self {
Self::new()
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct AtFrameOwned {
location: Option<&'static Location<'static>>,
contexts: Vec<AtContext>,
}
impl AtFrameOwned {
#[inline]
pub fn new(location: Option<&'static Location<'static>>) -> Self {
Self {
location,
contexts: Vec::new(),
}
}
#[inline]
#[track_caller]
pub fn capture() -> Self {
Self::new(Some(Location::caller()))
}
#[inline]
pub fn location(&self) -> Option<&'static Location<'static>> {
self.location
}
#[inline]
pub fn is_skipped(&self) -> bool {
self.location.is_none()
}
#[inline]
pub fn contexts(&self) -> impl Iterator<Item = AtContextRef<'_>> {
self.contexts.iter().map(|c| AtContextRef { inner: c })
}
#[inline]
pub fn context_count(&self) -> usize {
self.contexts.len()
}
#[inline]
pub fn with_str(mut self, msg: &'static str) -> Self {
self.contexts.push(AtContext::Text(Cow::Borrowed(msg)));
self
}
#[inline]
pub fn with_string(mut self, msg: String) -> Self {
self.contexts.push(AtContext::Text(Cow::Owned(msg)));
self
}
#[inline]
pub fn with_data<T: fmt::Display + Send + Sync + 'static>(mut self, data: T) -> Self {
if let Some(boxed) = try_box(data) {
self.contexts.push(AtContext::Display(boxed));
}
self
}
#[inline]
pub fn with_debug<T: fmt::Debug + Send + Sync + 'static>(mut self, data: T) -> Self {
if let Some(boxed) = try_box(data) {
self.contexts.push(AtContext::Debug(boxed));
}
self
}
}
#[derive(Clone, Copy)]
pub struct AtFrame<'a> {
location: Option<&'static Location<'static>>,
trace: &'a AtTrace,
index: usize,
}
impl<'a> AtFrame<'a> {
#[inline]
pub fn location(&self) -> Option<&'static Location<'static>> {
self.location
}
#[inline]
pub fn is_skipped(&self) -> bool {
self.location.is_none()
}
#[inline]
pub fn contexts(&self) -> impl Iterator<Item = AtContextRef<'a>> {
let idx = self.index;
context_iter(&self.trace.contexts)
.filter(move |(i, _)| *i as usize == idx)
.map(|(_, ctx)| AtContextRef { inner: ctx })
}
#[inline]
pub fn has_contexts(&self) -> bool {
let idx = self.index;
context_iter(&self.trace.contexts).any(|(i, _)| *i as usize == idx)
}
}
impl fmt::Debug for AtFrame<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.location {
Some(loc) => {
write!(f, "at {}:{}", loc.file(), loc.line())?;
for ctx in self.contexts() {
write!(f, " ({:?})", ctx)?;
}
Ok(())
}
None => write!(f, "[...]"),
}
}
}
#[doc(hidden)]
#[derive(Default)]
pub struct AtTraceBoxed(Option<Box<AtTrace>>);
impl AtTraceBoxed {
#[inline]
pub const fn new() -> Self {
Self(None)
}
#[track_caller]
#[inline]
pub fn capture() -> Self {
Self(Some(Box::new(AtTrace::capture())))
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.as_ref().is_none_or(|t| t.is_empty())
}
#[inline]
pub fn as_ref(&self) -> Option<&AtTrace> {
self.0.as_deref()
}
#[inline]
pub fn as_mut(&mut self) -> Option<&mut AtTrace> {
self.0.as_deref_mut()
}
#[inline]
pub fn get_or_insert_mut(&mut self) -> &mut AtTrace {
self.0.get_or_insert_with(|| Box::new(AtTrace::new()))
}
#[inline]
pub fn take(&mut self) -> Option<AtTrace> {
self.0.take().map(|b| *b)
}
#[inline]
pub fn set(&mut self, trace: AtTrace) {
if trace.is_empty() {
self.0 = None;
} else {
self.0 = Some(Box::new(trace));
}
}
pub fn frames(&self) -> impl Iterator<Item = AtFrame<'_>> {
self.0.iter().flat_map(|t| t.frames())
}
#[inline]
pub fn frame_count(&self) -> usize {
self.0.as_ref().map_or(0, |t| t.frame_count())
}
#[inline]
pub fn crate_info(&self) -> Option<&'static AtCrateInfo> {
self.0.as_ref().and_then(|t| t.crate_info())
}
}
impl fmt::Debug for AtTraceBoxed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
Some(trace) => fmt::Debug::fmt(trace, f),
None => write!(f, "AtTraceBoxed(empty)"),
}
}
}
impl From<AtTrace> for AtTraceBoxed {
fn from(trace: AtTrace) -> Self {
if trace.is_empty() {
Self(None)
} else {
Self(Some(Box::new(trace)))
}
}
}
impl From<AtTraceBoxed> for Option<AtTrace> {
fn from(boxed: AtTraceBoxed) -> Self {
boxed.0.map(|b| *b)
}
}
pub trait AtTraceable: Sized {
fn trace_mut(&mut self) -> &mut AtTrace;
fn trace(&self) -> Option<&AtTrace>;
fn fmt_message(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
#[track_caller]
#[inline]
fn at(mut self) -> Self {
let _ = self.trace_mut().try_push(Location::caller());
self
}
#[track_caller]
#[inline]
fn at_str(mut self, msg: &'static str) -> Self {
let context = AtContext::Text(Cow::Borrowed(msg));
self.trace_mut()
.try_add_context(Location::caller(), context);
self
}
#[track_caller]
#[inline]
fn at_string(mut self, f: impl FnOnce() -> String) -> Self {
let context = AtContext::Text(Cow::Owned(f()));
self.trace_mut()
.try_add_context(Location::caller(), context);
self
}
#[track_caller]
#[inline]
fn at_data<T: fmt::Display + Send + Sync + 'static>(mut self, f: impl FnOnce() -> T) -> Self {
let ctx = f();
let Some(boxed_ctx) = try_box(ctx) else {
return self;
};
let context = AtContext::Display(boxed_ctx);
self.trace_mut()
.try_add_context(Location::caller(), context);
self
}
#[track_caller]
#[inline]
fn at_debug<T: fmt::Debug + Send + Sync + 'static>(mut self, f: impl FnOnce() -> T) -> Self {
let ctx = f();
let Some(boxed_ctx) = try_box(ctx) else {
return self;
};
let context = AtContext::Debug(boxed_ctx);
self.trace_mut()
.try_add_context(Location::caller(), context);
self
}
#[track_caller]
#[inline]
fn at_aside_error<E: core::error::Error + Send + Sync + 'static>(mut self, err: E) -> Self {
let Some(boxed_err) = try_box(err) else {
return self;
};
let context = AtContext::Error(boxed_err);
self.trace_mut()
.try_add_context(Location::caller(), context);
self
}
#[deprecated(
since = "0.1.4",
note = "Renamed to `at_aside_error()`. The attached error is NOT part of the `.source()` chain."
)]
#[track_caller]
#[inline]
fn at_error<E: core::error::Error + Send + Sync + 'static>(mut self, err: E) -> Self {
let Some(boxed_err) = try_box(err) else {
return self;
};
let context = AtContext::Error(boxed_err);
self.trace_mut()
.try_add_context(Location::caller(), context);
self
}
#[track_caller]
#[inline]
fn at_crate(mut self, info: &'static AtCrateInfo) -> Self {
self.trace_mut()
.try_add_crate_boundary(Location::caller(), info);
self
}
#[doc(hidden)]
#[inline]
fn at_skipped_frames(mut self) -> Self {
let _ = self.trace_mut().try_push_skipped();
self
}
#[track_caller]
#[inline]
fn at_fn<F: Fn()>(mut self, _marker: F) -> Self {
let full_name = core::any::type_name::<F>();
let name = full_name.strip_suffix("::{{closure}}").unwrap_or(full_name);
let loc = Location::caller();
let trace = self.trace_mut();
let _ = trace.try_push(loc);
let context = AtContext::FunctionName(name);
trace.try_add_context(loc, context);
self
}
#[track_caller]
#[inline]
fn at_named(mut self, name: &'static str) -> Self {
let loc = Location::caller();
let trace = self.trace_mut();
let _ = trace.try_push(loc);
let context = AtContext::FunctionName(name);
trace.try_add_context(loc, context);
self
}
#[inline]
fn at_pop(&mut self) -> Option<AtFrameOwned> {
self.trace_mut().pop()
}
#[inline]
fn at_push(&mut self, segment: AtFrameOwned) {
self.trace_mut().push(segment);
}
#[inline]
fn at_first_pop(&mut self) -> Option<AtFrameOwned> {
self.trace_mut().pop_first()
}
#[inline]
fn at_first_insert(&mut self, segment: AtFrameOwned) {
self.trace_mut().push_first(segment);
}
fn map_traceable<E2, F>(mut self, f: F) -> E2
where
F: FnOnce(Self) -> E2,
E2: AtTraceable,
{
let trace = self.trace_mut().take();
let mut new_err = f(self);
*new_err.trace_mut() = trace;
new_err
}
fn into_at<E2, F>(mut self, f: F) -> crate::At<E2>
where
F: FnOnce(Self) -> E2,
{
let trace = self.trace_mut().take();
let error = f(self);
crate::At::from_parts(error, trace)
}
fn full_trace(&self) -> impl fmt::Display + '_ {
FullTraceDisplay { error: self }
}
fn last_error_trace(&self) -> impl fmt::Display + '_ {
LastErrorTraceDisplay { error: self }
}
fn last_error(&self) -> impl fmt::Display + '_ {
LastErrorDisplay { error: self }
}
}
struct FullTraceDisplay<'a, E: AtTraceable> {
error: &'a E,
}
impl<E: AtTraceable> fmt::Display for FullTraceDisplay<'_, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.error.fmt_message(f)?;
if let Some(trace) = self.error.trace() {
let mut current_crate: Option<&str> = trace.crate_info().map(|i| i.name());
for frame in trace.frames() {
for ctx in frame.contexts() {
if let Some(info) = ctx.as_crate_info() {
let from = current_crate.unwrap_or("?");
let to = info.name();
write!(f, "\n ─── {} (above) → {} (below) ───", from, to)?;
current_crate = Some(to);
}
}
if let Some(loc) = frame.location() {
write!(f, "\n at {}:{}:{}", loc.file(), loc.line(), loc.column())?;
} else {
write!(f, "\n [...]")?;
}
for ctx in frame.contexts() {
if ctx.as_crate_info().is_some() {
continue;
}
if let Some(text) = ctx.as_text() {
write!(f, "\n {}", text)?;
} else if let Some(fn_name) = ctx.as_function_name() {
write!(f, "\n in {}", fn_name)?;
} else if let Some(err) = ctx.as_error() {
write!(f, "\n caused by: {}", err)?;
let mut source = err.source();
let mut depth = 2;
while let Some(src) = source {
let indent = " ".repeat(depth);
write!(f, "\n{}caused by: {}", indent, src)?;
source = src.source();
depth += 1;
}
} else {
write!(f, "\n {}", ctx)?;
}
}
}
}
Ok(())
}
}
struct LastErrorTraceDisplay<'a, E: AtTraceable> {
error: &'a E,
}
impl<E: AtTraceable> fmt::Display for LastErrorTraceDisplay<'_, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.error.fmt_message(f)?;
if let Some(trace) = self.error.trace() {
for frame in trace.frames() {
if let Some(loc) = frame.location() {
write!(f, "\n at {}:{}:{}", loc.file(), loc.line(), loc.column())?;
} else {
write!(f, "\n [...]")?;
}
}
}
Ok(())
}
}
struct LastErrorDisplay<'a, E: AtTraceable> {
error: &'a E,
}
impl<E: AtTraceable> fmt::Display for LastErrorDisplay<'_, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.error.fmt_message(f)
}
}