#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(maybe_debug_nightly, feature(specialization, ptr_metadata))]
#![allow(
incomplete_features, // specialization is incomplete...
)]
use core::marker::PhantomData;
use core::fmt::{self, Debug, Formatter};
use core::ffi::c_void;
#[rustversion::nightly]
const NIGHTLY: bool = true;
#[rustversion::not(nightly)]
const NIGHTLY: bool = false;
#[rustversion::nightly]
mod backend {
include!("nightly.rs");
}
#[rustversion::not(nightly)]
mod backend {
include!("stable.rs");
}
#[macro_export]
macro_rules! maybe_dbg {
() => (std::dbg!());
($val:expr $(,)?) => {
{
let val = $val;
std::dbg!($crate::maybe_debug(&val));
val
}
};
($($val:expr),+ $(,)?) => {
($($crate::maybe_dbg!($val)),+ ,)
};
}
pub use self::maybe_dbg as dbg;
#[inline]
pub fn cast_debug<'a, T: 'a>(val: &'a T) -> Option<&'a (dyn Debug + 'a)> {
<T as backend::MaybeDebug>::cast_debug(val)
}
#[inline]
pub fn maybe_debug<T: ?Sized>(val: &T) -> MaybeDebug<'_> {
<T as backend::MaybeDebug>::maybe_debug(val)
}
#[non_exhaustive]
#[allow(missing_docs)]
#[derive(Copy, Clone)]
pub enum MaybeDebug<'a> {
#[doc(hidden)]
DynTrait {
type_name: &'static str,
val: &'a (dyn Debug + 'a)
},
#[doc(hidden)]
TypeName {
type_name: &'static str
},
#[doc(hidden)]
Slice(MaybeDebugSlice<'a>),
#[doc(hidden)]
Str(&'a str),
}
impl<'a> MaybeDebug<'a> {
#[inline]
pub fn has_debug_info(&self) -> bool {
match *self {
MaybeDebug::Str(_) | MaybeDebug::DynTrait { .. } => true,
MaybeDebug::TypeName { .. } => false,
MaybeDebug::Slice(ref s) => s.has_debug_info(),
}
}
#[inline]
pub fn is_fallback(&self) -> bool {
!self.has_debug_info()
}
#[inline]
pub fn is_known_slice(&self) -> bool {
matches!(*self, MaybeDebug::Slice { .. })
}
#[inline]
pub fn original_slice_len(&self) -> Option<usize> {
match *self {
MaybeDebug::Slice(ref s) => Some(s.original_len()),
_ => None
}
}
#[inline]
pub fn type_name(&self) -> &'static str {
match *self {
MaybeDebug::DynTrait { type_name, .. } |
MaybeDebug::TypeName { type_name } => type_name,
MaybeDebug::Slice(ref s) => s.type_name,
MaybeDebug::Str(_) => core::any::type_name::<str>(),
}
}
#[inline]
pub fn passthrough<T: Debug + 'a>(val: &'a T) -> Self {
MaybeDebug::DynTrait {
val: val as &'a (dyn Debug + 'a),
type_name: core::any::type_name::<T>()
}
}
#[inline]
pub fn passthrough_str(val: &'a str) -> Self {
MaybeDebug::Str(val)
}
#[inline]
pub fn passthrough_slice<T: Debug + 'a>(val: &'a [T]) -> Self {
if NIGHTLY {
MaybeDebug::Slice(MaybeDebugSlice::from_slice(val))
} else {
MaybeDebug::fallback_slice(val)
}
}
#[inline]
pub fn fallback_slice<T>(val: &'a [T]) -> Self {
MaybeDebug::Slice(unsafe { MaybeDebugSlice::with_vtable(val, None) })
}
#[inline]
pub fn fallback<T: ?Sized>() -> Self {
MaybeDebug::TypeName {
type_name: core::any::type_name::<T>()
}
}
}
impl<'a> Debug for MaybeDebug<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
MaybeDebug::DynTrait { val, .. } => val.fmt(f),
MaybeDebug::TypeName { type_name } => {
f.debug_struct(type_name)
.finish_non_exhaustive()
},
MaybeDebug::Slice(ref s) => Debug::fmt(s, f),
MaybeDebug::Str(s) => Debug::fmt(s, f),
}
}
}
#[doc(hidden)]
#[derive(Copy, Clone)]
pub struct MaybeDebugSlice<'a> {
elements: *const c_void,
len: usize,
type_size: usize,
type_name: &'static str,
debug_vtable: Option<backend::DebugVtable>,
marker: PhantomData<&'a ()>
}
impl<'a> MaybeDebugSlice<'a> {
#[inline]
fn has_debug_info(&self) -> bool {
self.debug_vtable.is_some()
}
#[inline]
pub(crate) fn from_slice<T: 'a>(elements: &'a [T]) -> Self {
let debug_vtable = if elements.is_empty() {
None
} else {
crate::cast_debug::<T>(&elements[0]).map(backend::retrieve_vtable)
};
unsafe { Self::with_vtable(elements, debug_vtable) }
}
#[inline]
unsafe fn with_vtable<T: 'a>(elements: &'a [T], debug_vtable: Option<backend::DebugVtable>) -> Self {
MaybeDebugSlice {
elements: elements.as_ptr() as *const _,
type_size: core::mem::size_of::<T>(),
len: elements.len(),
type_name: core::any::type_name::<T>(),
debug_vtable, marker: PhantomData
}
}
#[inline]
fn original_len(&self) -> usize {
self.len
}
}
impl Debug for MaybeDebugSlice<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if self.len == 0 {
f.debug_list().finish()
} else if let Some(vtable) = self.debug_vtable {
let mut elements = self.elements;
let mut debugger = f.debug_list();
unsafe {
for _ in 0..self.len {
debugger.entry(backend::from_vtable(
elements, vtable
));
elements = (elements as *const u8).add(self.type_size).cast();
}
}
debugger.finish()
} else {
write!(f, "[{} of {}]", self.len, self.type_name)
}
}
}