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}