solar_data_structures/
index.rs

1//! Index types. See [`::index_vec`].
2
3use std::fmt;
4
5pub use index_vec::{
6    Idx, IdxRangeBounds, IdxSliceIndex, IndexBox, IndexSlice, IndexVec, index_box, index_vec,
7};
8
9/// Creates a new index to use with [`::index_vec`].
10#[macro_export]
11macro_rules! newtype_index {
12    () => {};
13    ($(#[$attr:meta])* $vis:vis struct $name:ident; $($rest:tt)*) => {
14        $(#[$attr])*
15        #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16        #[repr(transparent)]
17        $vis struct $name($crate::index::BaseIndex32);
18
19        impl std::fmt::Debug for $name {
20            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21                write!(f, "{}({:?})", stringify!($name), self.get())
22            }
23        }
24
25        impl $crate::index::Idx for $name {
26            #[inline(always)]
27            fn from_usize(value: usize) -> Self {
28                Self::from_usize(value)
29            }
30
31            #[inline(always)]
32            fn index(self) -> usize {
33                self.index()
34            }
35        }
36
37        impl $name {
38            /// The maximum index value.
39            $vis const MAX: Self = Self($crate::index::BaseIndex32::MAX);
40
41            #[doc = "Creates a new `"]
42            #[doc = stringify!($name)]
43            #[doc = "` from the given `value`."]
44            ///
45            /// # Panics
46            ///
47            /// Panics if `value` exceeds `MAX`.
48            #[inline(always)]
49            #[must_use]
50            #[cfg_attr(debug_assertions, track_caller)]
51            $vis const fn new(value: u32) -> Self {
52                Self($crate::index::BaseIndex32::new(value))
53            }
54
55            #[doc = "Creates a new `"]
56            #[doc = stringify!($name)]
57            #[doc = "` from the given `value`."]
58            ///
59            /// # Safety
60            ///
61            /// The caller must ensure that `value` is less than or equal to `MAX`.
62            #[inline(always)]
63            #[must_use]
64            $vis const unsafe fn new_unchecked(value: u32) -> Self {
65                Self(unsafe { $crate::index::BaseIndex32::new_unchecked(value) })
66            }
67
68            #[doc = "Creates a new `"]
69            #[doc = stringify!($name)]
70            #[doc = "` from the given `value`."]
71            ///
72            /// Returns `None` if `value` exceeds `MAX`.
73            #[inline(always)]
74            #[must_use]
75            $vis const fn try_new(value: u32) -> Option<Self> {
76                match $crate::index::BaseIndex32::try_new(value) {
77                    Some(value) => Some(Self(value)),
78                    None => None,
79                }
80            }
81
82            #[doc = "Creates a new `"]
83            #[doc = stringify!($name)]
84            #[doc = "` from the given `value`."]
85            ///
86            /// # Panics
87            ///
88            /// Panics if `value` exceeds `MAX`.
89            #[inline(always)]
90            #[must_use]
91            #[cfg_attr(debug_assertions, track_caller)]
92            $vis const fn from_usize(value: usize) -> Self {
93                Self($crate::index::BaseIndex32::from_usize(value))
94            }
95
96            #[doc = "Creates a new `"]
97            #[doc = stringify!($name)]
98            #[doc = "` from the given `value`."]
99            ///
100            /// # Safety
101            ///
102            /// The caller must ensure that `value` is less than or equal to `MAX`.
103            #[inline(always)]
104            #[must_use]
105            $vis const unsafe fn from_usize_unchecked(value: usize) -> Self {
106                Self(unsafe { $crate::index::BaseIndex32::from_usize_unchecked(value) })
107            }
108
109            #[doc = "Creates a new `"]
110            #[doc = stringify!($name)]
111            #[doc = "` from the given `value`."]
112            ///
113            /// Returns `None` if `value` exceeds `MAX`.
114            #[inline(always)]
115            #[must_use]
116            $vis const fn try_from_usize(value: usize) -> Option<Self> {
117                match $crate::index::BaseIndex32::try_from_usize(value) {
118                    Some(value) => Some(Self(value)),
119                    None => None,
120                }
121            }
122
123            /// Returns the underlying index value.
124            #[inline(always)]
125            #[must_use]
126            $vis const fn get(self) -> u32 {
127                self.0.get()
128            }
129
130            /// Returns the underlying index value.
131            #[inline(always)]
132            #[must_use]
133            $vis const fn index(self) -> usize {
134                self.0.index()
135            }
136        }
137
138        $crate::newtype_index!($($rest)*);
139    };
140}
141
142// NOTE: The max MUST be less than the maximum value of the underlying integer.
143macro_rules! base_index {
144    ($(#[$attr:meta])* $name:ident($primitive:ident <= $max:literal)) => {
145        /// A specialized wrapper around a primitive number.
146        ///
147        $(#[$attr])*
148        #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
149        #[cfg_attr(feature = "nightly", rustc_layout_scalar_valid_range_end($max))]
150        #[cfg_attr(feature = "nightly", rustc_nonnull_optimization_guaranteed)]
151        #[cfg_attr(feature = "nightly", rustc_pass_by_value)]
152        #[repr(transparent)]
153        pub struct $name {
154            // NOTE: Use `value()` instead of projecting the field directly.
155            #[cfg(feature = "nightly")]
156            value: $primitive,
157            #[cfg(not(feature = "nightly"))]
158            value: std::num::NonZero<$primitive>,
159        }
160
161        impl fmt::Debug for $name {
162            #[inline]
163            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164                self.get().fmt(f)
165            }
166        }
167
168        impl Idx for $name {
169            #[inline(always)]
170            fn from_usize(value: usize) -> Self {
171                Self::from_usize(value)
172            }
173
174            #[inline(always)]
175            fn index(self) -> usize {
176                self.index()
177            }
178        }
179
180        impl $name {
181            /// The maximum index value, as the underlying primitive type.
182            pub const MAX_AS: $primitive = $max;
183
184            /// The maximum index value.
185            pub const MAX: Self = Self::new(Self::MAX_AS);
186
187            #[doc = "Creates a new `"]
188            #[doc = stringify!($name)]
189            #[doc = "` from the given `value`."]
190            ///
191            /// # Panics
192            ///
193            /// Panics if `value` exceeds `MAX`.
194            #[inline(always)]
195            #[must_use]
196            #[cfg_attr(debug_assertions, track_caller)]
197            pub const fn new(value: $primitive) -> Self {
198                match Self::try_new(value) {
199                    Some(value) => value,
200                    None => index_overflow(),
201                }
202            }
203
204            #[doc = "Creates a new `"]
205            #[doc = stringify!($name)]
206            #[doc = "` from the given `value`."]
207            ///
208            /// Returns `None` if `value` exceeds `MAX`.
209            #[inline(always)]
210            #[must_use]
211            pub const fn try_new(value: $primitive) -> Option<Self> {
212                if value > Self::MAX_AS {
213                    None
214                } else {
215                    // SAFETY: `value` is less than or equal to `MAX`.
216                    Some(unsafe { Self::new_unchecked(value) })
217                }
218            }
219
220            #[doc = "Creates a new `"]
221            #[doc = stringify!($name)]
222            #[doc = "` from the given `value`."]
223            ///
224            /// # Panics
225            ///
226            /// Panics if `value` exceeds `MAX`.
227            #[inline(always)]
228            #[must_use]
229            #[cfg_attr(debug_assertions, track_caller)]
230            pub const fn from_usize(value: usize) -> Self {
231                match Self::try_from_usize(value) {
232                    Some(value) => value,
233                    None => index_overflow(),
234                }
235            }
236
237            #[doc = "Creates a new `"]
238            #[doc = stringify!($name)]
239            #[doc = "` from the given `value`."]
240            ///
241            /// Returns `None` if `value` exceeds `MAX`.
242            #[inline(always)]
243            #[must_use]
244            pub const fn try_from_usize(value: usize) -> Option<Self> {
245                if value > Self::MAX_AS as usize {
246                    None
247                } else {
248                    // SAFETY: `value` is less than or equal to `MAX`.
249                    Some(unsafe { Self::new_unchecked(value as $primitive) })
250                }
251            }
252
253            #[doc = "Creates a new `"]
254            #[doc = stringify!($name)]
255            #[doc = "` from the given `value`, without checking for overflow."]
256            ///
257            /// # Safety
258            ///
259            /// The caller must ensure that `value` is less than or equal to `MAX`.
260            #[inline(always)]
261            #[must_use]
262            pub const unsafe fn new_unchecked(value: $primitive) -> Self {
263                debug_assert!(value <= Self::MAX_AS);
264
265                // SAFETY: guaranteed by the caller.
266                #[cfg(feature = "nightly")]
267                return unsafe { std::intrinsics::transmute_unchecked(value) };
268
269                #[cfg(not(feature = "nightly"))]
270                return unsafe { Self { value: std::num::NonZero::new_unchecked(value.unchecked_add(1)) } };
271            }
272
273
274            #[doc = "Creates a new `"]
275            #[doc = stringify!($name)]
276            #[doc = "` from the given `value`, without checking for overflow."]
277            ///
278            /// # Safety
279            ///
280            /// The caller must ensure that `value` is less than or equal to `MAX`.
281            #[inline(always)]
282            #[must_use]
283            pub const unsafe fn from_usize_unchecked(value: usize) -> Self {
284                debug_assert!(value <= Self::MAX_AS as usize);
285                unsafe { Self::new_unchecked(value as $primitive) }
286            }
287
288            /// Returns the underlying index value.
289            #[inline(always)]
290            #[must_use]
291            pub const fn get(self) -> $primitive {
292                // SAFETY: Transmute instead of projecting the field directly.
293                //
294                // See:
295                // - https://github.com/rust-lang/rust/pull/133651
296                // - https://github.com/rust-lang/compiler-team/issues/807
297                #[cfg(feature = "nightly")]
298                return unsafe { std::intrinsics::transmute_unchecked(self) };
299
300                // SAFETY: non-zero minus one doesn't overflow.
301                #[cfg(not(feature = "nightly"))]
302                return unsafe { self.value.get().unchecked_sub(1) };
303            }
304
305            /// Returns the underlying index value.
306            #[inline(always)]
307            #[must_use]
308            pub const fn index(self) -> usize {
309                self.get() as usize
310            }
311        }
312    };
313}
314
315base_index!(BaseIndex32(u32 <= 0xFFFF_FF00));
316
317#[inline(never)]
318#[cold]
319#[cfg_attr(debug_assertions, track_caller)]
320const fn index_overflow() -> ! {
321    panic!("index overflowed")
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327
328    #[test]
329    fn base_index() {
330        assert_eq!(BaseIndex32::new(0).get(), 0);
331        assert_eq!(BaseIndex32::new(1).get(), 1);
332        assert_eq!(BaseIndex32::MAX.get(), BaseIndex32::MAX_AS);
333        assert_eq!(BaseIndex32::MAX.get(), 0xFFFF_FF00);
334        assert_eq!(BaseIndex32::new(0xFFFF_FF00).get(), 0xFFFF_FF00);
335    }
336}