bounded_integer/
macro.rs

1/// Generate a bounded integer type.
2///
3/// It takes in single struct or enum, with the content being a bounded range expression, whose
4/// upper bound can be inclusive (`x, y`) or exclusive (`x, y`). The attributes and visibility
5/// (e.g. `pub`) of the type are forwarded directly to the output type.
6///
7/// If the type is a struct and the bounded integer's range does not include zero,
8/// the struct will have a niche at zero,
9/// allowing for `Option<BoundedInteger>` to be the same size as `BoundedInteger` itself.
10///
11/// See the [`examples`](crate::examples) module for examples of what this macro generates.
12///
13/// # Examples
14///
15/// With a struct:
16/// ```
17#[cfg_attr(feature = "step_trait", doc = "# #![feature(step_trait)]")]
18/// # mod force_item_scope {
19/// # use bounded_integer::bounded_integer;
20/// bounded_integer! {
21///     pub struct S(2, 5);
22/// }
23/// # }
24/// ```
25/// The generated item should look like this (u8 is chosen as it is the smallest repr):
26/// ```
27/// #[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
28/// #[repr(transparent)]
29/// pub struct S(u8);
30/// ```
31/// And the methods will ensure that `2 ≤ S.0 ≤ 5`.
32///
33/// With an enum:
34/// ```
35#[cfg_attr(feature = "step_trait", doc = "# #![feature(step_trait)]")]
36/// # mod force_item_scope {
37/// # use bounded_integer::bounded_integer;
38/// bounded_integer! {
39///     pub enum S(-1, 1);
40/// }
41/// # }
42/// ```
43/// The generated item should look like this (i8 is chosen as it is the smallest repr):
44/// ```
45/// #[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
46/// #[repr(i8)]
47/// pub enum S {
48///     N1 = -1, Z, P1
49/// }
50/// ```
51///
52/// You can also ascribe dedicated names to the enum variants:
53/// ```
54#[cfg_attr(feature = "step_trait", doc = "# #![feature(step_trait)]")]
55/// # use bounded_integer::bounded_integer;
56/// bounded_integer! {
57///     pub enum Sign {
58///         Negative = -1,
59///         Zero,
60///         Positive,
61///     }
62/// }
63/// assert_eq!(Sign::Negative.get(), -1);
64/// assert_eq!(Sign::new(1).unwrap(), Sign::Positive);
65/// ```
66///
67/// # Custom repr
68///
69/// The item can have a `repr` attribute to specify how it will be represented in memory, which can
70/// be a `u*` or `i*` type. In this example we override the `repr` to be a `u16`, when it would
71/// have normally been a `u8`.
72///
73/// ```
74#[cfg_attr(feature = "step_trait", doc = "# #![feature(step_trait)]")]
75/// # mod force_item_scope {
76/// # use bounded_integer::bounded_integer;
77/// bounded_integer! {
78///     #[repr(u16)]
79///     pub struct S(2, 4);
80/// }
81/// # }
82/// ```
83/// The generated item should look like this:
84/// ```
85/// #[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
86/// #[repr(transparent)]
87/// pub struct S(u16);
88/// ```
89#[macro_export]
90macro_rules! bounded_integer {
91    (
92        $(#![$($outer_attr:tt)*])*
93        $(#[$($attr:tt)*])*
94        $(pub $(($($vis:tt)*))?)? struct $name:ident($min:expr, $max:expr);
95    ) => {
96        $crate::__helper! { validate_attrs
97            [$([$($outer_attr)*])*]
98            [$([$($attr)*])*]
99            [$(pub $(($($vis)*))?)?]
100            [-] [struct] [$name] [$min] [$max]
101            [$crate]
102        }
103    };
104    (
105        $(#![$($outer_attr:tt)*])*
106        $(#[$($attr:tt)*])*
107        $(pub $(($($vis:tt)*))?)? enum $name:ident($min:expr, $max:expr);
108    ) => {
109        $crate::__helper! { validate_attrs
110            [$([$($outer_attr)*])*]
111            [$([$($attr)*])*]
112            [$(pub $(($($vis)*))?)?]
113            [-] [enum] [$name] [$min] [$max]
114            [$crate]
115        }
116    };
117    (
118        $(#![$($outer_attr:tt)*])*
119        $(#[$($attr:tt)*])*
120        $(pub $(($($vis:tt)*))?)? enum $name:ident {
121            $($(#[$($var_attr:tt)*])* $variant:ident $(= $val:literal)?),* $(,)?
122        }
123    ) => {
124        $crate::__helper! { validate_attrs
125            [$([$($outer_attr)*])*]
126            [$([$($attr)*])*]
127            [$(pub $(($($vis)*))?)?]
128            [+] [enum] [$name] [$([[$(#[$($var_attr)*])*] $variant [$($val)?]])*] []
129            [$crate]
130        }
131    };
132    // Migration
133    ($(#[$($attr:tt)*])* $vis:vis struct $name:ident { $($_:tt)* }) => {
134        compile_error!(concat!(
135            "syntax has changed; use `struct ",
136            stringify!($name),
137            "(MIN, MAX);` instead.",
138        ));
139    };
140    ($(#[$($attr:tt)*])* $vis:vis enum $name:ident { $($_:tt)* }) => {
141        compile_error!(concat!(
142            "syntax has changed; use `enum ",
143            stringify!($name),
144            "(MIN, MAX);` instead.",
145        ));
146    };
147}
148
149#[macro_export]
150#[cfg(feature = "zerocopy")]
151#[doc(hidden)]
152macro_rules! __dispatch_zerocopy {
153    ($outer_attr:tt [$($attr:tt)*] $($t:tt)*) => {
154        $crate::__helper! { vis
155            [+]
156            $outer_attr
157            [
158                $($attr)*
159                [derive($crate::__private::zerocopy::IntoBytes)]
160                [derive($crate::__private::zerocopy::Immutable)]
161            ]
162            $($t)*
163        }
164    };
165}
166#[macro_export]
167#[cfg(not(feature = "zerocopy"))]
168#[doc(hidden)]
169macro_rules! __dispatch_zerocopy {
170    ($($t:tt)*) => {
171        $crate::__helper! { vis [-] $($t)* }
172    };
173}
174
175#[macro_export]
176#[doc(hidden)]
177macro_rules! __helper {
178    (validate_attrs [$($outer_attr:tt)*] [$($attr:tt)*] $($t:tt)*) => {
179        $crate::__dispatch_zerocopy! { [$(#$outer_attr)*] [$($attr)*] $($t)* }
180        $($crate::__helper! { validate_attr $outer_attr })*
181        $($crate::__helper! { validate_attr $attr })*
182    };
183    (validate_attr [doc = $($_:tt)*]) => {};
184    (validate_attr [repr($($_:tt)*)]) => {};
185    (validate_attr [allow($($_:tt)*)]) => {};
186    (validate_attr [expect($($_:tt)*)]) => {};
187    (validate_attr [warn($($_:tt)*)]) => {};
188    (validate_attr [deny($($_:tt)*)]) => {};
189    (validate_attr [forbid($($_:tt)*)]) => {};
190    (validate_attr [deprecated$(($($_:tt)*))?]) => {};
191    (validate_attr [must_use]) => {};
192    (validate_attr [cfg_attr($cfg:meta, $($attr:tt)*)]) => { $crate::__helper! { validate_attr [$($attr)*] } };
193    (validate_attr [$($attr:tt)*]) => {
194        ::core::compile_error!("for soundness reasons, custom attributes are not allowed");
195    };
196    (vis $zerocopy:tt $outer_attr:tt $attr:tt [$(pub($(in)? self))?] $($t:tt)*) => {
197        $crate::__private::proc_macro! { $zerocopy $outer_attr $attr [] [pub(super)] $($t)* }
198    };
199    (vis $zerocopy:tt $outer_attr:tt $attr:tt [pub] $($t:tt)*) => {
200        $crate::__private::proc_macro! { $zerocopy $outer_attr $attr [pub] [pub] $($t)* }
201    };
202    (vis $zerocopy:tt $outer_attr:tt $attr:tt [pub($(in)? crate$(::$($path:ident)::+)?)] $($t:tt)*) => {
203        $crate::__private::proc_macro! { $zerocopy $outer_attr $attr [pub(in crate$(::$($path)::+)?)] [pub(in crate$(::$($path)::+)?)] $($t)* }
204    };
205    (vis $zerocopy:tt $outer_attr:tt $attr:tt [pub(super)] $($t:tt)*) => {
206        $crate::__private::proc_macro! { $zerocopy $outer_attr $attr [pub(super)] [pub(in super::super)] $($t)* }
207    };
208    (vis $zerocopy:tt $outer_attr:tt $attr:tt [pub(in $($path:ident)::+)] $($t:tt)*) => {
209        $crate::__private::proc_macro! { $zerocopy $outer_attr $attr [pub(in $($path)::+)] [pub(in super::$($path)::+)] $($t)* }
210    };
211}
212
213#[cfg(test)]
214mod tests {
215    use crate::bounded_integer;
216
217    #[test]
218    fn all_below_zero() {
219        bounded_integer!(#[expect(unused)] struct Struct(-400, -203););
220        bounded_integer!(#[expect(unused)] enum Enum(-500, -483););
221    }
222
223    #[test]
224    fn outer_attrs() {
225        bounded_integer!(#![expect(double_negations)] #[expect(unused)] struct S(--1, 4_i8););
226    }
227
228    #[test]
229    fn publicity() {
230        mod a {
231            #![expect(unused)]
232            bounded_integer!(struct A(0, 0););
233            bounded_integer!(pub(self) struct B(0, 0););
234            bounded_integer!(pub(in self) struct C(0, 0););
235            mod c {
236                bounded_integer!(pub(in super) struct D(0, 0););
237            }
238            pub(super) use c::*;
239        }
240        #[expect(unused)]
241        use a::*;
242        mod b {
243            bounded_integer!(pub(super) struct A(0, 0););
244            bounded_integer!(pub(crate) struct B(0, 0););
245            bounded_integer!(pub(in crate::r#macro) struct C(0, 0););
246            mod c {
247                bounded_integer!(pub(in super::super) struct D(0, 0););
248            }
249            pub(super) use c::*;
250        }
251        use b::*;
252        // would cause an ambiguity error if the number of reachable items above is >1
253        A::default();
254        B::default();
255        C::default();
256        D::default();
257    }
258
259    #[test]
260    fn inferred_reprs() {
261        bounded_integer!(struct ByteStruct(0, 255););
262        const _: u8 = ByteStruct::MIN_VALUE;
263        bounded_integer!(enum ByteEnum(0, 255););
264        const _: u8 = ByteEnum::MIN_VALUE;
265
266        bounded_integer!(struct U16Struct(0, 256););
267        const _: u16 = U16Struct::MIN_VALUE;
268        bounded_integer!(enum U16Enum(0, 256););
269        const _: u16 = U16Enum::MIN_VALUE;
270
271        bounded_integer!(struct I16Struct(-1, 255););
272        const _: i16 = I16Struct::MIN_VALUE;
273        bounded_integer!(enum I16Enum(-1, 255););
274        const _: i16 = I16Enum::MIN_VALUE;
275
276        bounded_integer!(struct SignedByteStruct(-128, 127););
277        const _: i8 = SignedByteStruct::MIN_VALUE;
278        bounded_integer!(struct SignedByteEnum(-128, 127););
279        const _: i8 = SignedByteEnum::MIN_VALUE;
280    }
281
282    #[test]
283    fn simple_enum() {
284        bounded_integer!(enum M(-3, 2););
285        assert_eq!(M::MIN_VALUE, -3);
286        assert_eq!(M::MAX_VALUE, 2);
287        assert_eq!(M::N3.get(), -3);
288        assert_eq!(M::N2.get(), -2);
289        assert_eq!(M::N1.get(), -1);
290        assert_eq!(M::Z.get(), 0);
291        assert_eq!(M::P1.get(), 1);
292        assert_eq!(M::P2.get(), 2);
293        assert_eq!(M::N3 as i8, -3);
294        assert_eq!(M::N2 as i8, -2);
295        assert_eq!(M::N1 as i8, -1);
296        assert_eq!(M::Z as i8, 0);
297        assert_eq!(M::P1 as i8, 1);
298        assert_eq!(M::P2 as i8, 2);
299
300        bounded_integer!(
301            enum X {
302                A = -1,
303                B,
304                C = 1,
305                D,
306            }
307        );
308        assert_eq!(X::A.get(), -1);
309        assert_eq!(X::B.get(), 0);
310        assert_eq!(X::C.get(), 1);
311        assert_eq!(X::D.get(), 2_i8);
312
313        bounded_integer!(
314            enum Y {
315                A = 4_294_967_295,
316            }
317        );
318        assert_eq!(Y::A.get(), 4_294_967_295_u32);
319
320        bounded_integer!(
321            enum Z {
322                A = 4_294_967_295,
323                B,
324            }
325        );
326        assert_eq!(Z::A.get(), 4_294_967_295_u64);
327        assert_eq!(Z::B.get(), 4_294_967_296_u64);
328    }
329
330    #[test]
331    fn zeroable() {
332        #[cfg(all(feature = "bytemuck1", feature = "zerocopy", feature = "num-traits02"))]
333        fn assert_zeroable<T>()
334        where
335            T: Default + bytemuck1::Zeroable + zerocopy::FromZeros + num_traits02::Zero,
336        {
337        }
338        #[cfg(not(all(feature = "bytemuck1", feature = "zerocopy", feature = "num-traits02")))]
339        fn assert_zeroable<T: Default>() {}
340        #[expect(unused)]
341        trait NotZeroable<const N: usize> {}
342        impl<T: Default> NotZeroable<0> for T {}
343        #[cfg(feature = "bytemuck1")]
344        impl<T: bytemuck1::Zeroable> NotZeroable<1> for T {}
345        #[cfg(feature = "zerocopy")]
346        impl<T: zerocopy::FromZeros> NotZeroable<2> for T {}
347        #[cfg(feature = "num-traits02")]
348        impl<T: num_traits02::Zero> NotZeroable<3> for T {}
349        macro_rules! not_zeroable {
350            ($t:ty) => {
351                impl NotZeroable<0> for $t {}
352                #[cfg(feature = "bytemuck1")]
353                impl NotZeroable<1> for $t {}
354                #[cfg(feature = "zerocopy")]
355                impl NotZeroable<2> for $t {}
356                #[cfg(feature = "num-traits02")]
357                impl NotZeroable<3> for $t {}
358            };
359        }
360
361        bounded_integer!(struct A(0, 0););
362        assert_zeroable::<A>();
363
364        bounded_integer!(struct B(-459, 3););
365        assert_zeroable::<B>();
366
367        bounded_integer!(struct C(1, 5););
368        not_zeroable!(C);
369        assert_eq!(size_of::<Option<C>>(), size_of::<C>());
370
371        bounded_integer!(struct D(-5, -1););
372        not_zeroable!(D);
373        assert_eq!(size_of::<Option<D>>(), size_of::<D>());
374
375        bounded_integer!(struct E(-(5), 0_i8););
376        not_zeroable!(E);
377        assert_ne!(size_of::<Option<E>>(), size_of::<E>());
378    }
379
380    #[test]
381    #[cfg(feature = "num-traits02")]
382    fn one() {
383        fn assert_one<T: num_traits02::One>() {}
384        bounded_integer!(struct A(1, 1););
385        assert_one::<A>();
386    }
387
388    #[test]
389    #[cfg(feature = "zerocopy")]
390    fn unaligned() {
391        fn assert_unaligned<T: zerocopy::Unaligned>() {}
392        bounded_integer!(struct A(0, 255););
393        assert_unaligned::<A>();
394        bounded_integer!(struct B(-128, 127););
395        assert_unaligned::<B>();
396
397        #[expect(unused)]
398        trait NotUnaligned {}
399        impl<T: zerocopy::Unaligned> NotUnaligned for T {}
400
401        bounded_integer!(struct C(255, 256););
402        bounded_integer!(struct D(-129, -128););
403        impl NotUnaligned for C {}
404        impl NotUnaligned for D {}
405    }
406
407    #[test]
408    fn lit_radix() {
409        #![expect(clippy::mixed_case_hex_literals)]
410
411        bounded_integer!(struct Hex(0x5, 0xf_F););
412        assert_eq!(Hex::MIN_VALUE, 5);
413        assert_eq!(Hex::MAX_VALUE, 255);
414
415        bounded_integer!(struct Oct(0o35, 0o40););
416        assert_eq!(Oct::MIN_VALUE, 29);
417        assert_eq!(Oct::MAX_VALUE, 32);
418
419        bounded_integer!(struct Bin(0b1101, 0b11101););
420        assert_eq!(Bin::MIN_VALUE, 0b1101);
421        assert_eq!(Bin::MAX_VALUE, 0b11101);
422    }
423
424    #[test]
425    fn repr_without_repr() {
426        bounded_integer!(#[expect(unused)] struct Owo(0_u8, 2 + 2););
427        bounded_integer!(#[expect(unused)] struct Uwu(-57 * 37, 8_i64););
428    }
429
430    #[test]
431    fn allowed_attrs() {
432        #![expect(deprecated)]
433        use crate::bounded_integer;
434
435        bounded_integer! {
436            #[cfg_attr(all(), doc = "…")]
437            #[deprecated]
438            #[must_use]
439            struct S(0, 255);
440        }
441
442        #[expect(deprecated, unused_must_use)]
443        S::new(0).unwrap();
444    }
445
446    #[test]
447    fn complex_expr() {
448        bounded_integer!(#[repr(u8)] struct S(0, 10 + 10););
449        assert_eq!(S::MIN_VALUE, 0);
450        assert_eq!(S::MAX_VALUE, 20);
451    }
452}