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) => 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<T, const LEN: usize>(args: &[&[PanicVal<'_>]]) -> Result<T, 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;
270
271#[cfg(feature = "test")]
272use crate::test_utils::TestString;
273
274#[doc(hidden)]
275#[cfg(feature = "test")]
276pub fn format_panic_message<const LEN: usize>(
277    args: &[&[PanicVal<'_>]],
278    capacity: usize,
279    max_capacity: usize,
280) -> Result<TestString<LEN>, NotEnoughSpace> {
281    let mut buffer = [0u8; LEN];
282    let mut len = 0usize;
283    {
284        // intentionally shadowed
285        let buffer = &mut buffer[..capacity];
286
287        make_buffer_writer_macros! {buffer, len}
288
289        write_to_buffer! {
290            args
291            (
292                len, capacity, max_capacity, Err(NotEnoughSpace),
293                write_buffer, write_buffer_checked,
294            )
295        }
296    }
297
298    Ok(TestString { buffer, len })
299}
300
301#[cfg(feature = "non_basic")]
302#[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))]
303#[doc(hidden)]
304pub(crate) const fn make_panic_string<const LEN: usize>(
305    args: &[&[PanicVal<'_>]],
306) -> Result<crate::ArrayString<LEN>, NotEnoughSpace> {
307    let mut buffer = [0u8; LEN];
308    let mut len = 0usize;
309
310    make_buffer_writer_macros! {buffer, len}
311
312    write_to_buffer! {
313        args
314        (len, LEN, LEN + 1, Err(NotEnoughSpace), write_buffer, write_buffer_checked,)
315    }
316
317    assert!(len as u32 as usize == len, "the panic message is too large");
318
319    Ok(crate::ArrayString {
320        buffer,
321        len: len as u32,
322    })
323}
324
325#[cfg(feature = "non_basic")]
326#[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))]
327#[doc(hidden)]
328#[track_caller]
329pub const fn make_panic_string_unwrapped<const LEN: usize>(
330    args: &[&[PanicVal<'_>]],
331) -> crate::ArrayString<LEN> {
332    match make_panic_string(args) {
333        Ok(x) => x,
334        Err(_) => panic!("arguments are too large to fit in LEN"),
335    }
336}
337
338#[cfg(feature = "non_basic")]
339#[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))]
340#[doc(hidden)]
341pub const fn compute_length(args: &[&[PanicVal<'_>]]) -> usize {
342    let mut len = 0usize;
343
344    macro_rules! add_to_len {
345        ($value:expr) => {{
346            let _: u8 = $value;
347            len += 1;
348        }};
349    }
350
351    write_to_buffer! {
352        args
353        (
354            len, usize::MAX - 1, usize::MAX, usize::MAX,
355            add_to_len, add_to_len,
356        )
357    }
358
359    len
360}