macro_rules! flatten_panicvals {
    ($fmtargs:expr $(, $length:expr)?; $($args:tt)* ) => { ... };
}
Available on crate feature non_basic only.
Expand description

Formats multiple values into an array of PanicVals.

The flattening part comes from the fact that each argument is converted to an array of PanicVal, which are then concatenated into a single array.

Arguments

The syntax for this macro is

flatten_panicvals!(
    $fmtarg:expr $(, $pv_count:expr)?;
    $(
        $($Type:ty => )? $($format_override:tt :)? $arg_to_fmt:expr
    ),*
    $()?
)

$fmtarg is a FmtArg argument which determines how non-literal $arg_to_fmt arguments are formatted.

$pv_count is an optional argument which overrides the length of the array that this returns.
If this argument is smaller than the flattened arrays would create, it produces a compile-time error. If this is larger, this fills the trailing elements with PanicVal::EMPTY.

$format_override overrides the $fmtarg argument, changing how that $arg_to_fmt argument is formatted.

$arg_to_fmt are the formatted arguments, which must implement the PanicFmt trait.

If the $Type => syntax is used, this calls the to_panicvals method on $arg_to_fmt.
If the $Type => syntax is not used, this calls the to_panicval method on $arg_to_fmt.

These are the signatures of those methods:

const fn to_panicvals(&self, f: FmtArg) -> [PanicVal<'_>; <Foo as PanicFmt>::PV_COUNT]
const fn to_panicval(&self, f: FmtArg) -> PanicVal<'_>

Parsing limitation

Because of limitations of macro_rules! macros, you’ll sometimes need to wrap $arg_to_fmt arguments in parentheses to fix this error:

error: expected type, found `foo`

Formatting

Literals are Display formatted by default, so that you can pass string literals 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 any of the options below:

  • debug: or {?}:: Debug formats the argument.

  • alt_debug: or {#?}:: alternate-Debug formats the argument.

  • display: or {}:: Display formats the argument.

  • alt_display: or {#}:: alternate-Display formats the argument.

  • bin: or {b}:: Debug formats the argument, with binary-formatted numbers.

  • alt_bin: or {#b}:: alternate-Debug formats the argument, with binary-formatted numbers.

  • hex: or {X}:: Debug formats the argument, with hexadecimal-formatted numbers.

  • alt_hex: or {#X}:: alternate-Debug formats the argument, with hexadecimal-formatted numbers.

  • open: increments $fmtarg’s indentation by fmt::INDENTATION_STEP before formatting the argument, and uses Display formatting for that argument.

  • close: decrements $fmtarg’s indentation by fmt::INDENTATION_STEP before formatting the argument, and uses Display formatting for that argument.

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.

Examples

Struct and Enum Formatting examples below.

Basic

use const_panic::{ArrayString, FmtArg, flatten_panicvals};


assert_eq!(
    ArrayString::<999>::from_panicvals(
        // Formatting literals
        &flatten_panicvals!(FmtArg::DEBUG; 100u8, "hello")
    ).unwrap(),
    "100hello"
);

assert_eq!(
    ArrayString::<999>::from_panicvals(
        // Formatting non-literals.
        // `"foo"` is considered a non-literal, because it's inside other tokens.
        &flatten_panicvals!(FmtArg::ALT_DEBUG; ("foo"), [100u8, 200])
    ).unwrap(),
    concat!(
        "\"foo\"[\n",
        "    100,\n",
        "    200,\n",
        "]",
    )
);

assert_eq!(
    ArrayString::<999>::from_panicvals(&
        // Alternate-Debug Formatting composite types.
        flatten_panicvals!(
            FmtArg::ALT_DEBUG;
            Foo => Foo(3, "5"),
            ", ",
            Bar => Bar{x: "hello"}
        )
    ).unwrap(),
    concat!(
        "Foo(\n",
        "    3,\n",
        "    \"5\",\n",
        "), Bar {\n",
        "    x: \"hello\",\n",
        "}",
    )
);

assert_eq!(
    ArrayString::<999>::from_panicvals(&
        // Overriding the formatting of arguments.
        //
        // The `open` and `close` overrides are demonstrated in the
        // struct and enum examples.
        flatten_panicvals!(
            FmtArg::DEBUG;
            Foo => display: Foo(3, "5"),
            debug: ", ",
            Bar => Bar{x: "hello"}
        )
    ).unwrap(),
    r#"Foo(3, 5)", "Bar { x: "hello" }"#
);



struct Foo(u32, &'static str);

const_panic::impl_panicfmt!{
    struct Foo(u32, &'static str);
}

struct Bar {
    x: &'static str,
}

const_panic::impl_panicfmt!{
    struct Bar {
        x: &'static str,
    }
}

Struct formatting

Implementing panic formatting for braced and tuple structs.

use const_panic::{
    fmt::{self, FmtArg, PanicFmt, ComputePvCount},
    ArrayString, PanicVal,
    flatten_panicvals,
};

fn main(){
    let foo = Foo {
        x: &[3, 5, 8, 13],
        y: 21,
        z: Bar(false, true),
    };
     
    assert_eq!(
        ArrayString::<100>::from_panicvals(&foo.to_panicvals(FmtArg::DEBUG)).unwrap(),
        "Foo { x: [3, 5, 8, 13], y: 21, z: Bar(false, true) }",
    );
    assert_eq!(
        ArrayString::<200>::from_panicvals(&foo.to_panicvals(FmtArg::ALT_DEBUG)).unwrap(),
        concat!(
            "Foo {\n",
            "    x: [\n",
            "        3,\n",
            "        5,\n",
            "        8,\n",
            "        13,\n",
            "    ],\n",
            "    y: 21,\n",
            "    z: Bar(\n",
            "        false,\n",
            "        true,\n",
            "    ),\n",
            "}",
        ),
    );
}



struct Foo<'a> {
    x: &'a [u8],
    y: u8,
    z: Bar,
}

struct Bar(bool, bool);


impl PanicFmt for Foo<'_> {
    type This = Self;
    type Kind = const_panic::IsCustomType;

    // `ComputePvCount` allows computing the length of the array of `PanicVal`s
    // returned by `Foo::to_panicvals` below.
    //
    // Note that ComputePvCount only calculates the correct number if you
    // follow the pattern in this example.
    const PV_COUNT: usize = ComputePvCount{
        field_amount: 3,
        summed_pv_count: <&[u8]>::PV_COUNT
            + <u8>::PV_COUNT
            + <Bar>::PV_COUNT,
        delimiter: fmt::TypeDelim::Braced,
    }.call();
}

impl<'a> Foo<'a> {
    const fn to_panicvals(&self, fmtarg: FmtArg) -> [PanicVal<'a>; Foo::PV_COUNT] {
        // These constants from `fmt` add newlines and padding
        // when the `fmtarg.is_alternate` flag is enabled,
        // to match the standard behavior for `Debug` formatting.
        flatten_panicvals! {fmtarg;
            "Foo",
            // the `open:` format override increments `fmtarg.indentation`
            // by `const_panic::fmt::INDENTATION_STEP` spaces.
            // The indentation field is used by these constants when the
            // `fmtarg.is_alternate` flag is enabled.
            open: fmt::OpenBrace,
                // fmt::COMMA_SEP must only be used between fields
                "x: ", &[u8] => self.x, fmt::COMMA_SEP,
                "y: ", u8 => self.y, fmt::COMMA_SEP,
                // fmt::COMMA_TERM must only be used after the last field
                "z: ", Bar => self.z, fmt::COMMA_TERM,
            // the `close:` format override decrements the indentation.
            close: fmt::CloseBrace,
        }
    }
}

impl PanicFmt for Bar {
    type This = Self;
    type Kind = const_panic::IsCustomType;

    const PV_COUNT: usize = ComputePvCount{
        field_amount: 2,
        summed_pv_count: <bool>::PV_COUNT * 2,
        delimiter: fmt::TypeDelim::Tupled,
    }.call();
}

impl Bar {
    const fn to_panicvals(&self, f: FmtArg) -> [PanicVal<'static>; Bar::PV_COUNT] {
        flatten_panicvals! {f;
            "Bar",
            open: fmt::OpenParen,
                // fmt::COMMA_SEP must only be used between fields
                self.0, fmt::COMMA_SEP,
                // fmt::COMMA_TERM must only be used after the last field
                self.1, fmt::COMMA_TERM,
            close: fmt::CloseParen,
        }
    }
}

Enum Formatting

This example demonstrates formatting of generic enum types.

use const_panic::{
    fmt::{self, FmtArg, PanicFmt, ComputePvCount},
    ArrayString, PanicVal,
    flatten_panicvals,
};

fn main() {
    let up: Qux<u8> = Qux::Up;
    // Debug formatting the Up variant
    assert_eq!(
        ArrayString::<100>::from_panicvals(&up.to_panicvals(FmtArg::DEBUG)).unwrap(),
        "Up",
    );


    let down: Qux<u16> = Qux::Down { x: 21, y: 34, z: 55 };
    // Debug formatting the Down variant
    assert_eq!(
        ArrayString::<100>::from_panicvals(&down.to_panicvals(FmtArg::DEBUG)).unwrap(),
        "Down { x: 21, y: 34, z: 55 }",
    );
    // Alternate-Debug formatting the Down variant
    assert_eq!(
        ArrayString::<100>::from_panicvals(&down.to_panicvals(FmtArg::ALT_DEBUG)).unwrap(),
        concat!(
            "Down {\n",
            "    x: 21,\n",
            "    y: 34,\n",
            "    z: 55,\n",
            "}",
        )
    );


    let left: Qux<u32> = Qux::Left(89);
    // Debug formatting the Left variant
    assert_eq!(
        ArrayString::<100>::from_panicvals(&left.to_panicvals(FmtArg::DEBUG)).unwrap(),
        "Left(89)",
    );
    // Alternate-Debug formatting the Left variant
    assert_eq!(
        ArrayString::<100>::from_panicvals(&left.to_panicvals(FmtArg::ALT_DEBUG)).unwrap(),
        concat!(
            "Left(\n",
            "    89,\n",
            ")",
        )
    );
}

enum Qux<T> {
    Up,
    Down { x: T, y: T, z: T },
    Left(u64),
}


impl<T: PanicFmt> PanicFmt for Qux<T> {
    type This = Self;
    type Kind = const_panic::IsCustomType;

    const PV_COUNT: usize = {
        // `ComputePvCount` computes the length of the array of `PanicVal`s
        // produced by each variant.
        //
        // `slice_max_usize` returns the maximum usize in a slice.
        // In this case, to return the longest array produced by the variants.
        const_panic::utils::slice_max_usize(&[
            ComputePvCount{
                field_amount: 0,
                summed_pv_count: 0,
                delimiter: fmt::TypeDelim::Braced,
            }.call(),
            ComputePvCount{
                field_amount: 3,
                summed_pv_count: <T>::PV_COUNT * 3,
                delimiter: fmt::TypeDelim::Braced,
            }.call(),
            ComputePvCount{
                field_amount: 1,
                summed_pv_count: <u64>::PV_COUNT,
                delimiter: fmt::TypeDelim::Tupled,
            }.call(),
        ])
    };
}

// Because of limitations of stable const evaluation,
// you have to use macros to implement the `to_panicvals` method
// for more than one concrete type (ignoring lifetimes).
//
// This macro implements panic formatting for
// - `Qux<u8>`
// - `Qux<u16>`
// - `Qux<u32>`
const_panic::inline_macro! {
    (u8),
    (u16),
    (u32);

    ($T:ty) =>

    impl Qux<$T> {
        pub const fn to_panicvals(
            &self,
            fmtarg: FmtArg,
        ) -> [PanicVal<'static>; <Qux<$T>>::PV_COUNT] {
            match self {
                Self::Up =>
                    // The `<Qux<$T>>::PV_COUNT` argument tells `flatten_panicvals` to
                    // create an array of that length.
                    // Variants that would otherwise produce shorter arrays
                    // pad that array with trailing `PanicVal::EMPTY`.
                    flatten_panicvals! {fmtarg, <Qux<$T>>::PV_COUNT;
                        "Up"
                    },
                Self::Down{x, y, z} =>
                    // These constants from `fmt` add newlines and padding
                    // when the `fmtarg.is_alternate` flag is enabled,
                    // to match the standard behavior for `Debug` formatting.
                    flatten_panicvals! {fmtarg, <Qux<$T>>::PV_COUNT;
                        "Down",
                        // the `open:` format override increments `fmtarg.indentation`
                        // by `const_panic::fmt::INDENTATION_STEP` spaces.
                        // The indentation field is used by these constants when the
                        // `fmtarg.is_alternate` flag is enabled.
                        open: fmt::OpenBrace,
                            // fmt::COMMA_SEP must only be used between fields
                            "x: ", x, fmt::COMMA_SEP,
                            "y: ", y, fmt::COMMA_SEP,
                            // fmt::COMMA_TERM must only be used after the last field
                            "z: ", z, fmt::COMMA_TERM,
                        close: fmt::CloseBrace,
                    },
                Self::Left(x) => flatten_panicvals! {fmtarg, <Qux<$T>>::PV_COUNT;
                    "Left",
                    open: fmt::OpenParen,
                        x, fmt::COMMA_TERM,
                    close: fmt::CloseParen,
                },
            }
        }
    }
}