diff --git a/Cargo.toml b/Cargo.toml index de32035..161f16e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "const_panic" -version = "0.1.0" +version = "0.1.1" authors = ["rodrimati1992 "] edition = "2021" license = "Zlib" diff --git a/Changelog.md b/Changelog.md new file mode 100644 index 0000000..8e72737 --- /dev/null +++ b/Changelog.md @@ -0,0 +1,93 @@ +This changelog is a summary of the changes made in each release. + +# 0.1 + +### 0.1.1 + +Added `PanicFmt`-based formatting for these types(all of which require the `"non_basic"` feature): +- `Option`s of integer, bool, and `&str` +- `Option`s of arrays and slices (of integer, bool, and `&str`) +- `NonZero*` integers, and `Option`s of them +- `NonNull`, and `Option`s of them +- `*const T` and `*mut T` +- `std::cmp::Ordering`, and `Option`s of them +- `std::sync::atomic::Ordering` +- `std::ops::Range*` types, parameterized with `usize`. +- `()` +- `std::marker::PhantomData` +- `std::marker::PhantomPinned` +- `StdWrapper` + +Added these macros: +- `unwrap_ok` +- `unwrap_err` +- `unwrap_some` + +Fixed signature of to_panicvals for arrays and slices of PanicVals, by adding a `FmtArg` parameter. + + +### 0.1.0 + +Defined the `fmt::PanicFmt` trait. + +Defined these types in the `fmt` module: +- `ComputePvCount` +- `FmtArg` +- `IsCustomType` +- `IsStdType` +- `IsPanicFMt` +- `Separator` +- `Delimiter` +- `FmtKind` +- `IsLast` +- `TypeDelim` +- `ShortString` (type alias for `ArrayString<16>`) + +Defined these constants in the `fmt` module: +- `COMMA_SEP` +- `COMMA_TERM` +- `INDENTATION_STEP` + +Re-exported these variants from `fmt::Delimiter` in `fmt`: +- `CloseBrace` +- `CloseBracket` +- `CloseParen` +- `Empty` renamed to `EmptyDelimiter` +- `OpenBrace` +- `OpenBracket` +- `OpenParen` + +Reexported these items from `fmt` in the root module: +- `FmtArg` +- `IsCustomType` +- `PanicFmt` +- `ComputePvCount` +- `TypeDelim` + + +Defined these macros: +- `coerce_fmt` +- `concat_panic`: for panicking with formatted arguments. +- `flatten_panicvals`: for flattening the argument slices of `PanicVal`s into an array. +- `impl_panicfmt`: for user-defined structs and enums to implement `PanicFmt` +- `inline_macro` + +Implemented `PanicFmt`-based formatting for: +- All the primitive integer types +- `str` +- `bool` +- Arrays and slices of `PanicVal` integers, `bool`, and `&str`. + +Defined the `ArrayString` stack allocated string type. + +Defined the `PanicVal` opaque enum used for formatting. + +Defined the `StdWrapper` wrapper type for defining methods on `std` types + +Defined the `concat_panic` function, for panicking with formatted arguments. + +Defined the `"non_basic"` crate feature, +which enables all items for doing more than panicking with primitive types. + + + diff --git a/README.md b/README.md index a377968..55853bd 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,28 @@ error[E0080]: evaluation of constant value failed neither foo nor bar can be zero! foo: 10 bar: 0', src/lib.rs:8:15 +``` +When called at runtime +```should_panic +use const_panic::concat_panic; + +assert_non_zero(10, 0); + +#[track_caller] +const fn assert_non_zero(foo: u32, bar: u32) { + if foo == 0 || bar == 0 { + concat_panic!("\nneither foo nor bar can be zero!\nfoo: ", foo, "\nbar: ", bar) + } +} +``` +it prints this: +```text +thread 'main' panicked at ' +neither foo nor bar can be zero! +foo: 10 +bar: 0', src/lib.rs:6:1 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + ``` ### Custom types diff --git a/src/concat_panic_.rs b/src/concat_panic_.rs index 0f547df..459fc3c 100644 --- a/src/concat_panic_.rs +++ b/src/concat_panic_.rs @@ -61,9 +61,11 @@ pub const fn concat_panic(args: &[&[PanicVal<'_>]]) -> ! { } } +/// The maximum length of panic messages (in bytes), +/// after which the message is truncated. // this should probably be smaller on platforms where this // const fn is called at runtime, and the stack is finy. -const MAX_PANIC_MSG_LEN: usize = 32768; +pub const MAX_PANIC_MSG_LEN: usize = 32768; macro_rules! write_panicval_to_buffer { ( diff --git a/src/doc_macros.rs b/src/doc_macros.rs index ca1ee93..1d68d10 100644 --- a/src/doc_macros.rs +++ b/src/doc_macros.rs @@ -1,5 +1,5 @@ macro_rules! formatting_docs {($($additional_fmt_overrides:expr)?) => { -concat!(" +concat!(r##" # Formatting Literals are Display formatted by default, so that you can pass string literals @@ -7,6 +7,9 @@ without worrying about what the current formatting settings are. Expressions are formatted as determined by the `$fmtarg` argument. +Note that literals inside parentheses (eg: `(100)`) are considered expressions +by this macro. + ### Formatting overrides You can override how an argument is formatted by prefixing the argument expression with @@ -15,8 +18,18 @@ any of the options below: - `alt_debug:` or `{#?}:`: alternate-`Debug` formats the argument. - `display:` or `{}:`: `Display` formats the argument. - `alt_display:` or `{#}:`: alternate-`Display` formats the argument. -", +"##, $($additional_fmt_overrides,)? +r##" +### String formatting + +String expressions are debug-formatted like this: +- Prepending and appending the double quote character (`"`). +- Escaping the `'\t'`,`'\n'`,`'\r'`,`'\\'`, `'\''`, and`'\"'` characters. +- Escaping control characters with `\xYY`, +where `YY` is the hexadecimal value of the control character. + +"## )}} macro_rules! limitation_docs {() => { diff --git a/src/fmt_impls/basic_fmt_impls.rs b/src/fmt_impls/basic_fmt_impls.rs index 880aca3..7f09e00 100644 --- a/src/fmt_impls/basic_fmt_impls.rs +++ b/src/fmt_impls/basic_fmt_impls.rs @@ -3,6 +3,38 @@ use crate::{ FmtArg, PanicFmt, StdWrapper, }; +macro_rules! primitive_static_panicfmt { + ( + fn[$($impl:tt)*](&$self:ident: $ty:ty, $f:ident) { + $($content:tt)* + } + ) => { + impl<$($impl)*> PanicFmt for $ty { + type This = Self; + type Kind = crate::fmt::IsStdType; + const PV_COUNT: usize = 1; + } + + impl<$($impl)*> crate::StdWrapper<&$ty> { + #[doc = concat!( + "Converts this `", stringify!($ty), "` to a single-element `PanicVal` array." + )] + pub const fn to_panicvals($self, $f: FmtArg) -> [PanicVal<'static>; 1] { + [{ + $($content)* + }] + } + + #[doc = concat!( + "Converts this `", stringify!($ty), "` to a `PanicVal`." + )] + pub const fn to_panicval($self, $f: FmtArg) -> PanicVal<'static> { + $($content)* + } + } + } +} + macro_rules! impl_panicfmt_panicval_array { ( PV_COUNT = $pv_len:expr; @@ -18,7 +50,7 @@ macro_rules! impl_panicfmt_panicval_array { impl<'s, $($impl)*> StdWrapper<&'s $ty> { /// - pub const fn to_panicvals($self: Self) -> $ret { + pub const fn to_panicvals($self: Self, _: FmtArg) -> $ret { $($content)* } } @@ -77,20 +109,8 @@ macro_rules! impl_panicfmt_int { } } - impl PanicFmt for $ty { - type This = Self; - type Kind = crate::fmt::IsStdType; - - const PV_COUNT: usize = 1; - } - - impl<'s> StdWrapper<&'s $ty> { - /// Converts this integer to a single-element `PanicVal` array. - pub const fn to_panicvals(self: Self, f: FmtArg) -> [PanicVal<'static>; 1] { - [PanicVal::$panic_arg_ctor(*self.0, f)] - } - /// Converts this integer to a `PanicVal`. - pub const fn to_panicval(self: Self, f: FmtArg) -> PanicVal<'static> { + primitive_static_panicfmt! { + fn[](&self: $ty, f) { PanicVal::$panic_arg_ctor(*self.0, f) } } diff --git a/src/fmt_impls/fmt_range.rs b/src/fmt_impls/fmt_range.rs new file mode 100644 index 0000000..46a5111 --- /dev/null +++ b/src/fmt_impls/fmt_range.rs @@ -0,0 +1,87 @@ +use crate::{FmtArg, PanicFmt, PanicVal, StdWrapper}; + +use core::ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; + +macro_rules! impl_range_panicfmt_one { + ( + fn(&$self:ident: $ty:ty, $f:ident) -> $pv_count:literal { + $($content:tt)* + } + ) => { + impl PanicFmt for $ty { + type This = Self; + type Kind = crate::fmt::IsStdType; + const PV_COUNT: usize = $pv_count; + } + + impl crate::StdWrapper<&$ty> { + #[doc = concat!( + "Converts this `", stringify!($ty), "` to a single-element `PanicVal` array." + )] + pub const fn to_panicvals($self, $f: FmtArg) -> [PanicVal<'static>; $pv_count] { + $($content)* + } + } + } +} + +macro_rules! impl_range_panicfmt { + ($elem_ty:ty) => { + impl_range_panicfmt_one! { + fn(&self: Range<$elem_ty>, f) -> 3 { + [ + StdWrapper(&self.0.start).to_panicval(f), + PanicVal::write_str(".."), + StdWrapper(&self.0.end).to_panicval(f), + ] + } + } + + impl_range_panicfmt_one! { + fn(&self: RangeFrom<$elem_ty>, f) -> 2 { + [ + StdWrapper(&self.0.start).to_panicval(f), + PanicVal::write_str(".."), + ] + } + } + + impl_range_panicfmt_one! { + fn(&self: RangeTo<$elem_ty>, f) -> 2 { + [ + PanicVal::write_str(".."), + StdWrapper(&self.0.end).to_panicval(f), + ] + } + } + + impl_range_panicfmt_one! { + fn(&self: RangeToInclusive<$elem_ty>, f) -> 2 { + [ + PanicVal::write_str("..="), + StdWrapper(&self.0.end).to_panicval(f), + ] + } + } + + impl_range_panicfmt_one! { + fn(&self: RangeInclusive<$elem_ty>, f) -> 3 { + [ + StdWrapper(self.0.start()).to_panicval(f), + PanicVal::write_str("..="), + StdWrapper(self.0.end()).to_panicval(f), + ] + } + } + }; +} + +impl_range_panicfmt! {usize} + +//////////////////////////////////////////////////////////////////////////////// + +impl_range_panicfmt_one! { + fn(&self: RangeFull, _f) -> 1 { + [PanicVal::write_str("..")] + } +} diff --git a/src/fmt_impls/nonzero_impls.rs b/src/fmt_impls/nonzero_impls.rs new file mode 100644 index 0000000..094ae97 --- /dev/null +++ b/src/fmt_impls/nonzero_impls.rs @@ -0,0 +1,37 @@ +use crate::{FmtArg, PanicFmt, PanicVal}; + +use core::num::{ + NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128, + NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, +}; + +macro_rules! nonzero_impls { + ($(($int_ctor:ident, $ty:ty))*) => ( + $( + primitive_static_panicfmt!{ + fn[](&self: $ty, fmtarg) { + PanicVal::$int_ctor(self.0.get(), fmtarg) + } + } + )* + + impl_for_option!{ + $((for[], 'static, $ty, $ty))* + } + ) +} + +nonzero_impls! { + (from_u8, NonZeroU8) + (from_i8, NonZeroI8) + (from_u16, NonZeroU16) + (from_i16, NonZeroI16) + (from_u32, NonZeroU32) + (from_i32, NonZeroI32) + (from_u64, NonZeroU64) + (from_i64, NonZeroI64) + (from_u128, NonZeroU128) + (from_i128, NonZeroI128) + (from_usize, NonZeroUsize) + (from_isize, NonZeroIsize) +} diff --git a/src/fmt_impls/option_fmt_impls.rs b/src/fmt_impls/option_fmt_impls.rs new file mode 100644 index 0000000..6709791 --- /dev/null +++ b/src/fmt_impls/option_fmt_impls.rs @@ -0,0 +1,78 @@ +use crate::{FmtArg, PanicFmt, PanicVal}; + +/// Note: there is only `to_panicvals` methods for `Option`s of standard library types +/// for now. +/// +impl PanicFmt for Option +where + T: PanicFmt, +{ + type This = Self; + type Kind = crate::fmt::IsStdType; + const PV_COUNT: usize = 4 + T::PV_COUNT; +} + +macro_rules! impl_for_option { + ( + $((for[$($generics:tt)*],$lt:lifetime, $ty:ty, $unref:ty))* + ) => ( + $( + impl<'s, $($generics)*> crate::StdWrapper<&'s Option<$ty>> { + #[doc = concat!( + "Converts this `Option<", + stringify!($ty), + ">` to a `PanicVal` array." + )] + pub const fn to_panicvals( + self: Self, + mut fmtarg: FmtArg, + ) -> [PanicVal<$lt>; 5] { + use crate::{PanicVal, StdWrapper, __::EPV, fmt}; + + match self.0 { + Some(x) => [ + PanicVal::write_str("Some"), + {fmtarg = fmtarg.indent(); fmt::OpenParen.to_panicval(fmtarg)}, + StdWrapper::<&$unref>(x).to_panicval(fmtarg), + fmt::COMMA_TERM.to_panicval(fmtarg), + {fmtarg = fmtarg.unindent(); fmt::CloseParen.to_panicval(fmtarg)}, + ], + None => [PanicVal::write_str("None"), EPV, EPV, EPV, EPV], + } + } + } + )* + ) +} + +macro_rules! impl_for_option_outer { + ( + $(($lt:lifetime, $ty:ty, $unref:ty))* + ) => ( + impl_for_option!{ + $( + (for[], $lt, $ty, $unref) + (for[const N: usize], 's, [$ty; N], [$ty; N]) + (for[const N: usize], 's, &'s [$ty; N], [$ty; N]) + (for[], 's, &'s [$ty], [$ty]) + )* + } + ) +} + +impl_for_option_outer! { + ('static, bool, bool) + ('static, u8, u8) + ('static, u16, u16) + ('static, u32, u32) + ('static, u64, u64) + ('static, u128, u128) + ('static, i8, i8) + ('static, i16, i16) + ('static, i32, i32) + ('static, i64, i64) + ('static, i128, i128) + ('static, isize, isize) + ('static, usize, usize) + ('s, &'s str, str) +} diff --git a/src/fmt_impls/other_impls.rs b/src/fmt_impls/other_impls.rs new file mode 100644 index 0000000..295b6e2 --- /dev/null +++ b/src/fmt_impls/other_impls.rs @@ -0,0 +1,75 @@ +use crate::{FmtArg, PanicFmt, PanicVal}; + +use core::{ + marker::{PhantomData, PhantomPinned}, + ptr::NonNull, +}; + +use core as std; + +macro_rules! ptr_impls { + ($ty:ty) => { + primitive_static_panicfmt! { + fn[T: ?Sized](&self: $ty, _f) { + PanicVal::write_str("") + } + } + }; +} + +ptr_impls! {*const T} + +ptr_impls! {*mut T} + +ptr_impls! {NonNull} + +impl_for_option! { + (for[T], 'static, NonNull, NonNull) +} + +primitive_static_panicfmt! { + fn[T: ?Sized](&self: PhantomData, _f) { + PanicVal::write_str("PhantomData") + } +} + +primitive_static_panicfmt! { + fn[](&self: PhantomPinned, _f) { + PanicVal::write_str("PhantomPinned") + } +} + +primitive_static_panicfmt! { + fn[](&self: (), _f) { + PanicVal::write_str("()") + } +} + +impl_for_option! { + (for[], 'static, core::cmp::Ordering, core::cmp::Ordering) +} +primitive_static_panicfmt! { + fn[](&self: std::cmp::Ordering, _f) { + let v = match self.0 { + std::cmp::Ordering::Less => "Less", + std::cmp::Ordering::Equal => "Equal", + std::cmp::Ordering::Greater => "Greater", + }; + PanicVal::write_str(v) + } +} + +primitive_static_panicfmt! { + fn[](&self: std::sync::atomic::Ordering, _f) { + use std::sync::atomic::Ordering; + let v = match self.0 { + Ordering::Relaxed => "Relaxed", + Ordering::Release => "Release", + Ordering::Acquire => "Acquire", + Ordering::AcqRel => "AcqRel", + Ordering::SeqCst => "SeqCst", + _ => "", + }; + PanicVal::write_str(v) + } +} diff --git a/src/lib.rs b/src/lib.rs index fbdb5e5..2849473 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,29 @@ //! bar: 0', src/lib.rs:8:15 //! ``` //! +//! When called at runtime +//! ```should_panic +//! use const_panic::concat_panic; +//! +//! assert_non_zero(10, 0); +//! +//! #[track_caller] +//! const fn assert_non_zero(foo: u32, bar: u32) { +//! if foo == 0 || bar == 0 { +//! concat_panic!("\nneither foo nor bar can be zero!\nfoo: ", foo, "\nbar: ", bar) +//! } +//! } +//! ``` +//! it prints this: +//! ```text +//! thread 'main' panicked at ' +//! neither foo nor bar can be zero! +//! foo: 10 +//! bar: 0', src/lib.rs:6:1 +//! note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +//! +//! ``` +//! //! ### Custom types //! //! Panic formatting for custom types can be done in these ways @@ -153,6 +176,11 @@ //! # Limitations #![doc = crate::doc_macros::limitation_docs!()] //! +//! ### Panic message length +//! +//! The panic message can only be up to [`MAX_PANIC_MSG_LEN`] long, +//! after which it is truncated. +//! //! # Cargo features //! //! - `"non_basic"`(enabled by default): @@ -220,10 +248,28 @@ pub use crate::array_string::ArrayString; mod wrapper; mod fmt_impls { + #[macro_use] mod basic_fmt_impls; + + #[macro_use] + #[cfg(feature = "non_basic")] + mod option_fmt_impls; + + #[cfg(feature = "non_basic")] + mod nonzero_impls; + + #[cfg(feature = "non_basic")] + mod other_impls; + + #[cfg(feature = "non_basic")] + mod fmt_range; } -pub use crate::{concat_panic_::concat_panic, panic_val::PanicVal, wrapper::StdWrapper}; +pub use crate::{ + concat_panic_::{concat_panic, MAX_PANIC_MSG_LEN}, + panic_val::PanicVal, + wrapper::StdWrapper, +}; #[doc(no_inline)] pub use crate::fmt::{FmtArg, IsCustomType, PanicFmt}; @@ -234,12 +280,28 @@ pub use crate::fmt::{ComputePvCount, TypeDelim}; #[doc(hidden)] pub mod __ { - pub use core::{compile_error, concat, primitive::usize, stringify}; + pub use core::{ + compile_error, concat, + option::Option::{None, Some}, + primitive::usize, + result::Result::{Err, Ok}, + stringify, + }; - pub use crate::fmt::{FmtArg, PanicFmt}; + pub use crate::*; #[cfg(feature = "non_basic")] + pub use crate::reexported_non_basic::*; +} + +#[cfg(feature = "non_basic")] +#[doc(hidden)] +mod reexported_non_basic { + pub use core::option::Option::{self, None, Some}; + pub use crate::utils::{assert_flatten_panicvals_length, flatten_panicvals, panicvals_id}; + + pub const EPV: crate::PanicVal<'_> = crate::PanicVal::EMPTY; } #[doc(hidden)] diff --git a/src/macros.rs b/src/macros.rs index 0e8cf64..910579e 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -10,6 +10,9 @@ mod macro_utils; #[macro_use] mod impl_panicfmt; +#[macro_use] +mod unwrapping; + #[doc(hidden)] #[macro_export] macro_rules! __write_array { diff --git a/src/macros/unwrapping.rs b/src/macros/unwrapping.rs new file mode 100644 index 0000000..ac64e06 --- /dev/null +++ b/src/macros/unwrapping.rs @@ -0,0 +1,188 @@ +/// Returns the value in the `Some` variant. +/// +/// # Panics +/// +/// Panics if `$opt` is a None. +/// +/// # Example +/// +/// ```rust +/// use const_panic::unwrap_some; +/// +/// const SUM: u8 = unwrap_some!(add_up(&[3, 5, 8, 13])); +/// +/// assert_eq!(SUM, 29); +/// +/// +/// const fn add_up(mut slice: &[u8]) -> Option { +/// let mut sum = 0u8; +/// +/// while let [x, ref rem @ ..] = *slice { +/// match sum.checked_add(x) { +/// Some(x) => sum = x, +/// None => return None, +/// } +/// slice = rem; +/// } +/// +/// Some(sum) +/// } +/// +/// ``` +/// +/// +/// ### Error +/// +/// This is what the compile-time error looks like when attempting to unwrap a `None`: +/// +/// ```text +/// error[E0080]: evaluation of constant value failed +/// --> src/macros/unwrapping.rs:10:17 +/// | +/// 6 | const SUM: u8 = unwrap_some!(add_up(&[3, 5, 8, 13, 250])); +/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at ' +/// invoked `unwrap_some` macro with a `None` value', src/macros/unwrapping.rs:6:17 +/// | +/// = note: this error originates in the macro `unwrap_some` (in Nightly builds, run with -Z macro-backtrace for more info) +/// +/// ``` +/// +#[macro_export] +macro_rules! unwrap_some { + ($opt:expr) => { + match $opt { + $crate::__::Some(x) => x, + $crate::__::None => $crate::concat_panic(&[&[$crate::PanicVal::write_str( + "\ninvoked `unwrap_some` macro with a `None` value", + )]]), + } + }; +} + +/// Returns the value in the `Ok` variant. +/// +/// # Panics +/// +/// This panics if `$res` is an `Err`, including the debug-formatted error in the panic message. +/// +/// # Example +/// +/// The struct formatting below requires the `"non_basic"` feature (enabled by default) +/// +#[cfg_attr(feature = "non_basic", doc = "```rust")] +#[cfg_attr(not(feature = "non_basic"), doc = "```ignore")] +/// use const_panic::unwrap_ok; +/// +/// const SUM: u64 = unwrap_ok!(add_up_evens(&[2, 4, 8, 16])); +/// +/// assert_eq!(SUM, 30); +/// +/// const fn add_up_evens(slice: &[u8]) -> Result { +/// let mut sum = 0u64; +/// let mut i = 0; +/// +/// while i < slice.len() { +/// let x = slice[i]; +/// +/// if x % 2 == 1 { +/// return Err(OddError{at: i, number: x}); +/// } +/// +/// sum += x as u64; +/// i += 1; +/// } +/// +/// Ok(sum) +/// } +/// +/// +/// struct OddError { +/// at: usize, +/// number: u8, +/// } +/// +/// const_panic::impl_panicfmt!{ +/// impl OddError; +/// +/// struct OddError { +/// at: usize, +/// number: u8, +/// } +/// } +/// +/// ``` +/// +/// ### Error +/// +/// This is what the compile-time error looks like when attempting to unwrap an `Err`: +/// +/// ```text +/// error[E0080]: evaluation of constant value failed +/// --> src/macros/unwrapping.rs:51:18 +/// | +/// 6 | const SUM: u64 = unwrap_ok!(add_up_evens(&[3, 5, 8, 13])); +/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at ' +/// invoked `unwrap_ok` macro with an `Err` value: OddError { at: 0, number: 3 }', src/macros/unwrapping.rs:6:18 +/// | +/// ``` +#[macro_export] +macro_rules! unwrap_ok { + ($res:expr) => { + match $res { + $crate::__::Ok(x) => x, + $crate::__::Err(e) => $crate::concat_panic(&[ + &[$crate::PanicVal::write_str( + "\ninvoked `unwrap_ok` macro with an `Err` value: ", + )], + &$crate::coerce_fmt!(e).to_panicvals($crate::FmtArg::DEBUG), + ]), + } + }; +} + +/// Returns the value in the `Err` variant. +/// +/// # Panics +/// +/// This panics if `$res` is an `Ok`, including the debug-formatted value in the panic message. +/// +/// # Example +/// +/// ```rust +/// use const_panic::unwrap_err; +/// +/// type Res = Result; +/// +/// const ERR: &str = unwrap_err!(Res::Err("this is an error")); +/// +/// assert_eq!(ERR, "this is an error"); +/// +/// ``` +/// +/// ### Error +/// +/// This is what the compile-time error looks like when attempting to unwrap an `Ok`: +/// +/// ```text +/// error[E0080]: evaluation of constant value failed +/// --> src/macros/unwrapping.rs:174:19 +/// | +/// 8 | const ERR: &str = unwrap_err!(Res::Ok(1234)); +/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at ' +/// invoked `unwrap_err` macro with an `Ok` value: 1234', src/macros/unwrapping.rs:8:19 +/// | +/// ``` +#[macro_export] +macro_rules! unwrap_err { + ($res:expr) => { + match $res { + $crate::__::Ok(x) => $crate::concat_panic(&[ + &[$crate::PanicVal::write_str( + "\ninvoked `unwrap_err` macro with an `Ok` value: ", + )], + &$crate::coerce_fmt!(x).to_panicvals($crate::FmtArg::DEBUG), + ]), + $crate::__::Err(e) => e, + } + }; +} diff --git a/src/wrapper.rs b/src/wrapper.rs index bd6949a..6d1409b 100644 --- a/src/wrapper.rs +++ b/src/wrapper.rs @@ -1,3 +1,5 @@ +use crate::PanicFmt; + /// A wrapper type used to define methods for std types. /// /// Std types are coerced to this type through the approach used in the @@ -30,3 +32,13 @@ #[derive(Copy, Clone)] #[repr(transparent)] pub struct StdWrapper(pub T); + +impl PanicFmt for StdWrapper +where + T: PanicFmt, +{ + type This = Self; + type Kind = crate::fmt::IsCustomType; + + const PV_COUNT: usize = T::PV_COUNT; +} diff --git a/tests/main_test_modules.rs b/tests/main_test_modules.rs index ff54821..c8b408b 100644 --- a/tests/main_test_modules.rs +++ b/tests/main_test_modules.rs @@ -15,8 +15,16 @@ mod main_tests { #[cfg(feature = "non_basic")] mod impl_panicfmt_tests; + #[cfg(feature = "non_basic")] + mod option_fmt_tests; + + #[cfg(feature = "non_basic")] + mod other_fmt_tests; + mod integer_tests; + mod misc_macros_tests; + mod panicval_macros_tests; #[cfg(feature = "non_basic")] diff --git a/tests/main_tests/integer_tests.rs b/tests/main_tests/integer_tests.rs index 0408491..31f657a 100644 --- a/tests/main_tests/integer_tests.rs +++ b/tests/main_tests/integer_tests.rs @@ -1,22 +1,28 @@ -#[test] -fn integer_test() { - macro_rules! test_case { - ($($nums:expr),* $(,)*)=> ({ - for int in [$($nums),*] { - let string = format!("{:?}", int); +#[cfg(feature = "non_basic")] +use core::num::{ + NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128, + NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, +}; - assert_eq!( - overf_fmt!(string.len(); int).unwrap(), - *string, - ); - assert_eq!(trunc_fmt!(string.len(); int), *string); +macro_rules! test_case { + ($($nums:expr),* $(,)*)=> ({ + for int in [$($nums),*] { + let string = format!("{:?}", int); - overf_fmt!(string.len() - 1; int).unwrap_err(); - assert_eq!(trunc_fmt!(string.len() - 1; int), ""); - } - }) - } + assert_eq!( + overf_fmt!(string.len(); int).unwrap(), + *string, + ); + assert_eq!(trunc_fmt!(string.len(); int), *string); + overf_fmt!(string.len() - 1; int).unwrap_err(); + assert_eq!(trunc_fmt!(string.len() - 1; int), ""); + } + }) +} + +#[test] +fn integer_test() { test_case!(0u8, 1, 2, u8::MAX / 2, u8::MAX); test_case!(0u16, 1, 2, u16::MAX / 2, u16::MAX); test_case!(0u32, 1, 2, u32::MAX / 2, u32::MAX); @@ -91,3 +97,21 @@ fn integer_test() { isize::MAX ); } + +// Tests aren't so thorough, since NonZero integers just delegate to the built-in ones. +#[cfg(feature = "non_basic")] +#[test] +fn nonzero_integer_test() { + test_case! {NonZeroU8::new(5).unwrap()} + test_case! {NonZeroI8::new(-5).unwrap()} + test_case! {NonZeroU16::new(8).unwrap()} + test_case! {NonZeroI16::new(-8).unwrap()} + test_case! {NonZeroU32::new(13).unwrap()} + test_case! {NonZeroI32::new(-13).unwrap()} + test_case! {NonZeroU64::new(21).unwrap()} + test_case! {NonZeroI64::new(-21).unwrap()} + test_case! {NonZeroU128::new(34).unwrap()} + test_case! {NonZeroI128::new(-34).unwrap()} + test_case! {NonZeroUsize::new(55).unwrap()} + test_case! {NonZeroIsize::new(-55).unwrap()} +} diff --git a/tests/main_tests/option_fmt_tests.rs b/tests/main_tests/option_fmt_tests.rs new file mode 100644 index 0000000..b1f0185 --- /dev/null +++ b/tests/main_tests/option_fmt_tests.rs @@ -0,0 +1,98 @@ +use const_panic::FmtArg; + +use core::{ + cmp::Ordering, + num::{ + NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128, + NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, + }, + ptr::NonNull, +}; + +#[test] +fn test_option_fmt() { + macro_rules! test_case { + ($expr:expr, $fmt:expr, $expected:expr) => { + assert_eq!(trunc_fmt!(1024; $fmt; $expr), $expected); + }; + } + + test_case! {Some("hello"), FmtArg::DEBUG, "Some(\"hello\")"} + test_case! {Some(false), FmtArg::DEBUG, "Some(false)"} + test_case! {Some(3u8), FmtArg::DEBUG, "Some(3)"} + test_case! {Some(3u16), FmtArg::DEBUG, "Some(3)"} + test_case! {Some(3u32), FmtArg::DEBUG, "Some(3)"} + test_case! {Some(3u64), FmtArg::DEBUG, "Some(3)"} + test_case! {Some(3u128), FmtArg::DEBUG, "Some(3)"} + test_case! {Some(3usize), FmtArg::DEBUG, "Some(3)"} + test_case! {Some(3i8), FmtArg::DEBUG, "Some(3)"} + test_case! {Some(3i16), FmtArg::DEBUG, "Some(3)"} + test_case! {Some(3i32), FmtArg::DEBUG, "Some(3)"} + test_case! {Some(3i64), FmtArg::DEBUG, "Some(3)"} + test_case! {Some(3i128), FmtArg::DEBUG, "Some(3)"} + test_case! {Some(3isize), FmtArg::DEBUG, "Some(3)"} + + test_case! {NonNull::new(&mut 100), FmtArg::DEBUG, "Some()"} + + test_case! {NonZeroU8::new(5), FmtArg::DEBUG, "Some(5)"} + test_case! {NonZeroI8::new(5), FmtArg::DEBUG, "Some(5)"} + test_case! {NonZeroU16::new(5), FmtArg::DEBUG, "Some(5)"} + test_case! {NonZeroI16::new(5), FmtArg::DEBUG, "Some(5)"} + test_case! {NonZeroU32::new(5), FmtArg::DEBUG, "Some(5)"} + test_case! {NonZeroI32::new(5), FmtArg::DEBUG, "Some(5)"} + test_case! {NonZeroU64::new(5), FmtArg::DEBUG, "Some(5)"} + test_case! {NonZeroI64::new(5), FmtArg::DEBUG, "Some(5)"} + test_case! {NonZeroU128::new(5), FmtArg::DEBUG, "Some(5)"} + test_case! {NonZeroI128::new(5), FmtArg::DEBUG, "Some(5)"} + test_case! {NonZeroUsize::new(5), FmtArg::DEBUG, "Some(5)"} + test_case! {NonZeroIsize::new(5), FmtArg::DEBUG, "Some(5)"} + + test_case! {Some(Ordering::Less), FmtArg::DEBUG, "Some(Less)"} + test_case! {Some(Ordering::Equal), FmtArg::DEBUG, "Some(Equal)"} + test_case! {Some(Ordering::Greater), FmtArg::DEBUG, "Some(Greater)"} + + test_case! {Some([3u8, 5, 8]), FmtArg::DEBUG, "Some([3, 5, 8])"} + test_case! {Some(&[3u16, 5, 8]), FmtArg::DEBUG, "Some([3, 5, 8])"} + test_case! {Some(&[3u32, 5, 8][..]), FmtArg::DEBUG, "Some([3, 5, 8])"} + + test_case! {Some([false, true]), FmtArg::DEBUG, "Some([false, true])"} + test_case! { + Some( [false, true]), + FmtArg::ALT_DEBUG, + concat!( + "Some(\n", + " [\n", + " false,\n", + " true,\n", + " ],\n", + ")", + ) + } + + test_case! {Some(["hello", "world"]), FmtArg::DEBUG, "Some([\"hello\", \"world\"])"} + test_case! {Some(["hello", "world"]), FmtArg::DISPLAY, "Some([hello, world])"} + test_case! { + Some( ["hello", "world"]), + FmtArg::ALT_DEBUG, + concat!( + "Some(\n", + " [\n", + " \"hello\",\n", + " \"world\",\n", + " ],\n", + ")", + ) + } + test_case! { + Some( ["hello", "world"]), + FmtArg::ALT_DISPLAY, + concat!( + "Some(\n", + " [\n", + " hello,\n", + " world,\n", + " ],\n", + ")", + ) + } +} diff --git a/tests/main_tests/other_fmt_tests.rs b/tests/main_tests/other_fmt_tests.rs new file mode 100644 index 0000000..5d29064 --- /dev/null +++ b/tests/main_tests/other_fmt_tests.rs @@ -0,0 +1,70 @@ +use const_panic::{FmtArg, StdWrapper}; + +use core::{ + cmp::Ordering, + marker::{PhantomData, PhantomPinned}, + ptr::NonNull, + sync::atomic::Ordering as AtomicOrdering, +}; + +macro_rules! test_val { + ($value:expr, $expected:expr) => ({ + let val = $value; + assert_eq!(trunc_fmt!(1024; StdWrapper(&val)), $expected); + assert_eq!(trunc_fmt!(1024; StdWrapper(&val).to_panicvals(FmtArg::DEBUG)), $expected); + assert_eq!(trunc_fmt!(1024; StdWrapper(&val).to_panicval(FmtArg::DEBUG)), $expected); + }) +} + +macro_rules! test_vals { + ($value:expr, $expected:expr) => ({ + let val = $value; + assert_eq!(trunc_fmt!(1024; StdWrapper(&val)), $expected); + }) +} + +#[test] +fn fmt_pointer() { + test_val! {&3u8 as *const u8, ""} + test_val! {&mut 3u8 as *mut u8, ""} + test_val! {NonNull::::from(&mut 3), ""} + + test_val! {"hello" as *const str, ""} + + test_val! {&[][..] as *const [u8], ""} + test_val! {&mut [][..] as *mut [u8], ""} + test_val! {NonNull::<[u8]>::from(&mut [][..]), ""} +} + +#[test] +fn fmt_units() { + test_val! {PhantomData::, "PhantomData"} + test_val! {PhantomData::, "PhantomData"} + + test_val! {PhantomPinned, "PhantomPinned"} + + test_val! {(), "()"} +} + +#[test] +fn fmt_orderings() { + test_val! {Ordering::Less, "Less"} + test_val! {Ordering::Equal, "Equal"} + test_val! {Ordering::Greater, "Greater"} + + test_val! {AtomicOrdering::Relaxed, "Relaxed"} + test_val! {AtomicOrdering::Release, "Release"} + test_val! {AtomicOrdering::Acquire, "Acquire"} + test_val! {AtomicOrdering::AcqRel, "AcqRel"} + test_val! {AtomicOrdering::SeqCst, "SeqCst"} +} + +#[test] +fn fmt_range() { + test_vals! {3..5, "3..5"} + test_vals! {3.., "3.."} + test_vals! {.., ".."} + test_vals! {3..=5, "3..=5"} + test_vals! {..5, "..5"} + test_vals! {..=5, "..=5"} +}