507 lines
13 KiB
Rust
507 lines
13 KiB
Rust
#[macro_use]
|
|
mod concat_assert;
|
|
|
|
#[cfg(feature = "non_basic")]
|
|
#[macro_use]
|
|
pub(crate) mod concat_macro;
|
|
|
|
#[cfg(feature = "non_basic")]
|
|
#[macro_use]
|
|
mod non_basic_macros;
|
|
|
|
#[cfg(feature = "non_basic")]
|
|
#[macro_use]
|
|
mod macro_utils;
|
|
|
|
#[cfg(feature = "non_basic")]
|
|
#[macro_use]
|
|
mod impl_panicfmt;
|
|
|
|
#[macro_use]
|
|
mod unwrapping;
|
|
|
|
#[doc(hidden)]
|
|
#[macro_export]
|
|
macro_rules! __write_array {
|
|
($array:expr, $len:expr, $value:expr) => {
|
|
$array[$len] = $value;
|
|
$len += 1;
|
|
};
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
#[macro_export]
|
|
macro_rules! __write_array_checked {
|
|
($array:expr, $len:expr, $value:expr) => {
|
|
if $array.len() > $len {
|
|
$array[$len] = $value;
|
|
$len += 1;
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Coerces `$reff` to a type that has a `to_panicvals` method,
|
|
/// which is expected to return a `[PanicVal<'_>; LEN]`.
|
|
///
|
|
/// # Limitations
|
|
///
|
|
#[doc = crate::doc_macros::limitation_docs!()]
|
|
///
|
|
/// # Example
|
|
///
|
|
/// This example uses [`const_panic::ArrayString`](crate::ArrayString)
|
|
/// to show what the values format into,
|
|
/// which requires the `"non_basic"` crate feature (enabled by default).
|
|
///
|
|
#[cfg_attr(feature = "non_basic", doc = "```rust")]
|
|
#[cfg_attr(not(feature = "non_basic"), doc = "```ignore")]
|
|
/// use const_panic::{ArrayString, FmtArg, IsCustomType, PanicFmt, PanicVal, coerce_fmt};
|
|
///
|
|
/// type AS = ArrayString<100>;
|
|
///
|
|
/// assert_eq!(
|
|
/// AS::from_panicvals(&coerce_fmt!(100u8).to_panicvals(FmtArg::DEBUG)).unwrap(),
|
|
/// "100",
|
|
/// );
|
|
///
|
|
/// assert_eq!(
|
|
/// AS::from_panicvals(&coerce_fmt!("hello\n").to_panicvals(FmtArg::DEBUG)).unwrap(),
|
|
/// r#""hello\n""#,
|
|
/// );
|
|
///
|
|
/// assert_eq!(
|
|
/// AS::from_panicvals(&coerce_fmt!(IsReal::No).to_panicvals(FmtArg::DEBUG)).unwrap(),
|
|
/// "No",
|
|
/// );
|
|
///
|
|
/// assert_eq!(
|
|
/// AS::from_panicvals(&coerce_fmt!(IsReal::Yes).to_panicvals(FmtArg::DEBUG)).unwrap(),
|
|
/// "Yes",
|
|
/// );
|
|
///
|
|
///
|
|
///
|
|
/// enum IsReal{Yes, No}
|
|
///
|
|
/// // All the code below manually implements panic formatting for a field-less enum.
|
|
/// // This can be written concisely with the `PanicFmt` derive or `impl_panicfmt` macro.
|
|
/// impl PanicFmt for IsReal {
|
|
/// type This = Self;
|
|
/// type Kind = IsCustomType;
|
|
/// const PV_COUNT: usize = 1;
|
|
/// }
|
|
///
|
|
/// impl IsReal {
|
|
/// pub const fn to_panicvals(&self, _f: FmtArg) -> [PanicVal<'_>; IsReal::PV_COUNT] {
|
|
/// let x = match self {
|
|
/// Self::Yes => "Yes",
|
|
/// Self::No => "No",
|
|
/// };
|
|
/// [PanicVal::write_str(x)]
|
|
/// }
|
|
/// }
|
|
///
|
|
/// ```
|
|
#[macro_export]
|
|
macro_rules! coerce_fmt {
|
|
($reff:expr) => {
|
|
match &$reff {
|
|
reff => $crate::__::PanicFmt::PROOF.infer(reff).coerce(reff),
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Panics with the concanenation of the arguments.
|
|
///
|
|
/// [**Examples below**](#examples)
|
|
///
|
|
/// # Syntax
|
|
///
|
|
/// This macro uses this syntax:
|
|
/// ```text
|
|
/// concat_panic!(
|
|
/// $($fmtarg:expr;)?
|
|
/// $(
|
|
/// $( $format_override:tt: )? $arg_to_fmt:expr
|
|
/// ),*
|
|
/// $(,)?
|
|
/// )
|
|
/// ```
|
|
///
|
|
/// `$fmtarg` is an optional [`FmtArg`](crate::FmtArg) argument
|
|
/// which defaults to `FmtArg::DEBUG`,
|
|
/// determining how non-literal `$arg_to_fmt` arguments are formatted.
|
|
///
|
|
/// [`$format_override`](#formatting-overrides) overrides the `$fmtarg` argument,
|
|
/// changing how that `$arg_to_fmt` argument is formatted.
|
|
///
|
|
#[doc = formatting_docs!()]
|
|
///
|
|
/// # Limitations
|
|
///
|
|
#[doc = crate::doc_macros::limitation_docs!()]
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ### `Odd`-type
|
|
///
|
|
/// ```rust, compile_fail
|
|
/// use const_panic::concat_panic;
|
|
///
|
|
/// use odd::Odd;
|
|
///
|
|
/// # fn main(){
|
|
/// const _: Odd = match Odd::new(3 * 4) {
|
|
/// Ok(x) => x,
|
|
/// Err(x) => concat_panic!("\nexpected odd number, got `", x, "`"),
|
|
/// };
|
|
/// # }
|
|
///
|
|
/// mod odd {
|
|
/// pub struct Odd(u32);
|
|
///
|
|
/// impl Odd {
|
|
/// pub const fn new(n: u32) -> Result<Odd, Even> {
|
|
/// if n % 2 == 1 {
|
|
/// Ok(Odd(n))
|
|
/// } else {
|
|
/// Err(Even(n))
|
|
/// }
|
|
/// }
|
|
/// }
|
|
///
|
|
/// # /*
|
|
/// #[derive(const_panic::PanicFmt))]
|
|
/// # */
|
|
/// pub struct Even(u32);
|
|
/// #
|
|
/// # impl const_panic::PanicFmt for Even {
|
|
/// # type This = Self;
|
|
/// # type Kind = const_panic::IsCustomType;
|
|
/// # const PV_COUNT: usize = 1;
|
|
/// # }
|
|
/// # impl Even {
|
|
/// # pub const fn to_panicvals(
|
|
/// # &self,
|
|
/// # f: const_panic::FmtArg,
|
|
/// # ) -> [const_panic::PanicVal<'static>; 1] {
|
|
/// # const_panic::StdWrapper(&self.0).to_panicvals(f)
|
|
/// # }
|
|
/// # }
|
|
/// }
|
|
///
|
|
/// ```
|
|
/// produces this compile-time error:
|
|
/// ```text
|
|
/// error[E0080]: evaluation of constant value failed
|
|
/// --> src/macros.rs:188:15
|
|
/// |
|
|
/// 10 | Err(x) => concat_panic!("\nexpected odd number, got `", x, "`"),
|
|
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at '
|
|
/// expected odd number, got `12`', src/macros.rs:10:15
|
|
/// |
|
|
/// = note: this error originates in the macro `concat_panic` (in Nightly builds, run with -Z macro-backtrace for more info)
|
|
///
|
|
/// ```
|
|
///
|
|
///
|
|
/// ### All the syntax
|
|
///
|
|
/// This example demonstrates using all of the syntax of this macro.
|
|
///
|
|
/// ```compile_fail
|
|
/// use const_panic::{FmtArg, concat_panic, fmt};
|
|
///
|
|
/// const _: () = concat_panic!{
|
|
/// // the optional `$fmtarg` parameter.
|
|
/// // If this argument isn't passed, it defaults to `FmtArg::DEBUG`
|
|
/// FmtArg::ALT_DEBUG;
|
|
///
|
|
/// "\n\nshowing off literals:\n",
|
|
/// 100u8,
|
|
/// "hello",
|
|
///
|
|
/// "\n\nnon-literals with formatting determined by the $fmtarg parameter:\n",
|
|
/// // this is considered a non-literal, because it's inside other tokens.
|
|
/// ("a non-literal"),
|
|
/// [100u8, 200],
|
|
///
|
|
/// "\n\nexplicitly debug formatted:\n",
|
|
/// debug: "foo",
|
|
/// // `{?}:` is The same as `debug:`
|
|
/// {?}: "bar",
|
|
///
|
|
/// "\n\nalternate debug formatted:\n",
|
|
/// alt_debug: ["foo"],
|
|
/// // `{#?}:` is The same as `alt_debug:`
|
|
/// {#?}: "bar",
|
|
///
|
|
/// "\n\ndisplay formatted:\n",
|
|
/// display: "baz",
|
|
/// // `{}:` is The same as `display:`
|
|
/// {}: ["qux", "aaa"],
|
|
///
|
|
/// "\n\nalternate display formatted:",
|
|
/// alt_display: ["bbb", "ccc"],
|
|
/// // `{#}:` is The same as `alt_display:`
|
|
/// {#}: ["bbb", "ccc"],
|
|
///
|
|
/// "\n\nbinary formatted:\n",
|
|
/// bin: [3u8, 5, 8, 13],
|
|
/// // `{b}:` is The same as `bin:`
|
|
/// {b}: [3u8, 5, 8, 13],
|
|
///
|
|
/// "\n\nalternate-binary formatted:\n",
|
|
/// alt_bin: [21u8, 34, 55, 89],
|
|
/// // `{#b}:` is The same as `alt_bin:`
|
|
/// {#b}: [21u8, 34, 55, 89],
|
|
///
|
|
/// "\n\nhexadecimal formatted:\n",
|
|
/// hex: [3u8, 5, 8, 13],
|
|
/// // `{X}:` is The same as `hex:`
|
|
/// {X}: [3u8, 5, 8, 13],
|
|
///
|
|
/// "\n\nalternate-hexadecimal formatted:\n",
|
|
/// alt_hex: [21u8, 34, 55, 89],
|
|
/// // `{#X}:` is The same as `alt_hex:`
|
|
/// {#X}: [21u8, 34, 55, 89],
|
|
///
|
|
/// "\n\n",
|
|
/// };
|
|
///
|
|
/// ```
|
|
/// The above code produces this compile-time error:
|
|
/// ```text
|
|
/// error[E0080]: evaluation of constant value failed
|
|
/// --> src/macros.rs:186:15
|
|
/// |
|
|
/// 6 | const _: () = concat_panic!{
|
|
/// | _______________^
|
|
/// 7 | | // the optional `$fmtarg` parameter.
|
|
/// 8 | | // If this argument isn't passed, it defaults to `FmtArg::DEBUG`
|
|
/// 9 | | FmtArg::ALT_DEBUG;
|
|
/// ... |
|
|
/// 60 | | "\n\n",
|
|
/// 61 | | };
|
|
/// | |_^ the evaluated program panicked at '
|
|
///
|
|
/// showing off literals:
|
|
/// 100hello
|
|
///
|
|
/// non-literals with formatting determined by the $fmtarg parameter:
|
|
/// "a non-literal"[
|
|
/// 100,
|
|
/// 200,
|
|
/// ]
|
|
///
|
|
/// explicitly debug formatted:
|
|
/// "foo""bar"
|
|
///
|
|
/// alternate debug formatted:
|
|
/// [
|
|
/// "foo",
|
|
/// ]"bar"
|
|
///
|
|
/// display formatted:
|
|
/// baz[qux, aaa]
|
|
///
|
|
/// alternate display formatted:[
|
|
/// bbb,
|
|
/// ccc,
|
|
/// ][
|
|
/// bbb,
|
|
/// ccc,
|
|
/// ]
|
|
///
|
|
/// binary formatted:
|
|
/// [11, 101, 1000, 1101][11, 101, 1000, 1101]
|
|
///
|
|
/// alternate-binary formatted:
|
|
/// [
|
|
/// 0b10101,
|
|
/// 0b100010,
|
|
/// 0b110111,
|
|
/// 0b1011001,
|
|
/// ][
|
|
/// 0b10101,
|
|
/// 0b100010,
|
|
/// 0b110111,
|
|
/// 0b1011001,
|
|
/// ]
|
|
///
|
|
/// hexadecimal formatted:
|
|
/// [3, 5, 8, D][3, 5, 8, D]
|
|
///
|
|
/// alternate-hexadecimal formatted:
|
|
/// [
|
|
/// 0x15,
|
|
/// 0x22,
|
|
/// 0x37,
|
|
/// 0x59,
|
|
/// ][
|
|
/// 0x15,
|
|
/// 0x22,
|
|
/// 0x37,
|
|
/// 0x59,
|
|
/// ]
|
|
///
|
|
/// ', src/macros.rs:6:15
|
|
/// |
|
|
/// = note: this error originates in the macro `concat_panic` (in Nightly builds, run with -Z macro-backtrace for more info)
|
|
///
|
|
/// error: aborting due to previous error
|
|
///
|
|
/// ```
|
|
///
|
|
#[macro_export]
|
|
macro_rules! concat_panic {
|
|
($($args:tt)*) => (
|
|
$crate::__concat_func_setup!{
|
|
(|args| $crate::concat_panic(args))
|
|
[]
|
|
[$($args)*,]
|
|
}
|
|
)
|
|
}
|
|
|
|
// This macro takes the optional `$fmt:expr;` argument before everything else.
|
|
// But I had to parse the argument manually,
|
|
// because `$fmt:expr;` fails compilation instead of trying the following branches
|
|
// when the argument isn't valid expression syntax.
|
|
#[doc(hidden)]
|
|
#[macro_export]
|
|
macro_rules! __concat_func_setup {
|
|
($args:tt $prev:tt [$($fmt:tt).*; $($rem:tt)* ]) => ({
|
|
let mut fmt: $crate::FmtArg = $($fmt).*;
|
|
$crate::__concat_func!{fmt $args $prev [$($rem)*]}
|
|
});
|
|
($args:tt $prev:tt [$(:: $(@$_dummy:tt@)?)? $($fmt:ident)::* ; $($rem:tt)* ]) => ({
|
|
let mut fmt: $crate::FmtArg = $(:: $($_dummy)?)? $($fmt)::*;
|
|
$crate::__concat_func!{fmt $args $prev [$($rem)*]}
|
|
});
|
|
($args:tt $prev:tt $rem:tt) => ({
|
|
let mut fmt: $crate::FmtArg = $crate::FmtArg::DEBUG;
|
|
$crate::__concat_func!{fmt $args $prev $rem}
|
|
});
|
|
}
|
|
#[doc(hidden)]
|
|
#[macro_export]
|
|
macro_rules! __concat_func {
|
|
($fmt:ident $args:tt [$($prev:tt)*] [$keyword:tt: $expr:expr, $($rem:tt)* ]) => {
|
|
$crate::__concat_func!{
|
|
$fmt
|
|
$args
|
|
[$($prev)* ($crate::__set_fmt_from_kw!($keyword, $fmt), $expr)]
|
|
[$($rem)*]
|
|
}
|
|
};
|
|
($fmt:ident $args:tt [$($prev:tt)*] [$expr:literal, $($rem:tt)* ]) => {
|
|
$crate::__concat_func!{
|
|
$fmt
|
|
$args
|
|
[$($prev)* ($crate::__set_fmt_from_kw!(display, $fmt), $expr)]
|
|
[$($rem)*]
|
|
}
|
|
};
|
|
($fmt:ident $args:tt [$($prev:tt)*] [$expr:expr, $($rem:tt)* ]) => {
|
|
$crate::__concat_func!{
|
|
$fmt
|
|
$args
|
|
[$($prev)* ($fmt, $expr)]
|
|
[$($rem)*]
|
|
}
|
|
};
|
|
($fmt:ident (|$args:ident| $function_call:expr) [$(($fmt_arg:expr, $reff:expr))*] [$(,)*]) => {
|
|
match &[
|
|
$(
|
|
$crate::StdWrapper(
|
|
&$crate::coerce_fmt!($reff)
|
|
.to_panicvals($fmt_arg)
|
|
).deref_panic_vals(),
|
|
)*
|
|
] {
|
|
$args => $function_call,
|
|
}
|
|
};
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
#[macro_export]
|
|
macro_rules! __set_fmt_from_kw {
|
|
(open, $fmtarg:ident) => {{
|
|
$fmtarg = $fmtarg.indent();
|
|
$fmtarg.set_display()
|
|
}};
|
|
(close, $fmtarg:ident) => {{
|
|
$fmtarg = $fmtarg.unindent();
|
|
$fmtarg.set_display()
|
|
}};
|
|
(display, $fmtarg:ident) => {
|
|
$fmtarg.set_display().set_alternate(false)
|
|
};
|
|
({}, $fmtarg:ident) => {
|
|
$fmtarg.set_display().set_alternate(false)
|
|
};
|
|
(alt_display, $fmtarg:ident) => {
|
|
$fmtarg.set_display().set_alternate(true)
|
|
};
|
|
({#}, $fmtarg:ident) => {
|
|
$fmtarg.set_display().set_alternate(true)
|
|
};
|
|
(debug, $fmtarg:ident) => {
|
|
$fmtarg.set_debug().set_alternate(false)
|
|
};
|
|
({?}, $fmtarg:ident) => {
|
|
$fmtarg.set_debug().set_alternate(false)
|
|
};
|
|
(alt_debug, $fmtarg:ident) => {
|
|
$fmtarg.set_debug().set_alternate(true)
|
|
};
|
|
({#?}, $fmtarg:ident) => {
|
|
$fmtarg.set_debug().set_alternate(true)
|
|
};
|
|
(hex, $fmtarg:ident) => {
|
|
$fmtarg.set_hex().set_alternate(false)
|
|
};
|
|
({X}, $fmtarg:ident) => {
|
|
$fmtarg.set_hex().set_alternate(false)
|
|
};
|
|
(alt_hex, $fmtarg:ident) => {
|
|
$fmtarg.set_hex().set_alternate(true)
|
|
};
|
|
({#X}, $fmtarg:ident) => {
|
|
$fmtarg.set_hex().set_alternate(true)
|
|
};
|
|
(bin, $fmtarg:ident) => {
|
|
$fmtarg.set_bin().set_alternate(false)
|
|
};
|
|
({b}, $fmtarg:ident) => {
|
|
$fmtarg.set_bin().set_alternate(false)
|
|
};
|
|
(alt_bin, $fmtarg:ident) => {
|
|
$fmtarg.set_bin().set_alternate(true)
|
|
};
|
|
({#b}, $fmtarg:ident) => {
|
|
$fmtarg.set_bin().set_alternate(true)
|
|
};
|
|
(_, $fmtarg:ident) => {
|
|
$fmtarg
|
|
};
|
|
($kw:tt, $fmtarg:ident) => {
|
|
compile_error!(concat!(
|
|
"unrecognized formatting specifier: ",
|
|
stringify!($kw),
|
|
"\n",
|
|
"expected one of:\n",
|
|
"- display/{}\n",
|
|
"- alt_display/{#}\n",
|
|
"- debug/{?}\n",
|
|
"- alt_debug/{#?}\n",
|
|
"- hex/{X}\n",
|
|
"- alt_hex/{#X}\n",
|
|
"- bin/{b}\n",
|
|
"- alt_bin/{#b}\n",
|
|
))
|
|
};
|
|
}
|