use crate::io::{ErrorKind, SimpleError, UnpackedError};
#[cfg(feature = "alloc")]
use crate::io::CustomError;
#[cfg(feature = "std")]
use crate::io::RawOsError;
use core::fmt::{self, Debug, Formatter};
use core::num::NonZero;
use core::ptr::NonNull;
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
#[cfg(feature = "std")]
const _: () = assert!(
RawOsError::BITS < usize::BITS - Tag::BITS,
"cannot encode `RawOsError` when overlapping with packed error tag",
);
#[derive(Clone, Copy)]
pub(super) struct PackedError(NonNull<()>);
impl PackedError {
#[must_use]
pub const fn new_simple(message: &'static SimpleError) -> Self {
let p = NonNull::from_ref(message).cast();
Self(p)
}
#[must_use]
pub fn new_inline(kind: ErrorKind) -> Self {
let mut addr = 0usize;
addr |= Tag::Inline.to_usize();
addr |= kind.to_usize() << Tag::BITS;
let addr = unsafe { NonZero::new_unchecked(addr) };
let p = NonNull::without_provenance(addr);
Self(p)
}
#[cfg(feature = "alloc")]
#[must_use]
pub fn new_custom(custom: Box<CustomError>) -> Self {
let mut p = Box::into_raw(custom).cast::<()>();
p = unsafe { p.byte_add(Tag::Custom.to_usize()) };
let p = unsafe { NonNull::new_unchecked(p) };
Self(p)
}
#[cfg(feature = "std")]
#[must_use]
pub fn new_os(raw: RawOsError) -> Self {
#[cfg(target_pointer_width = "16")]
compile_error!("cannot encode operating system errors on 16-bit platforms");
#[allow(clippy::cast_possible_wrap)]
let raw = raw.cast_unsigned() as usize;
let mut addr = 0usize;
addr |= Tag::Os.to_usize();
addr |= raw << Tag::BITS;
let addr = unsafe { NonZero::new_unchecked(addr) };
let p = NonNull::without_provenance(addr);
Self(p)
}
#[must_use]
pub fn unpack(self) -> UnpackedError {
match self.tag() {
Tag::Simple => {
let p = self.0.as_ptr().cast_const().cast::<SimpleError>();
let message = unsafe { &*p };
UnpackedError::Simple(message)
}
Tag::Inline => {
let kind = self.as_usize() >> Tag::BITS;
let kind = unsafe { ErrorKind::from_usize_unchecked(kind) };
UnpackedError::Inline(kind)
}
#[cfg(feature = "alloc")]
Tag::Custom => {
let mut p = self.0.as_ptr().cast::<CustomError>();
p = unsafe { p.byte_sub(Tag::Custom.to_usize()) };
UnpackedError::Custom(p)
}
#[cfg(feature = "std")]
Tag::Os => {
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_possible_wrap)]
let raw = (self.as_usize() >> Tag::BITS) as RawOsError;
UnpackedError::Os(raw)
}
}
}
#[inline]
#[must_use]
fn tag(self) -> Tag {
const TAG_MASK: usize = !(!0 << Tag::BITS);
let tag = self.as_usize() & TAG_MASK;
match tag {
0b00 => Tag::Simple,
0b01 => Tag::Inline,
#[cfg(feature = "alloc")]
0b10 => Tag::Custom,
#[cfg(feature = "std")]
0b11 => Tag::Os,
_ => unreachable!("tag should not be constructable"),
}
}
#[inline]
#[must_use]
fn as_usize(self) -> usize {
self.0.addr().get()
}
}
impl Debug for PackedError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Debug::fmt(&self.unpack(), f)
}
}
unsafe impl Send for PackedError {}
unsafe impl Sync for PackedError {}
#[repr(usize)]
#[derive(
Clone,
Copy,
Debug,
Eq,
Hash,
PartialEq,
)]
enum Tag {
Simple = 0b00,
Inline = 0b01,
#[cfg(feature = "alloc")]
Custom = 0b10,
#[cfg(feature = "std")]
Os = 0b11,
}
impl Tag {
pub const BITS: u32 = 2;
#[inline(always)]
#[must_use]
pub(crate) const fn to_usize(self) -> usize {
self as usize
}
}