const_panic/
concat_panic_.rs

1use crate::{
2    fmt::FmtKind,
3    panic_val::{PanicClass, PanicVal, StrFmt},
4    utils::{bytes_up_to, string_cap, WasTruncated},
5};
6
7/// Panics by concatenating the argument slice.
8///
9/// This is the function that the [`concat_panic`](macro@concat_panic) macro calls to panic.
10///
11/// # Example
12///
13/// Here's how to panic with formatting without using any macros:
14///
15/// ```compile_fail
16/// use const_panic::{FmtArg, PanicVal, concat_panic};
17///
18/// const _: () = concat_panic(&[&[
19///     PanicVal::write_str("\nthe error was "),
20///     PanicVal::from_u8(100, FmtArg::DISPLAY),
21///     PanicVal::write_str(" and "),
22///     PanicVal::from_str("\nHello\tworld", FmtArg::DEBUG),
23/// ]]);
24///
25///
26/// ```
27/// That fails to compile with this error message:
28/// ```text
29/// error[E0080]: evaluation of constant value failed
30///   --> src/concat_panic_.rs:13:15
31///    |
32/// 6  |   const _: () = concat_panic(&[&[
33///    |  _______________^
34/// 7  | |     PanicVal::write_str("\nthe error was "),
35/// 8  | |     PanicVal::from_u8(100, FmtArg::DISPLAY),
36/// 9  | |     PanicVal::write_str(" and "),
37/// 10 | |     PanicVal::from_str("\nHello\tworld", FmtArg::DEBUG),
38/// 11 | | ]]);
39///    | |___^ the evaluated program panicked at '
40/// the error was 100 and "\nHello\tworld"', src/concat_panic_.rs:6:15
41/// ```
42///
43#[cold]
44#[inline(never)]
45#[track_caller]
46pub const fn concat_panic(args: &[&[PanicVal<'_>]]) -> ! {
47    // The panic message capacity starts small and gets larger each time,
48    // so that platforms with smaller stacks can call this at runtime.
49    //
50    // Also, given that most(?) panic messages are smaller than 1024 bytes long,
51    // it's not going to be any less efficient in the common case.
52    if let Err(_) = panic_inner::<1024>(args) {}
53
54    if let Err(_) = panic_inner::<{ 1024 * 6 }>(args) {}
55
56    match panic_inner::<MAX_PANIC_MSG_LEN>(args) {
57        Ok(x) => match x {},
58        Err(_) => panic!(
59            "\
60            unreachable:\n\
61            the `write_panicval_to_buffer` macro must not return Err when \
62            $capacity == $max_capacity\
63        "
64        ),
65    }
66}
67
68/// The maximum length of panic messages (in bytes),
69/// after which the message is truncated.
70// this should probably be smaller on platforms where this
71// const fn is called at runtime, and the stack is finy.
72pub const MAX_PANIC_MSG_LEN: usize = 32768;
73
74// writes a single PanicVal to an array
75macro_rules! write_panicval {
76    (
77        $outer_label:lifetime,
78        $mout:ident, $lout:ident, $tct:expr,
79        (
80            $len:expr,
81            $capacity:expr,
82            $max_capacity:expr,
83            $not_enough_space:expr,
84            $write_buffer:ident,
85            $write_buffer_checked:ident,
86        )
87    ) => {
88        let rem_space = $capacity - $len;
89        let (strfmt, class, was_truncated) = $tct;
90        let StrFmt {
91            leftpad: mut lpad,
92            rightpad: mut rpad,
93            fmt_kind,
94        } = strfmt;
95
96        let ranged = match class {
97            PanicClass::PreFmt(str) => str,
98            PanicClass::Int(int) => {
99                if int.len() <= string_cap::MEDIUM {
100                    $mout = int.fmt::<{ string_cap::MEDIUM }>();
101                    $mout.ranged()
102                } else {
103                    $lout = int.fmt::<{ string_cap::LARGE }>();
104                    $lout.ranged()
105                }
106            }
107            #[cfg(feature = "non_basic")]
108            PanicClass::Slice(_) => unreachable!(),
109        };
110
111        let trunc_end = ranged.start + was_truncated.get_length(ranged.len());
112
113        while lpad != 0 {
114            $write_buffer! {b' '}
115            lpad -= 1;
116        }
117
118        if let FmtKind::Display = fmt_kind {
119            let mut i = ranged.start;
120            while i < trunc_end {
121                $write_buffer! {ranged.bytes[i]}
122                i += 1;
123            }
124        } else if rem_space != 0 {
125            $write_buffer! {b'"'}
126            let mut i = 0;
127            while i < trunc_end {
128                use crate::debug_str_fmt::{hex_as_ascii, ForEscaping};
129
130                let c = ranged.bytes[i];
131                let mut written_c = c;
132                if ForEscaping::is_escaped(c) {
133                    $write_buffer! {b'\\'}
134                    if ForEscaping::is_backslash_escaped(c) {
135                        written_c = ForEscaping::get_backslash_escape(c);
136                    } else {
137                        $write_buffer! {b'x'}
138                        $write_buffer! {hex_as_ascii(c >> 4)}
139                        written_c = hex_as_ascii(c & 0b1111);
140                    };
141                }
142                $write_buffer! {written_c}
143
144                i += 1;
145            }
146            if let WasTruncated::No = was_truncated {
147                $write_buffer_checked! {b'"'}
148            }
149        }
150
151        while rpad != 0 {
152            $write_buffer! {b' '}
153            rpad -= 1;
154        }
155
156        if let WasTruncated::Yes(_) = was_truncated {
157            if $capacity < $max_capacity {
158                return $not_enough_space;
159            } else {
160                break $outer_label;
161            }
162        }
163    };
164}
165
166macro_rules! write_to_buffer_inner {
167    (
168        $args:ident
169        (
170            $len:expr,
171            $capacity:expr,
172            $($_rem:tt)*
173        )
174        $wptb_args:tt
175    ) => {
176        let mut args = $args;
177
178        let mut mout;
179        let mut lout;
180
181        'outer: while let [mut outer, ref nargs @ ..] = args {
182            while let [arg, nouter @ ..] = outer {
183                let tct = arg.to_class_truncated($capacity - $len);
184                match tct.1 {
185                    #[cfg(feature = "non_basic")]
186                    #[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))]
187                    PanicClass::Slice(slice) => {
188                        let mut iter = slice.iter();
189
190                        'iter: loop {
191                            let (two_args, niter) = iter.next();
192
193                            let mut two_args: &[_] = &two_args;
194                            while let [arg, ntwo_args @ ..] = two_args {
195                                let tct = arg.to_class_truncated($capacity - $len);
196                                write_panicval! {'outer, mout, lout, tct, $wptb_args}
197                                two_args = ntwo_args;
198                            }
199
200                            match niter {
201                                Some(x) => iter = x,
202                                None => break 'iter,
203                            }
204                        }
205                    }
206                    _ => {
207                        write_panicval! {'outer, mout, lout, tct, $wptb_args}
208                    }
209                }
210
211                outer = nouter;
212            }
213            args = nargs;
214        }
215    };
216}
217
218macro_rules! write_to_buffer {
219    ($args:ident $wptb_args:tt) => {
220        write_to_buffer_inner! {
221            $args
222            $wptb_args
223            $wptb_args
224        }
225    };
226}
227
228macro_rules! make_buffer_writer_macros {
229    ($buffer:ident, $len:ident) => {
230        macro_rules! write_buffer {
231            ($value:expr) => {
232                __write_array! {$buffer, $len, $value}
233            };
234        }
235        macro_rules! write_buffer_checked {
236            ($value:expr) => {
237                __write_array_checked! {$buffer, $len, $value}
238            };
239        }
240    };
241}
242
243#[cold]
244#[inline(never)]
245#[track_caller]
246const fn panic_inner<const LEN: usize>(args: &[&[PanicVal<'_>]]) -> Result<Never, NotEnoughSpace> {
247    let mut buffer = [0u8; LEN];
248    let mut len = 0usize;
249
250    make_buffer_writer_macros! {buffer, len}
251
252    write_to_buffer! {
253        args
254        (
255            len, LEN, MAX_PANIC_MSG_LEN, Err(NotEnoughSpace),
256            write_buffer, write_buffer_checked,
257        )
258    }
259
260    unsafe {
261        let buffer = bytes_up_to(&buffer, len);
262        let str = core::str::from_utf8_unchecked(buffer);
263        panic!("{}", str)
264    }
265}
266
267#[doc(hidden)]
268#[derive(Debug)]
269pub struct NotEnoughSpace;
270enum Never {}
271
272#[cfg(feature = "test")]
273use crate::test_utils::TestString;
274
275#[doc(hidden)]
276#[cfg(feature = "test")]
277pub fn format_panic_message<const LEN: usize>(
278    args: &[&[PanicVal<'_>]],
279    capacity: usize,
280    max_capacity: usize,
281) -> Result<TestString<LEN>, NotEnoughSpace> {
282    let mut buffer = [0u8; LEN];
283    let mut len = 0usize;
284    {
285        // intentionally shadowed
286        let buffer = &mut buffer[..capacity];
287
288        make_buffer_writer_macros! {buffer, len}
289
290        write_to_buffer! {
291            args
292            (
293                len, capacity, max_capacity, Err(NotEnoughSpace),
294                write_buffer, write_buffer_checked,
295            )
296        }
297    }
298
299    Ok(TestString { buffer, len })
300}
301
302#[cfg(feature = "non_basic")]
303#[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))]
304#[doc(hidden)]
305pub(crate) const fn make_panic_string<const LEN: usize>(
306    args: &[&[PanicVal<'_>]],
307) -> Result<crate::ArrayString<LEN>, NotEnoughSpace> {
308    let mut buffer = [0u8; LEN];
309    let mut len = 0usize;
310
311    make_buffer_writer_macros! {buffer, len}
312
313    write_to_buffer! {
314        args
315        (len, LEN, LEN + 1, Err(NotEnoughSpace), write_buffer, write_buffer_checked,)
316    }
317
318    assert!(len as u32 as usize == len, "the panic message is too large");
319
320    Ok(crate::ArrayString {
321        buffer,
322        len: len as u32,
323    })
324}
325
326#[cfg(feature = "non_basic")]
327#[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))]
328#[doc(hidden)]
329#[track_caller]
330pub const fn make_panic_string_unwrapped<const LEN: usize>(
331    args: &[&[PanicVal<'_>]],
332) -> crate::ArrayString<LEN> {
333    match make_panic_string(args) {
334        Ok(x) => x,
335        Err(_) => panic!("arguments are too large to fit in LEN"),
336    }
337}
338
339#[cfg(feature = "non_basic")]
340#[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))]
341#[doc(hidden)]
342pub const fn compute_length(args: &[&[PanicVal<'_>]]) -> usize {
343    let mut len = 0usize;
344
345    macro_rules! add_to_len {
346        ($value:expr) => {{
347            let _: u8 = $value;
348            len += 1;
349        }};
350    }
351
352    write_to_buffer! {
353        args
354        (
355            len, usize::MAX - 1, usize::MAX, usize::MAX,
356            add_to_len, add_to_len,
357        )
358    }
359
360    len
361}