1#[cfg_attr(feature = "step_trait", doc = "# #![feature(step_trait)]")]
18#[cfg_attr(feature = "step_trait", doc = "# #![feature(step_trait)]")]
36#[cfg_attr(feature = "step_trait", doc = "# #![feature(step_trait)]")]
55#[cfg_attr(feature = "step_trait", doc = "# #![feature(step_trait)]")]
75#[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 ($(#[$($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 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}