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}