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 #[test]
216 fn all_below_zero() {
217 bounded_integer!(#[expect(unused)] struct Struct(-400, -203););
218 bounded_integer!(#[expect(unused)] enum Enum(-500, -483););
219 }
220
221 #[test]
222 fn outer_attrs() {
223 bounded_integer!(#![expect(double_negations)] #[expect(unused)] struct S(--1, 4_i8););
224 }
225
226 #[test]
227 fn publicity() {
228 mod a {
229 #![expect(unused)]
230 bounded_integer!(struct A(0, 0););
231 bounded_integer!(pub(self) struct B(0, 0););
232 bounded_integer!(pub(in self) struct C(0, 0););
233 mod c {
234 bounded_integer!(pub(in super) struct D(0, 0););
235 }
236 pub(super) use c::*;
237 }
238 #[expect(unused)]
239 use a::*;
240 mod b {
241 bounded_integer!(pub(super) struct A(0, 0););
242 bounded_integer!(pub(crate) struct B(0, 0););
243 bounded_integer!(pub(in crate::r#macro) struct C(0, 0););
244 mod c {
245 bounded_integer!(pub(in super::super) struct D(0, 0););
246 }
247 pub(super) use c::*;
248 }
249 use b::*;
250 A::default();
252 B::default();
253 C::default();
254 D::default();
255 }
256
257 #[test]
258 fn inferred_reprs() {
259 bounded_integer!(struct ByteStruct(0, 255););
260 const _: u8 = ByteStruct::MIN_VALUE;
261 bounded_integer!(enum ByteEnum(0, 255););
262 const _: u8 = ByteEnum::MIN_VALUE;
263
264 bounded_integer!(struct U16Struct(0, 256););
265 const _: u16 = U16Struct::MIN_VALUE;
266 bounded_integer!(enum U16Enum(0, 256););
267 const _: u16 = U16Enum::MIN_VALUE;
268
269 bounded_integer!(struct I16Struct(-1, 255););
270 const _: i16 = I16Struct::MIN_VALUE;
271 bounded_integer!(enum I16Enum(-1, 255););
272 const _: i16 = I16Enum::MIN_VALUE;
273
274 bounded_integer!(struct SignedByteStruct(-128, 127););
275 const _: i8 = SignedByteStruct::MIN_VALUE;
276 bounded_integer!(struct SignedByteEnum(-128, 127););
277 const _: i8 = SignedByteEnum::MIN_VALUE;
278 }
279
280 #[test]
281 fn simple_enum() {
282 bounded_integer!(enum M(-3, 2););
283 assert_eq!(M::MIN_VALUE, -3);
284 assert_eq!(M::MAX_VALUE, 2);
285 assert_eq!(M::N3.get(), -3);
286 assert_eq!(M::N2.get(), -2);
287 assert_eq!(M::N1.get(), -1);
288 assert_eq!(M::Z.get(), 0);
289 assert_eq!(M::P1.get(), 1);
290 assert_eq!(M::P2.get(), 2);
291 assert_eq!(M::N3 as i8, -3);
292 assert_eq!(M::N2 as i8, -2);
293 assert_eq!(M::N1 as i8, -1);
294 assert_eq!(M::Z as i8, 0);
295 assert_eq!(M::P1 as i8, 1);
296 assert_eq!(M::P2 as i8, 2);
297
298 bounded_integer!(
299 enum X {
300 A = -1,
301 B,
302 C = 1,
303 D,
304 }
305 );
306 assert_eq!(X::A.get(), -1);
307 assert_eq!(X::B.get(), 0);
308 assert_eq!(X::C.get(), 1);
309 assert_eq!(X::D.get(), 2_i8);
310
311 bounded_integer!(
312 enum Y {
313 A = 4_294_967_295,
314 }
315 );
316 assert_eq!(Y::A.get(), 4_294_967_295_u32);
317
318 bounded_integer!(
319 enum Z {
320 A = 4_294_967_295,
321 B,
322 }
323 );
324 assert_eq!(Z::A.get(), 4_294_967_295_u64);
325 assert_eq!(Z::B.get(), 4_294_967_296_u64);
326 }
327
328 #[test]
329 fn zeroable() {
330 #[cfg(all(feature = "bytemuck1", feature = "zerocopy", feature = "num-traits02"))]
331 fn assert_zeroable<T>()
332 where
333 T: Default + bytemuck1::Zeroable + zerocopy::FromZeros + num_traits02::Zero,
334 {
335 }
336 #[cfg(not(all(feature = "bytemuck1", feature = "zerocopy", feature = "num-traits02")))]
337 fn assert_zeroable<T: Default>() {}
338 #[allow(unused)]
339 trait NotZeroable<const N: usize> {}
340 impl<T: Default> NotZeroable<0> for T {}
341 #[cfg(feature = "bytemuck1")]
342 impl<T: bytemuck1::Zeroable> NotZeroable<1> for T {}
343 #[cfg(feature = "zerocopy")]
344 impl<T: zerocopy::FromZeros> NotZeroable<2> for T {}
345 #[cfg(feature = "num-traits02")]
346 impl<T: num_traits02::Zero> NotZeroable<3> for T {}
347 macro_rules! not_zeroable {
348 ($t:ty) => {
349 impl NotZeroable<0> for $t {}
350 #[cfg(feature = "bytemuck1")]
351 impl NotZeroable<1> for $t {}
352 #[cfg(feature = "zerocopy")]
353 impl NotZeroable<2> for $t {}
354 #[cfg(feature = "num-traits02")]
355 impl NotZeroable<3> for $t {}
356 };
357 }
358
359 bounded_integer!(struct A(0, 0););
360 assert_zeroable::<A>();
361
362 bounded_integer!(struct B(-459, 3););
363 assert_zeroable::<B>();
364
365 bounded_integer!(struct C(1, 5););
366 not_zeroable!(C);
367 assert_eq!(size_of::<Option<C>>(), size_of::<C>());
368
369 bounded_integer!(struct D(-5, -1););
370 not_zeroable!(D);
371 assert_eq!(size_of::<Option<D>>(), size_of::<D>());
372
373 bounded_integer!(struct E(-(5), 0_i8););
374 not_zeroable!(E);
375 assert_ne!(size_of::<Option<E>>(), size_of::<E>());
376 }
377
378 #[test]
379 #[cfg(feature = "num-traits02")]
380 fn one() {
381 fn assert_one<T: num_traits02::One>() {}
382 bounded_integer!(struct A(1, 1););
383 assert_one::<A>();
384 }
385
386 #[test]
387 #[cfg(feature = "schemars1")]
388 fn schemars() {
389 use schemars1::{generate::SchemaSettings, schema_for};
390 use serde_json1::json;
391
392 let meta = SchemaSettings::default().meta_schema.unwrap();
393
394 bounded_integer!(struct A(3, 12););
395 assert_eq!(
396 schema_for!(A),
397 json!({
398 "$schema": meta,
399 "type": "integer",
400 "title": "uint8",
401 "format": "uint8",
402 "minimum": 3,
403 "maximum": 12,
404 })
405 );
406
407 bounded_integer!(struct B(-10_000_000_005, -10_000_000_001););
409 assert_eq!(
410 schema_for!(B),
411 json!({
412 "$schema": meta,
413 "type": "integer",
414 "title": "int64",
415 "format": "int64",
416 "minimum": -10_000_000_005i64,
417 "maximum": -10_000_000_001i64,
418 })
419 );
420
421 bounded_integer!(enum C(-1, 1););
423 assert_eq!(
424 schema_for!(C),
425 json!({
426 "$schema": meta,
427 "type": "integer",
428 "title": "int8",
429 "format": "int8",
430 "minimum": -1,
431 "maximum": 1,
432 })
433 );
434
435 bounded_integer! {
437 enum Sign {
438 #[allow(unused)]
439 Negative = -1,
440 #[allow(unused)]
441 Zero,
442 #[allow(unused)]
443 Positive,
444 }
445 }
446 assert_eq!(
447 schema_for!(Sign),
448 json!({
449 "$schema": meta,
450 "type": "integer",
451 "title": "int8",
452 "format": "int8",
453 "minimum": -1,
454 "maximum": 1,
455 })
456 );
457
458 bounded_integer!(#[repr(u16)] struct D(2, 4););
460 assert_eq!(
461 schema_for!(D),
462 json!({
463 "$schema": meta,
464 "type": "integer",
465 "title": "uint16",
466 "format": "uint16",
467 "minimum": 2,
468 "maximum": 4,
469 })
470 );
471
472 bounded_integer!(struct E(0x1_0000_0000_0000_0000, 0x2_0000_0000_0000_0000););
475 assert_eq!(
476 schema_for!(E),
477 json!({
478 "$schema": meta,
479 "type": "integer",
480 "title": "uint128",
481 "format": "uint128",
482 })
483 );
484 }
485
486 #[test]
487 #[cfg(feature = "zerocopy")]
488 fn unaligned() {
489 fn assert_unaligned<T: zerocopy::Unaligned>() {}
490 bounded_integer!(struct A(0, 255););
491 assert_unaligned::<A>();
492 bounded_integer!(struct B(-128, 127););
493 assert_unaligned::<B>();
494
495 #[allow(unused)]
496 trait NotUnaligned {}
497 impl<T: zerocopy::Unaligned> NotUnaligned for T {}
498
499 bounded_integer!(struct C(255, 256););
500 bounded_integer!(struct D(-129, -128););
501 impl NotUnaligned for C {}
502 impl NotUnaligned for D {}
503 }
504
505 #[test]
506 fn lit_radix() {
507 #![expect(clippy::mixed_case_hex_literals)]
508
509 bounded_integer!(struct Hex(0x5, 0xf_F););
510 assert_eq!(Hex::MIN_VALUE, 5);
511 assert_eq!(Hex::MAX_VALUE, 255);
512
513 bounded_integer!(struct Oct(0o35, 0o40););
514 assert_eq!(Oct::MIN_VALUE, 29);
515 assert_eq!(Oct::MAX_VALUE, 32);
516
517 bounded_integer!(struct Bin(0b1101, 0b11101););
518 assert_eq!(Bin::MIN_VALUE, 0b1101);
519 assert_eq!(Bin::MAX_VALUE, 0b11101);
520 }
521
522 #[test]
523 fn repr_without_repr() {
524 bounded_integer!(#[expect(unused)] struct Owo(0_u8, 2 + 2););
525 bounded_integer!(#[expect(unused)] struct Uwu(-57 * 37, 8_i64););
526 }
527
528 #[test]
529 fn allowed_attrs() {
530 #![expect(deprecated)]
531 bounded_integer! {
532 #[cfg_attr(all(), doc = "…")]
533 #[deprecated]
534 #[must_use]
535 struct S(0, 255);
536 }
537
538 #[expect(deprecated, unused_must_use)]
539 S::new(0).unwrap();
540 }
541
542 #[test]
543 fn complex_expr() {
544 bounded_integer!(#[repr(u8)] struct S(0, 10 + 10););
545 assert_eq!(S::MIN_VALUE, 0);
546 assert_eq!(S::MAX_VALUE, 20);
547 }
548}