compile_fmt/
macros.rs

1//! Formatting macros.
2
3/// Concatenates arguments in compile time.
4///
5/// # Specifying arguments
6///
7/// Arguments to this macro must be comma-separated. The argument type must be supported; for now,
8/// the supported types are:
9///
10/// - Signed and unsigned integers (`u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64`, `u128`,
11///   `i128`, `usize`, `isize`)
12/// - Strings (`&str`)
13/// - [`Ascii`](crate::Ascii) strings
14/// - Chars (`char`)
15/// - References to [`CompileArgs`](crate::CompileArgs).
16///
17/// Due to how Rust type inference works, you might need to specify the type suffix for integer
18/// literals (e.g., `42_usize` instead of `42`).
19///
20/// Optionally, an argument may specify its [format](crate::Fmt) as `$arg => $fmt`.
21/// A format is mandatory if the argument is not a constant; e.g. if it is an argument or a local variable
22/// in a `const fn`.
23///
24/// The value output by the macro is [`CompileArgs`](crate::CompileArgs).
25///
26/// # Specifying capacity
27///
28/// You can specify capacity of the returned `CompileArgs` by prefacing arguments with `capacity: $cap,`.
29/// Here, `$cap` is a constant expression of type `usize`. The specified capacity must be greater or equal
30/// that the capacity inferred from the arguments; the macro will fail with a compilation error otherwise.
31///
32/// # See also
33///
34/// - [`compile_panic!`](crate::compile_panic) provides a version of the `panic!` macro with support
35///   of dynamic arguments.
36/// - [`compile_assert!`](crate::compile_assert) provides a version of the `assert!` macro with support
37///   of dynamic arguments.
38///
39/// # Examples
40///
41/// ## Basic usage
42///
43/// ```
44/// # use compile_fmt::{compile_args, CompileArgs};
45/// const ARGS: CompileArgs<9> = compile_args!(2_u32, " + ", 2_u32, " = ", 2_u32 + 2);
46/// assert_eq!(ARGS.as_str(), "2 + 2 = 4");
47/// ```
48///
49/// ## Usage in `const fn` with dynamic args
50///
51/// ```
52/// use compile_fmt::{compile_args, fmt};
53/// use std::fmt;
54///
55/// const fn create_args(x: usize) -> impl fmt::Display {
56///     let args = compile_args!(
57///         "2 * x + 3 = ", 2 * x + 3 => fmt::<usize>()
58///     );
59///     // ^ `args` are evaluated in compile time, but are not a constant.
60///     // They can still be useful e.g. for creating compile-time panic messages.
61///     assert!(x < 1000, "{}", args.as_str());
62///     args
63/// }
64///
65/// let args = create_args(100);
66/// assert_eq!(args.to_string(), "2 * x + 3 = 203");
67/// ```
68///
69/// ## Usage with explicit capacity
70///
71/// ```
72/// # use compile_fmt::compile_args;
73/// let args = compile_args!(capacity: 16, "Value: ", 42_i32);
74/// assert_eq!(args.as_str(), "Value: 42");
75/// ```
76///
77/// Insufficient specified capacity will lead to a compilation error:
78///
79/// ```compile_fail
80/// # use compile_fmt::compile_args;
81/// let args = compile_args!(capacity: 4, "Value: ", 42_i32);
82/// ```
83///
84/// The error message will include details about the necessary capacity:
85///
86/// ```text
87/// error[E0080]: evaluation of constant value failed
88///    -->
89///     |
90///     |     let args = compile_args!(capacity: 4, "Value: ", 42_i32);
91///     |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
92/// the evaluated program panicked at 'Insufficient capacity (4 bytes)
93/// provided for `compile_args` macro; it requires at least 9 bytes'
94/// ```
95#[macro_export]
96macro_rules! compile_args {
97    (capacity: $cap:expr, $($arg:expr $(=> $fmt:expr)?),+) => {{
98        const __CAPACITY: usize = $cap;
99        const _: () = {
100            let required_capacity = $crate::__compile_args_impl!(@total_capacity $($arg $(=> $fmt)?,)+);
101            $crate::CompileArgs::<__CAPACITY>::assert_capacity(required_capacity);
102        };
103        $crate::CompileArgs::<__CAPACITY>::format(&[
104            $($crate::ArgumentWrapper::new($arg)$(.with_fmt($fmt))?.into_argument(),)+
105        ]) as $crate::CompileArgs<__CAPACITY>
106        // ^ The type hint sometimes helps in const contexts
107    }};
108    ($($arg:expr $(=> $fmt:expr)?),+) => {{
109        const __CAPACITY: usize = $crate::__compile_args_impl!(@total_capacity $($arg $(=> $fmt)?,)+);
110        $crate::CompileArgs::<__CAPACITY>::format(&[
111            $($crate::ArgumentWrapper::new($arg)$(.with_fmt($fmt))?.into_argument(),)+
112        ]) as $crate::CompileArgs<__CAPACITY>
113        // ^ The type hint sometimes helps in const contexts
114    }};
115}
116
117#[doc(hidden)] // implementation detail of `compile_args`
118#[macro_export]
119macro_rules! __compile_args_impl {
120    (@total_capacity $first_arg:expr $(=> $first_fmt:expr)?, $($arg:expr $(=> $fmt:expr)?,)*) => {
121        $crate::__compile_args_impl!(@arg_capacity $first_arg $(=> $first_fmt)?)
122            $(+ $crate::__compile_args_impl!(@arg_capacity $arg $(=> $fmt)?))*
123    };
124    (@arg_capacity $arg:expr) => {
125        $crate::ArgumentWrapper::new($arg).into_argument().formatted_len()
126    };
127    (@arg_capacity $arg:expr => $fmt:expr) => {
128        $crate::Fmt::capacity(&$fmt)
129    };
130}
131
132/// Version of the [`panic!`] macro with the ability to format args in compile time.
133///
134/// Arguments have the same syntax as in the [`compile_args!`] macro.
135///
136/// # Examples
137///
138/// ```
139/// use compile_fmt::{compile_panic, clip};
140///
141/// const fn unwrap_result(res: Result<(), &str>) {
142///     if let Err(err) = res {
143///         compile_panic!("Encountered an error: ", err => clip(64, "…"));
144///     }
145/// }
146/// ```
147#[macro_export]
148macro_rules! compile_panic {
149    ($($arg:tt)+) => {
150        ::core::panic!("{}", $crate::compile_args!($($arg)+).as_str());
151    };
152}
153
154/// Version of the [`assert!`] macro with the ability to format args in compile time.
155///
156/// The first argument of the macro must be a boolean value. The remaining arguments have the same syntax
157/// as in the [`compile_args!`] macro.
158///
159/// # Examples
160///
161/// ```
162/// use compile_fmt::{compile_assert, fmt};
163///
164/// const fn check_args(x: usize, s: &str) {
165///     const MAX_STR_LEN: usize = 10;
166///
167///     compile_assert!(
168///         x < 1_000,
169///         "`x` should be less than 1000 (got: ",
170///         x => fmt::<usize>(), ")"
171///     );
172///     compile_assert!(
173///         s.len() <= MAX_STR_LEN,
174///         "String is too long (expected at most ", MAX_STR_LEN,
175///         " bytes; got ", s.len() => fmt::<usize>(), " bytes)"
176///     );
177///     // main logic...
178/// }
179/// ```
180#[macro_export]
181macro_rules! compile_assert {
182    ($check:expr, $($arg:tt)+) => {{
183        if !$check {
184            ::core::panic!("{}", $crate::compile_args!($($arg)+).as_str());
185        }
186    }};
187}