linearize/macro.rs
1use {
2 crate::{Linearize, StaticMap},
3 core::{mem::MaybeUninit, ptr},
4};
5
6#[doc(hidden)]
7#[macro_export]
8macro_rules! static_map_internal_wrapper {
9 ($builder_name:ident, $builder_val:expr, $($tt:tt)*) => {{
10 let mut $builder_name = $builder_val;
11 if false {
12 unsafe {
13 // SAFETY: This call is guarded by `if false` and therefore unreachable.
14 ::core::hint::unreachable_unchecked();
15 #[deny(unfulfilled_lint_expectations)]
16 #[expect(unreachable_code)]
17 // SAFETY: unreachable_unchecked returns !, therefore this line is
18 // unreachable.
19 //
20 // NOTE: This branch exists so that type inference is dominated by the
21 // return value of this function. Otherwise the inference of the key
22 // parameter would be dominated by the match body which is bad UX.
23 $builder_name.get()
24 }
25 } else {
26 $($tt)*
27 }
28 }};
29}
30
31#[doc(hidden)]
32#[macro_export]
33macro_rules! static_map_internal {
34 ($builder_name:ident, $builder_val:expr, $i:ident, $val:ident, $get_key:expr, $set_value:expr, $($tt:tt)*) => {{
35 $crate::static_map_internal_wrapper! {
36 $builder_name,
37 $builder_val,
38 let mut $i = 0;
39 let len = $builder_name.len();
40 while $i < len {
41 struct PleaseDoNotUseBreakWithoutLabel;
42 let please_do_not_use_continue_without_label;
43 let $val;
44 loop {
45 please_do_not_use_continue_without_label = ();
46 let key = $get_key;
47 $val = match key {
48 $($tt)*
49 };
50 break PleaseDoNotUseBreakWithoutLabel;
51 };
52 let _ = please_do_not_use_continue_without_label;
53 $set_value;
54 $i += 1;
55 }
56 unsafe {
57 // SAFETY:
58 // - The loop { } around the $tt ensures that no control flow
59 // statement in $tt can interact with the for i loop unless it
60 // early-exits this macro entirely.
61 // - Therefore, if we reach this line, the builder.set line was reached
62 // for each i in 0..builder.len() which is L::LENGTH.
63 $builder_name.get()
64 }
65 }
66 }};
67}
68
69/// Macro to create a [StaticMap](StaticMap).
70///
71/// # Example
72///
73/// ```rust
74/// # use linearize::static_map;
75/// let map = static_map! {
76/// false => 0,
77/// true => 0,
78/// };
79/// ```
80///
81/// # Variants
82///
83/// This macro has three variants:
84///
85/// 1. ```rust
86/// macro_rules! static_map {
87/// ($($tt:tt)*) => { /* ... */ }
88/// }
89/// ```
90///
91/// The body of the macro invocation should be the body of a match statement. It will be
92/// called once for each possible variant. For example:
93///
94/// ```rust
95/// # use linearize::{StaticMap, static_map};
96/// let map: StaticMap<u8, u32> = static_map! {
97/// n if n % 2 == 0 => n as u32 / 2,
98/// n => 3 * n as u32 + 1,
99/// };
100/// ```
101///
102/// Disadvantages:
103///
104/// - Cannot be used in constants or statics.
105/// - It must be possible to move out of the values on the right-hand-side.
106/// The following example does not compile:
107///
108/// ```rust,compile_fail
109/// # use linearize::{StaticMap, static_map};
110/// let on_false = "this is false".to_string();
111/// let on_true = "this is true".to_string();
112/// let map = static_map! {
113/// false => on_false,
114/// true => on_true,
115/// };
116/// ```
117/// 2. ```rust
118/// macro_rules! static_map {
119/// (of type $ty:ty: $($tt:tt)*) => { /* ... */ }
120/// }
121/// ```
122///
123/// The body of the macro invocation should be the body of a match statement. It will be
124/// called once for each possible variant. For example:
125///
126#[cfg_attr(more_const_functions, doc = " ```rust")]
127#[cfg_attr(not(more_const_functions), doc = " ```rust,ignore")]
128/// # use linearize::{StaticMap, static_map, Linearize};
129/// #[derive(Linearize)]
130/// #[linearize(const)]
131/// enum Key {
132/// False,
133/// True,
134/// }
135///
136/// const MAP: StaticMap<Key, u32> = static_map! {
137/// of type Key:
138/// Key::False => 0,
139/// Key::True => 1,
140/// };
141/// assert_eq!(MAP[Key::False], 0);
142/// assert_eq!(MAP[Key::True], 1);
143#[doc = " ```"]
144///
145/// Disadvantages:
146///
147/// - Requires rust 1.83 or later.
148/// - The key type must be a concrete type. This variant cannot be used in code that is
149/// generic over the key type.
150/// - Cannot be used with keys containing any of the core types `bool`, `u8`, etc.
151/// - Can only be used with keys that use the derive macro and enable the `linearize(const)`
152/// feature.
153/// - It must be possible to move out of the values on the right-hand-side.
154///
155/// Advantages:
156///
157/// - Can be used in constants and statics.
158/// 3. ```rust
159/// macro_rules! static_map {
160/// (constants of type $ty:ty: $($key:expr => $val:expr),*$(,)?) => { /* ... */ }
161/// }
162/// ```
163///
164/// The body of the macro invocation should be an exhaustive map from constant keys to values.
165/// For example:
166///
167#[cfg_attr(more_const_functions, doc = " ```rust")]
168#[cfg_attr(not(more_const_functions), doc = " ```rust,ignore")]
169/// # use linearize::{StaticMap, static_map, Linearize};
170/// #[derive(Linearize)]
171/// #[linearize(const)]
172/// enum Key {
173/// False,
174/// True,
175/// }
176///
177/// let on_false = "this is false".to_string();
178/// let on_true = "this is true".to_string();
179///
180/// let map = static_map! {
181/// constants of type Key:
182/// Key::False => on_false,
183/// Key::True => on_true,
184/// };
185///
186/// assert_eq!(map[Key::False], "this is false");
187/// assert_eq!(map[Key::True], "this is true");
188#[doc = " ```"]
189///
190/// Disadvantages:
191///
192/// - Requires rust 1.83 or later.
193/// - The key type must be a concrete type. This variant cannot be used in code that is
194/// generic over the key type.
195/// - Cannot be used with keys containing any of the core types `bool`, `u8`, etc.
196/// - Can only be used with keys that use the derive macro and enable the `linearize(const)`
197/// feature.
198/// - The keys must be constants.
199///
200/// Advantages:
201///
202/// - Can be used in constants and statics.
203/// - Each value will only be accessed once, allowing them to move out of variables.
204#[macro_export]
205macro_rules! static_map {
206 (constants of type $ty:ty: $($key:expr => $val:expr),*$(,)?) => {
207 $crate::static_map_internal_wrapper! {
208 builder,
209 $crate::Builder::<$ty, _>::new(),
210 const {
211 let mut init = [false; <$ty as $crate::Linearize>::LENGTH];
212 $(
213 let i = <$ty>::__linearize_d66aa8fa_6974_4651_b2b7_75291a9e7105(&$key);
214 init[i] = true;
215 )*
216 let mut i = 0;
217 while i < <$ty as $crate::Linearize>::LENGTH {
218 if !init[i] {
219 core::panic!("Not all keys are initialized");
220 }
221 i += 1;
222 }
223 }
224 const fn write<T>(builder: &mut $crate::Builder<$ty, T>, i: usize, v: T) {
225 unsafe {
226 // SAFETY:
227 // - StaticMap<$ty, T> is a transparent wrapper around $ty::Storage<$ty, T>.
228 // - $ty::Storage<$ty, T> is required to be [T; $ty::LENGTH].
229 // - Therefore, builder.0.as_mut_ptr() is morally a dereferencable
230 // mut pointer to [MaybeUninit<T>; $ty::LENGTH].
231 // - i is $key.__linearize_d66aa8fa_6974_4651_b2b7_75291a9e7105().
232 // - The const block above would panic if i >= $ty::LENGTH.
233 // - Therefore i < $ty::LENGTH and the `add` is in bounds.
234 // - And the pointer is aligned an in bounds for `write`.
235 core::ptr::write(builder.0.as_mut_ptr().cast::<T>().add(i), v);
236 }
237 }
238 $(
239 let i = <$ty>::__linearize_d66aa8fa_6974_4651_b2b7_75291a9e7105(&$key);
240 write(&mut builder, i, $val);
241 )*
242 unsafe {
243 // SAFETY:
244 // - The const block above proves that, init[i] == true for all
245 // i < $ty::LENGTH.
246 // - Initially init[i] == false for all i.
247 // - init[i] is set to true iff there is at least one $key that linearizes
248 // to i.
249 // - Above we call write(linearize($key)) for each $key.
250 // - The body of write initializes the i'th element of the array.
251 builder.get()
252 }
253 }
254 };
255 (of type $ty:ty: $($tt:tt)*) => {
256 $crate::static_map_internal! {
257 builder,
258 $crate::Builder::<$ty, _>::new(),
259 i,
260 val,
261 unsafe {
262 // SAFETY: i is less than builder.len() which is L::LENGTH.
263 <$ty>::__from_linear_unchecked_fb2f0b31_5b5a_48b4_9264_39d0bdf94f1d(i)
264 },
265 {
266 const fn write<T>(builder: &mut $crate::Builder<$ty, T>, i: usize, v: T) {
267 unsafe {
268 // SAFETY:
269 // - StaticMap<$ty, T> is a transparent wrapper around L::Storage<$ty, T>.
270 // - $ty::Storage<$ty, T> is required to be [T; $ty::LENGTH].
271 // - Therefore, builder.0.as_mut_ptr() is morally a dereferencable
272 // mut pointer to [MaybeUninit<T>; $ty::LENGTH].
273 // - Therefore, since i < $ty::LENGTH, the `add` is in bounds.
274 // - And the pointer is aligned an in bounds for `write`.
275 core::ptr::write(builder.0.as_mut_ptr().cast::<T>().add(i), v);
276 }
277 }
278 write(&mut builder, i, val);
279 },
280 $($tt)*
281 }
282 };
283 ($($tt:tt)*) => {
284 $crate::static_map_internal! {
285 builder,
286 $crate::Builder::new(),
287 i,
288 val,
289 unsafe {
290 // SAFETY: i is less than builder.len() which is L::LENGTH.
291 builder.key(i)
292 },
293 unsafe {
294 // SAFETY: i is less than builder.len() which is L::LENGTH.
295 builder.set(i, val);
296 },
297 $($tt)*
298 }
299 };
300}
301
302/// Macro to create a [StaticCopyMap](crate::StaticCopyMap).
303///
304/// This macro is a thin wrapper around [static_map](crate::static_map). The behavior is
305/// identical except that is creates a [StaticCopyMap](crate::StaticCopyMap) instead of
306/// a [StaticMap].
307#[macro_export]
308macro_rules! static_copy_map {
309 (constants of type $ty:ty: $($key:expr => $val:expr),*$(,)?) => {
310 $crate::StaticCopyMap($crate::static_map!(constants of type $ty: $($key => $val,)*).0)
311 };
312 (of type $ty:ty: $($tt:tt)*) => {
313 $crate::StaticCopyMap($crate::static_map!(of type $ty: $($tt)*).0)
314 };
315 ($($tt:tt)*) => {
316 $crate::StaticCopyMap::from_static_map($crate::static_map!($($tt)*))
317 };
318}
319
320/// A builder for a [`StaticMap`].
321///
322/// This type should only be used via the [`static_map!`] macro.
323pub struct Builder<L, T>(pub MaybeUninit<StaticMap<L, T>>)
324where
325 L: Linearize;
326
327impl<L, T> Builder<L, T>
328where
329 L: Linearize,
330{
331 /// Creates a new builder.
332 #[allow(clippy::new_without_default)]
333 #[inline]
334 pub const fn new() -> Self {
335 Self(MaybeUninit::uninit())
336 }
337
338 /// Returns [`L::LENGTH`].
339 #[allow(clippy::len_without_is_empty)]
340 #[inline]
341 pub const fn len(&self) -> usize {
342 L::LENGTH
343 }
344
345 /// Returns [`L::from_linear_unchecked(i)`](L::from_linear_unchecked).
346 ///
347 /// # Safety
348 ///
349 /// Same as [`L::from_linear_unchecked`].
350 #[inline]
351 pub unsafe fn key(&self, i: usize) -> L {
352 unsafe {
353 // SAFETY: The requirements are forwarded to the caller.
354 L::from_linear_unchecked(i)
355 }
356 }
357
358 /// Sets the `i`th element of the map.
359 ///
360 /// # Safety
361 ///
362 /// `i` must be less than [`L::LENGTH`].
363 #[inline]
364 pub unsafe fn set(&mut self, i: usize, v: T) {
365 unsafe {
366 // SAFETY:
367 // - StaticMap<L, T> is a transparent wrapper around L::Storage<L, T>.
368 // - L::Storage<L, T> is required to be [T; L::LENGTH].
369 // - Therefore, self.0.as_mut_ptr() is morally a dereferencable mut pointer to
370 // [MaybeUninit<T>; L::LENGTH].
371 // - Therefore, since i < L::LENGTH, the `add` is in bounds.
372 // - And the pointer is aligned an in bounds for `write`.
373 ptr::write(self.0.as_mut_ptr().cast::<T>().add(i), v);
374 }
375 }
376
377 /// # Safety
378 ///
379 /// All elements of the array must have initialized before calling this function.
380 ///
381 /// [`Self::set`] can be used to initialized an element.
382 #[inline]
383 pub const unsafe fn get(self) -> StaticMap<L, T> {
384 unsafe {
385 // SAFETY:
386 // - StaticMap<L, T> is a transparent wrapper around L::Storage<L, T>.
387 // - L::Storage<L, T> is required to be [T; L::LENGTH].
388 // - Therefore, self.0 is morally [MaybeUninit<T>; L::LENGTH].
389 // - self.set(i) initializes the ith element of this array.
390 // - By the requirements of this function, every element of the array has been
391 // initialized.
392 self.0.assume_init()
393 }
394 }
395
396 /// Returns `StaticMap::<L, bool>::default()`.
397 pub fn init_map(&self) -> StaticMap<L, bool> {
398 StaticMap::default()
399 }
400}