├── .gitignore ├── tests ├── force_build.rs └── hygiene.rs ├── ui ├── const_new.rs ├── bad_range.rs ├── not_zeroable.rs ├── macro.rs ├── const_new.stderr ├── not_zeroable.stderr ├── bad_range.stderr └── macro.stderr ├── optimization-tests ├── Cargo.toml └── src │ └── lib.rs ├── macro ├── Cargo.toml └── src │ └── lib.rs ├── src ├── examples.rs ├── prim_int.rs ├── parse.rs ├── lib.rs ├── macro.rs ├── types.rs └── unsafe_api.rs ├── Cargo.toml ├── .github └── workflows │ └── ci.yml ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /tests/force_build.rs: -------------------------------------------------------------------------------- 1 | // :/ 2 | // https://github.com/dtolnay/trybuild/issues/258 3 | fn main() {} 4 | -------------------------------------------------------------------------------- /ui/const_new.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _ = >::const_new::<6>(); 3 | } 4 | -------------------------------------------------------------------------------- /ui/bad_range.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _ = >::MIN; 3 | bounded_integer::bounded_integer!(#[expect(unused)] struct X(5, 4);); 4 | } 5 | -------------------------------------------------------------------------------- /optimization-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "optimization-tests" 3 | version = "0.1.0" 4 | edition = "2024" 5 | publish = false 6 | 7 | [dependencies] 8 | bounded-integer.path = ".." 9 | -------------------------------------------------------------------------------- /ui/not_zeroable.rs: -------------------------------------------------------------------------------- 1 | #[repr(transparent)] 2 | struct A(u8); 3 | 4 | bounded_integer::unsafe_api! { 5 | for A, 6 | unsafe repr: u8, 7 | min: 1, 8 | max: 1, 9 | zero, 10 | } 11 | 12 | #[repr(transparent)] 13 | struct B(u8); 14 | 15 | bounded_integer::unsafe_api! { 16 | for B, 17 | unsafe repr: u8, 18 | min: 0, 19 | max: 0, 20 | one, 21 | } 22 | 23 | fn main() {} 24 | -------------------------------------------------------------------------------- /macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bounded-integer-macro" 3 | version = "0.6.0" 4 | description = "Proc macro for `bounded-integer`. Do not use directly." 5 | keywords = ["bounded", "integer", "macro", "refinement"] 6 | license = "ISC" 7 | repository = "https://github.com/Kestrer/bounded-integer" 8 | readme = "../README.md" 9 | edition = "2024" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | proc-macro2 = "1.0.24" 16 | quote = "1.0.9" 17 | -------------------------------------------------------------------------------- /ui/macro.rs: -------------------------------------------------------------------------------- 1 | use bounded_integer::bounded_integer; 2 | 3 | bounded_integer! { 4 | #[repr(u8)] 5 | #[repr(u8)] 6 | struct DoubleRepr(0, 0); 7 | } 8 | 9 | bounded_integer! { 10 | enum EnumUnknownMin((0), 0); 11 | } 12 | 13 | bounded_integer! { 14 | enum EnumUnknownMax(0, (0)); 15 | } 16 | 17 | bounded_integer! { 18 | enum EnumTooBig(0, 100_000); 19 | } 20 | 21 | bounded_integer! { 22 | enum EnumTooHigh { 23 | A = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF, 24 | B, 25 | } 26 | } 27 | 28 | bounded_integer! { 29 | enum EnumUnparseable { 30 | A = "", 31 | } 32 | } 33 | 34 | bounded_integer! { 35 | enum EnumTooLarge { 36 | A = 0x1_0000_0000_0000_0000_0000_0000_0000_0000, 37 | } 38 | } 39 | 40 | bounded_integer! { 41 | enum EnumNotContiguous { 42 | A, 43 | B = 2, 44 | } 45 | } 46 | 47 | bounded_integer! { 48 | struct RangeTooLargeStruct(-0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF, 0); 49 | } 50 | 51 | bounded_integer! { 52 | enum RangeTooLargeEnum { 53 | A = -0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF, 54 | } 55 | } 56 | 57 | bounded_integer! { 58 | struct CouldNotInferRepr((0), 0); 59 | } 60 | 61 | bounded_integer! { 62 | #[derive(Default)] 63 | #[cfg_attr(all(), another_disallowed)] 64 | pub struct DisallowedAttr(1_u8, (1)); 65 | } 66 | 67 | bounded_integer! { 68 | #[repr(u8)] 69 | pub struct ReprTooSmall(256, 257); 70 | } 71 | 72 | bounded_integer! { 73 | #[repr(u8)] 74 | pub struct ReprWrong(0, 1_u16); 75 | } 76 | 77 | fn main() {} 78 | -------------------------------------------------------------------------------- /ui/const_new.stderr: -------------------------------------------------------------------------------- 1 | error[E0080]: evaluation panicked: value passed to `const_new` is out of range 2 | --> src/types.rs 3 | | 4 | | / define_bounded_integers! { 5 | | | BoundedU8(u8, unaligned, unsigned), 6 | | | BoundedU16(u16, unsigned), 7 | | | BoundedU32(u32, unsigned), 8 | ... | 9 | | | BoundedIsize(isize), 10 | | | } 11 | | |_^ evaluation of `bounded_integer::types::_::>::const_new::<6>::{constant#0}` failed here 12 | | 13 | = note: this error originates in the macro `$crate::__unsafe_api_internal` which comes from the expansion of the macro `define_bounded_integers` (in Nightly builds, run with -Z macro-backtrace for more info) 14 | 15 | note: erroneous constant encountered 16 | --> src/types.rs 17 | | 18 | | / define_bounded_integers! { 19 | | | BoundedU8(u8, unaligned, unsigned), 20 | | | BoundedU16(u16, unsigned), 21 | | | BoundedU32(u32, unsigned), 22 | ... | 23 | | | BoundedIsize(isize), 24 | | | } 25 | | |_^ 26 | | 27 | = note: this note originates in the macro `$crate::__unsafe_api_internal` which comes from the expansion of the macro `define_bounded_integers` (in Nightly builds, run with -Z macro-backtrace for more info) 28 | 29 | note: the above error was encountered while instantiating `fn bounded_integer::types::_::>::const_new::<6>` 30 | --> ui/const_new.rs:2:13 31 | | 32 | 2 | let _ = >::const_new::<6>(); 33 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 34 | -------------------------------------------------------------------------------- /src/examples.rs: -------------------------------------------------------------------------------- 1 | //! Examples of bounded integers generated by the [`bounded_integer!`] macro. 2 | //! 3 | //! This module is not present when using the crate. 4 | 5 | use crate::bounded_integer; 6 | 7 | bounded_integer! { 8 | /// A bounded struct, implemented as a protected newtype. 9 | /// 10 | /// This was generated from: 11 | /// ``` 12 | #[cfg_attr(feature = "step_trait", doc = "# #![feature(step_trait)]")] 13 | /// # use bounded_integer::bounded_integer; 14 | /// bounded_integer! { 15 | /// pub struct BoundedStruct(-8, 7); 16 | /// } 17 | /// ``` 18 | pub struct BoundedStruct(-8, 7); 19 | } 20 | 21 | bounded_integer! { 22 | /// A bounded enum. 23 | /// 24 | /// This was generated from: 25 | /// ``` 26 | #[cfg_attr(feature = "step_trait", doc = "# #![feature(step_trait)]")] 27 | /// # use bounded_integer::bounded_integer; 28 | /// bounded_integer! { 29 | /// pub enum BoundedEnum(-8, 7); 30 | /// } 31 | /// ``` 32 | pub enum BoundedEnum(-8, 7); 33 | } 34 | 35 | bounded_integer! { 36 | /// A bounded enum with named variants. 37 | /// 38 | /// This was generated from: 39 | /// ``` 40 | #[cfg_attr(feature = "step_trait", doc = "# #![feature(step_trait)]")] 41 | /// # use bounded_integer::bounded_integer; 42 | /// bounded_integer! { 43 | /// pub enum BoundedEnumNamed { 44 | /// Zero, 45 | /// One, 46 | /// Two, 47 | /// } 48 | /// } 49 | /// ``` 50 | pub enum BoundedEnumNamed { 51 | Zero, 52 | One, 53 | Two, 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bounded-integer" 3 | version = "0.6.1" 4 | description = "Bounded integers" 5 | keywords = ["bounded", "integer", "macro", "refinement", "modular"] 6 | license = "ISC" 7 | repository = "https://github.com/Kestrer/bounded-integer" 8 | readme = "README.md" 9 | edition = "2024" 10 | 11 | [dependencies] 12 | bounded-integer-macro = { path = "./macro", version = "=0.6.0", optional = true } 13 | 14 | arbitrary1 = { package = "arbitrary", version = "1.0.2", optional = true } 15 | bytemuck1 = { package = "bytemuck", version = "1.7.2", optional = true } 16 | num-traits02 = { package = "num-traits", version = "0.2.14", default-features = false, features = ["i128"], optional = true } 17 | serde1 = { package = "serde", version = "1.0.124", default-features = false, optional = true } 18 | zerocopy = { version = "0.8.14", features = ["derive"], optional = true } 19 | 20 | [profile.test.package.optimization-tests] 21 | opt-level = 3 22 | 23 | [features] 24 | std = ["alloc"] 25 | alloc = [] 26 | 27 | macro = ["bounded-integer-macro"] 28 | 29 | step_trait = [] 30 | 31 | # `__doc` is set when building the crate locally, but users should not set it. 32 | # There are a few reasons we use it instead of `cfg(doc)`: 33 | # + The examples are gated based on this feature, and we want them to typecheck. 34 | # + When it is enabled, we adjust the internals of the crate in a way that makes its macros unusable 35 | # outside the crate. This improves documentation, but should not be done only when `cfg(doc)` is 36 | # set. 37 | __doc = ["macro"] 38 | 39 | [dev-dependencies] 40 | trybuild = "1.0.110" 41 | 42 | [package.metadata.docs.rs] 43 | all-features = true 44 | 45 | [workspace] 46 | members = ["macro", "optimization-tests"] 47 | -------------------------------------------------------------------------------- /tests/hygiene.rs: -------------------------------------------------------------------------------- 1 | #![no_implicit_prelude] 2 | #![no_std] 3 | #![cfg_attr(feature = "step_trait", feature(step_trait))] 4 | #![cfg(feature = "macro")] 5 | #![deny(clippy::pedantic)] 6 | 7 | #[expect(dead_code, non_camel_case_types)] 8 | struct u8 {} 9 | #[expect(dead_code, non_camel_case_types)] 10 | struct u16 {} 11 | #[expect(dead_code, non_camel_case_types)] 12 | struct u32 {} 13 | #[expect(dead_code, non_camel_case_types)] 14 | struct u64 {} 15 | #[expect(dead_code, non_camel_case_types)] 16 | struct u128 {} 17 | #[expect(dead_code, non_camel_case_types)] 18 | struct usize {} 19 | #[expect(dead_code, non_camel_case_types)] 20 | struct i8 {} 21 | #[expect(dead_code, non_camel_case_types)] 22 | struct i16 {} 23 | #[expect(dead_code, non_camel_case_types)] 24 | struct i32 {} 25 | #[expect(dead_code, non_camel_case_types)] 26 | struct i64 {} 27 | #[expect(dead_code, non_camel_case_types)] 28 | struct i128 {} 29 | #[expect(dead_code, non_camel_case_types)] 30 | struct isize {} 31 | 32 | ::bounded_integer::bounded_integer! { 33 | #[repr(isize)] 34 | pub struct StructSigned(-3, 1); 35 | } 36 | ::bounded_integer::bounded_integer! { 37 | #[repr(u16)] 38 | pub struct StructUnsigned(36, 65535); 39 | } 40 | ::bounded_integer::bounded_integer! { 41 | #[repr(i64)] 42 | pub enum EnumSigned(-4, 5); 43 | } 44 | ::bounded_integer::bounded_integer! { 45 | #[repr(u8)] 46 | pub enum EnumUnsigned(253, 255); 47 | } 48 | // generic parameters are not hygienic :( 49 | ::bounded_integer::bounded_integer!(pub struct A(0, 0);); 50 | ::bounded_integer::bounded_integer!(pub struct B(0, 0);); 51 | ::bounded_integer::bounded_integer!(pub enum C(0, 0);); 52 | ::bounded_integer::bounded_integer!( 53 | pub enum T { 54 | X = 0, 55 | } 56 | ); 57 | -------------------------------------------------------------------------------- /optimization-tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Checking compilation with optimizations for the `assert_unchecked` range tests in `unsafe_api`. 2 | 3 | // We should not export anything when not running tests 4 | #![cfg(test)] 5 | 6 | use bounded_integer::BoundedUsize; 7 | 8 | unsafe extern "C" { 9 | /// This function should fail to link if range checks are not optimized out as expected. 10 | safe fn should_be_optimized_out() -> !; 11 | } 12 | 13 | /// Ensure that LLVM has enough information at compile-time to statically ensure that the 14 | /// condition is true. If LLVM cannot statically ensure that the condition is true and 15 | /// emits a run-time branch, the binary will contain a call to a non-existent `extern` 16 | /// function and fail to link. 17 | #[macro_export] 18 | macro_rules! optimizer_assert_guaranteed { 19 | ($cond:expr) => { 20 | if !$cond { 21 | $crate::should_be_optimized_out(); 22 | } 23 | }; 24 | } 25 | 26 | // If you uncomment this, the tests must fail to pass: 27 | // #[test] 28 | // fn should_fail() { 29 | // optimizer_assert_guaranteed!(core::hint::black_box(true)); 30 | // } 31 | 32 | /// Assert that the inner value is statically enforced to be between the bounds `LO` and 33 | /// `HI` inclusive. 34 | fn range_check_optimized_out_usize(expected: usize) { 35 | let i = core::hint::black_box(BoundedUsize::::new(expected).unwrap()); 36 | optimizer_assert_guaranteed!(i.get() >= LO && i.get() <= HI); 37 | let i = core::hint::black_box(i); 38 | optimizer_assert_guaranteed!(*i.get_ref() >= LO && *i.get_ref() <= HI); 39 | let mut i = core::hint::black_box(i); 40 | optimizer_assert_guaranteed!(*unsafe { i.get_mut() } >= LO && *unsafe { i.get_mut() } <= HI); 41 | 42 | assert_eq!( 43 | core::hint::black_box(i.get()), 44 | core::hint::black_box(expected) 45 | ); 46 | } 47 | 48 | #[test] 49 | fn range_check_optimized_out() { 50 | range_check_optimized_out_usize::<10, 20>(15); 51 | range_check_optimized_out_usize::<20, 20>(20); 52 | range_check_optimized_out_usize::<1, { usize::MAX - 1 }>(usize::MAX - 1); 53 | } 54 | -------------------------------------------------------------------------------- /ui/not_zeroable.stderr: -------------------------------------------------------------------------------- 1 | error[E0080]: evaluation panicked: used `zero` on a type whose range does not include zero 2 | --> ui/not_zeroable.rs:4:1 3 | | 4 | 4 | / bounded_integer::unsafe_api! { 5 | 5 | | for A, 6 | 6 | | unsafe repr: u8, 7 | 7 | | min: 1, 8 | 8 | | max: 1, 9 | 9 | | zero, 10 | 10 | | } 11 | | |_^ evaluation of `_::::default::{constant#0}` failed here 12 | | 13 | = note: this error originates in the macro `$crate::__unsafe_api_internal` which comes from the expansion of the macro `bounded_integer::unsafe_api` (in Nightly builds, run with -Z macro-backtrace for more info) 14 | 15 | note: erroneous constant encountered 16 | --> ui/not_zeroable.rs:4:1 17 | | 18 | 4 | / bounded_integer::unsafe_api! { 19 | 5 | | for A, 20 | 6 | | unsafe repr: u8, 21 | 7 | | min: 1, 22 | 8 | | max: 1, 23 | 9 | | zero, 24 | 10 | | } 25 | | |_^ 26 | | 27 | = note: this note originates in the macro `$crate::__unsafe_api_internal` which comes from the expansion of the macro `bounded_integer::unsafe_api` (in Nightly builds, run with -Z macro-backtrace for more info) 28 | 29 | error[E0080]: evaluation panicked: used `one` on a type whose range does not include one 30 | --> ui/not_zeroable.rs:15:1 31 | | 32 | 15 | / bounded_integer::unsafe_api! { 33 | 16 | | for B, 34 | 17 | | unsafe repr: u8, 35 | 18 | | min: 0, 36 | 19 | | max: 0, 37 | 20 | | one, 38 | 21 | | } 39 | | |_^ evaluation of `_::::one::{constant#0}` failed here 40 | | 41 | = note: this error originates in the macro `$crate::__unsafe_api_internal` which comes from the expansion of the macro `bounded_integer::unsafe_api` (in Nightly builds, run with -Z macro-backtrace for more info) 42 | 43 | note: erroneous constant encountered 44 | --> ui/not_zeroable.rs:15:1 45 | | 46 | 15 | / bounded_integer::unsafe_api! { 47 | 16 | | for B, 48 | 17 | | unsafe repr: u8, 49 | 18 | | min: 0, 50 | 19 | | max: 0, 51 | 20 | | one, 52 | 21 | | } 53 | | |_^ 54 | | 55 | = note: this note originates in the macro `$crate::__unsafe_api_internal` which comes from the expansion of the macro `bounded_integer::unsafe_api` (in Nightly builds, run with -Z macro-backtrace for more info) 56 | -------------------------------------------------------------------------------- /ui/bad_range.stderr: -------------------------------------------------------------------------------- 1 | error[E0080]: evaluation panicked: range minimum should be less than maximum 2 | --> src/types.rs 3 | | 4 | | / define_bounded_integers! { 5 | | | BoundedU8(u8, unaligned, unsigned), 6 | | | BoundedU16(u16, unsigned), 7 | | | BoundedU32(u32, unsigned), 8 | ... | 9 | | | BoundedIsize(isize), 10 | | | } 11 | | |_^ evaluation of `bounded_integer::types::_::>::MIN` failed here 12 | | 13 | = note: this error originates in the macro `$crate::__unsafe_api_internal` which comes from the expansion of the macro `define_bounded_integers` (in Nightly builds, run with -Z macro-backtrace for more info) 14 | 15 | note: erroneous constant encountered 16 | --> ui/bad_range.rs:2:13 17 | | 18 | 2 | let _ = >::MIN; 19 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 20 | 21 | error[E0080]: evaluation panicked: range minimum should be less than maximum 22 | --> ui/bad_range.rs:3:5 23 | | 24 | 3 | bounded_integer::bounded_integer!(#[expect(unused)] struct X(5, 4);); 25 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `main::__bounded_integer_private_X::_::::MIN` failed here 26 | | 27 | = note: this error originates in the macro `$crate::__unsafe_api_internal` which comes from the expansion of the macro `bounded_integer::bounded_integer` (in Nightly builds, run with -Z macro-backtrace for more info) 28 | 29 | note: erroneous constant encountered 30 | --> ui/bad_range.rs:3:5 31 | | 32 | 3 | bounded_integer::bounded_integer!(#[expect(unused)] struct X(5, 4);); 33 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 34 | | 35 | = note: this note originates in the macro `$crate::__unsafe_api_internal` which comes from the expansion of the macro `bounded_integer::bounded_integer` (in Nightly builds, run with -Z macro-backtrace for more info) 36 | 37 | error[E0080]: evaluation panicked: range minimum should be less than maximum 38 | --> ui/bad_range.rs:3:5 39 | | 40 | 3 | bounded_integer::bounded_integer!(#[expect(unused)] struct X(5, 4);); 41 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `main::__bounded_integer_private_X::_::::MAX` failed here 42 | | 43 | = note: this error originates in the macro `$crate::__unsafe_api_internal` which comes from the expansion of the macro `bounded_integer::bounded_integer` (in Nightly builds, run with -Z macro-backtrace for more info) 44 | 45 | note: erroneous constant encountered 46 | --> ui/bad_range.rs:3:5 47 | | 48 | 3 | bounded_integer::bounded_integer!(#[expect(unused)] struct X(5, 4);); 49 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 50 | | 51 | = note: this note originates in the macro `$crate::__unsafe_api_internal` which comes from the expansion of the macro `bounded_integer::bounded_integer` (in Nightly builds, run with -Z macro-backtrace for more info) 52 | -------------------------------------------------------------------------------- /src/prim_int.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{self, Display, Formatter}; 2 | use core::num::NonZero; 3 | 4 | /// An error returned when a checked conversion into a bounded integer fails. 5 | #[derive(Debug, Clone, Copy)] 6 | #[non_exhaustive] 7 | pub struct TryFromError; 8 | 9 | impl Display for TryFromError { 10 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 11 | f.write_str("out of range conversion to bounded integer attempted") 12 | } 13 | } 14 | 15 | #[cfg(feature = "std")] 16 | impl std::error::Error for TryFromError {} 17 | 18 | #[must_use] 19 | pub fn try_from_error() -> TryFromError { 20 | TryFromError 21 | } 22 | 23 | pub trait PrimInt { 24 | type Signed; 25 | type Unsigned; 26 | } 27 | 28 | pub type Signed = ::Signed; 29 | pub type Unsigned = ::Unsigned; 30 | 31 | generate! { 32 | u8 i8, 33 | u16 i16, 34 | u32 i32, 35 | u64 i64, 36 | u128 i128, 37 | usize isize, 38 | } 39 | 40 | macro_rules! generate { 41 | ($($unsigned:ident $signed:ident,)*) => { $( 42 | impl PrimInt for $unsigned { 43 | type Signed = $signed; 44 | type Unsigned = $unsigned; 45 | } 46 | impl PrimInt for $signed { 47 | type Signed = $signed; 48 | type Unsigned = $unsigned; 49 | } 50 | 51 | impl crate::__private::Dispatch<$unsigned> { 52 | pub const fn checked_add_unsigned(lhs: $unsigned, rhs: $unsigned) -> Option<$unsigned> { 53 | lhs.checked_add(rhs) 54 | } 55 | pub const fn checked_sub_unsigned(lhs: $unsigned, rhs: $unsigned) -> Option<$unsigned> { 56 | lhs.checked_sub(rhs) 57 | } 58 | pub const fn rem_euclid_unsigned(lhs: $unsigned, rhs: NonZero<$unsigned>) -> $unsigned { 59 | lhs.rem_euclid(rhs.get()) 60 | } 61 | } 62 | impl crate::__private::Dispatch<$signed> { 63 | pub const fn checked_add_unsigned(lhs: $signed, rhs: $unsigned) -> Option<$signed> { 64 | lhs.checked_add_unsigned(rhs) 65 | } 66 | pub const fn checked_sub_unsigned(lhs: $signed, rhs: $unsigned) -> Option<$signed> { 67 | lhs.checked_sub_unsigned(rhs) 68 | } 69 | pub const fn rem_euclid_unsigned(lhs: $signed, rhs: NonZero<$unsigned>) -> $unsigned { 70 | // In my benchmarks, this is faster than methods involving widening. 71 | #[expect(clippy::cast_possible_wrap)] 72 | #[expect(clippy::cast_sign_loss)] 73 | if 0 <= lhs { 74 | // If `lhs` is nonnegative, just use regular unsigned remainder. 75 | (lhs as $unsigned).rem_euclid(rhs.get()) 76 | } else if 0 <= rhs.get() as $signed { 77 | // If `rhs` is small enough to fit in a signed type, use regular `rem_euclid`. 78 | // We know the result is nonnegative, so we can cast to the unsigned type. 79 | lhs.rem_euclid(rhs.get() as $signed) as $unsigned 80 | } else { 81 | // Otherwise, `lhs` is negative and `rhs` is larger than all signed values. We 82 | // can therefore add `rhs` to `lhs` and get an unsigned value without overflow, 83 | // which won’t affect the result. 84 | rhs.get().checked_add_signed(lhs).unwrap().rem_euclid(rhs.get()) 85 | } 86 | } 87 | } 88 | )* }; 89 | } 90 | use generate; 91 | 92 | // We don’t implement for `usize` because we don’t get `(wide usize): From` impls which 93 | // makes things difficult trait-wise. 94 | pub trait HasWide { 95 | type Wide; 96 | } 97 | 98 | pub type Wide = ::Wide; 99 | 100 | impl HasWide for u8 { 101 | type Wide = u16; 102 | } 103 | impl HasWide for u16 { 104 | type Wide = u32; 105 | } 106 | impl HasWide for u32 { 107 | type Wide = u64; 108 | } 109 | impl HasWide for u64 { 110 | type Wide = u128; 111 | } 112 | impl HasWide for i8 { 113 | type Wide = i16; 114 | } 115 | impl HasWide for i16 { 116 | type Wide = i32; 117 | } 118 | impl HasWide for i32 { 119 | type Wide = i64; 120 | } 121 | impl HasWide for i64 { 122 | type Wide = i128; 123 | } 124 | -------------------------------------------------------------------------------- /ui/macro.stderr: -------------------------------------------------------------------------------- 1 | error: duplicate `repr` attribute 2 | --> ui/macro.rs:5:7 3 | | 4 | 5 | #[repr(u8)] 5 | | ^^^^ 6 | 7 | error: `enum` requires bound to be statically known 8 | --> ui/macro.rs:10:25 9 | | 10 | 10 | enum EnumUnknownMin((0), 0); 11 | | ^^^ 12 | 13 | error: `enum` requires bound to be statically known 14 | --> ui/macro.rs:14:28 15 | | 16 | 14 | enum EnumUnknownMax(0, (0)); 17 | | ^^^ 18 | 19 | error: refusing to generate this many `enum` variants 20 | --> ui/macro.rs:18:21 21 | | 22 | 18 | enum EnumTooBig(0, 100_000); 23 | | ^ 24 | 25 | error: too many variants (overflows a u128) 26 | --> ui/macro.rs:24:9 27 | | 28 | 24 | B, 29 | | ^ 30 | 31 | error: could not parse variant value 32 | --> ui/macro.rs:30:13 33 | | 34 | 30 | A = "", 35 | | ^^ 36 | 37 | error: could not parse variant value 38 | --> ui/macro.rs:36:13 39 | | 40 | 36 | A = 0x1_0000_0000_0000_0000_0000_0000_0000_0000, 41 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 42 | 43 | error: enum not contiguous 44 | --> ui/macro.rs:43:13 45 | | 46 | 43 | B = 2, 47 | | ^ 48 | 49 | error: range too large for any integer type 50 | --> ui/macro.rs:48:32 51 | | 52 | 48 | struct RangeTooLargeStruct(-0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF, 0); 53 | | ^ 54 | 55 | error: range too large for any integer type 56 | --> ui/macro.rs:53:13 57 | | 58 | 53 | A = -0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF, 59 | | ^ 60 | 61 | error: no #[repr] attribute found, and could not infer 62 | --> ui/macro.rs:58:30 63 | | 64 | 58 | struct CouldNotInferRepr((0), 0); 65 | | ^^^ 66 | 67 | error: for soundness reasons, custom attributes are not allowed 68 | --> ui/macro.rs:61:1 69 | | 70 | 61 | / bounded_integer! { 71 | 62 | | #[derive(Default)] 72 | 63 | | #[cfg_attr(all(), another_disallowed)] 73 | 64 | | pub struct DisallowedAttr(1_u8, (1)); 74 | 65 | | } 75 | | |_^ 76 | | 77 | = note: this error originates in the macro `$crate::__helper` which comes from the expansion of the macro `bounded_integer` (in Nightly builds, run with -Z macro-backtrace for more info) 78 | 79 | error: for soundness reasons, custom attributes are not allowed 80 | --> ui/macro.rs:61:1 81 | | 82 | 61 | / bounded_integer! { 83 | 62 | | #[derive(Default)] 84 | 63 | | #[cfg_attr(all(), another_disallowed)] 85 | 64 | | pub struct DisallowedAttr(1_u8, (1)); 86 | 65 | | } 87 | | |_^ 88 | | 89 | = note: this error originates in the macro `$crate::__helper` which comes from the expansion of the macro `bounded_integer` (in Nightly builds, run with -Z macro-backtrace for more info) 90 | 91 | error: cannot find attribute `another_disallowed` in this scope 92 | --> ui/macro.rs:63:23 93 | | 94 | 63 | #[cfg_attr(all(), another_disallowed)] 95 | | ^^^^^^^^^^^^^^^^^^ 96 | 97 | error[E0308]: mismatched types 98 | --> ui/macro.rs:74:29 99 | | 100 | 74 | pub struct ReprWrong(0, 1_u16); 101 | | ^^^^^ expected `u8`, found `u16` 102 | | 103 | help: change the type of the numeric literal from `u16` to `u8` 104 | | 105 | 74 - pub struct ReprWrong(0, 1_u16); 106 | 74 + pub struct ReprWrong(0, 1_u8); 107 | | 108 | 109 | error[E0080]: trying to set discriminant of a std::option::Option to the niched variant, but the value does not match 110 | --> ui/macro.rs:67:1 111 | | 112 | 67 | / bounded_integer! { 113 | 68 | | #[repr(u8)] 114 | 69 | | pub struct ReprTooSmall(256, 257); 115 | 70 | | } 116 | | |_^ evaluation of `__bounded_integer_private_ReprTooSmall::_::::MIN` failed inside this call 117 | | 118 | note: inside `__bounded_integer_private_ReprTooSmall::_::::new` 119 | --> ui/macro.rs:67:1 120 | | 121 | 67 | / bounded_integer! { 122 | 68 | | #[repr(u8)] 123 | 69 | | pub struct ReprTooSmall(256, 257); 124 | 70 | | } 125 | | |_^ the failure occurred here 126 | = note: this error originates in the macro `$crate::__unsafe_api_internal` which comes from the expansion of the macro `bounded_integer` (in Nightly builds, run with -Z macro-backtrace for more info) 127 | 128 | note: erroneous constant encountered 129 | --> ui/macro.rs:67:1 130 | | 131 | 67 | / bounded_integer! { 132 | 68 | | #[repr(u8)] 133 | 69 | | pub struct ReprTooSmall(256, 257); 134 | 70 | | } 135 | | |_^ 136 | | 137 | = note: this note originates in the macro `$crate::__unsafe_api_internal` which comes from the expansion of the macro `bounded_integer` (in Nightly builds, run with -Z macro-backtrace for more info) 138 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{self, Display, Formatter}; 2 | #[cfg(feature = "std")] 3 | use std::error::Error; 4 | 5 | macro_rules! from_str_radix_impl { 6 | ($($ty:ident)*) => { $( 7 | impl $crate::__private::Dispatch<$ty> { 8 | // Implement it ourselves (copying the implementation from std) because `IntErrorKind` 9 | // is non-exhaustive. 10 | pub const fn from_ascii_radix(src: &[u8], radix: u32) -> Result<$ty, ParseError> { 11 | assert!( 12 | 2 <= radix && radix <= 36, 13 | "from_str_radix: radix must lie in the range `[2, 36]`", 14 | ); 15 | 16 | macro_rules! yeet { 17 | ($e:expr) => { return Err(ParseError { kind: $e }) }; 18 | } 19 | 20 | let (positive, digits) = match *src { 21 | [b'+', ref digits @ ..] => (true, digits), 22 | [b'-', ref digits @ ..] => (false, digits), 23 | ref digits => (true, digits), 24 | }; 25 | 26 | if digits.is_empty() { 27 | yeet!(ParseErrorKind::NoDigits); 28 | } 29 | 30 | let overflow_kind = if positive { 31 | ParseErrorKind::AboveMax 32 | } else { 33 | ParseErrorKind::BelowMin 34 | }; 35 | 36 | let mut result: $ty = 0; 37 | 38 | let mut i = 0; 39 | while i < digits.len() { 40 | let digit = digits[i]; 41 | 42 | let Some(digit_value) = (digit as char).to_digit(radix) else { 43 | yeet!(ParseErrorKind::InvalidDigit); 44 | }; 45 | 46 | #[allow(clippy::cast_possible_wrap)] 47 | #[allow(clippy::cast_possible_truncation)] 48 | let Some(new_result) = result.checked_mul(radix as $ty) else { 49 | yeet!(overflow_kind); 50 | }; 51 | 52 | #[allow(clippy::cast_possible_wrap)] 53 | #[allow(clippy::cast_possible_truncation)] 54 | let Some(new_result) = (if positive { 55 | new_result.checked_add(digit_value as $ty) 56 | } else { 57 | new_result.checked_sub(digit_value as $ty) 58 | }) else { 59 | yeet!(overflow_kind); 60 | }; 61 | 62 | result = new_result; 63 | 64 | i += 1; 65 | } 66 | 67 | Ok(result) 68 | } 69 | } 70 | )* } 71 | } 72 | from_str_radix_impl! { u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize } 73 | 74 | /// An error which can be returned when parsing a bounded integer. 75 | /// 76 | /// This is the error type of all bounded integers' `from_str_radix()` functions (such as 77 | /// [`BoundedI8::from_str_radix`](crate::BoundedI8::from_str_radix)) as well as their 78 | /// [`FromStr`](std::str::FromStr) implementations. 79 | #[derive(Debug, Clone)] 80 | pub struct ParseError { 81 | kind: ParseErrorKind, 82 | } 83 | 84 | impl ParseError { 85 | /// Gives the cause of the error. 86 | #[must_use] 87 | pub fn kind(&self) -> ParseErrorKind { 88 | self.kind 89 | } 90 | } 91 | 92 | impl Display for ParseError { 93 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 94 | match self.kind() { 95 | ParseErrorKind::NoDigits => f.write_str("no digits found"), 96 | ParseErrorKind::InvalidDigit => f.write_str("invalid digit found in string"), 97 | ParseErrorKind::AboveMax => f.write_str("number too high to fit in target range"), 98 | ParseErrorKind::BelowMin => f.write_str("number too low to fit in target range"), 99 | } 100 | } 101 | } 102 | 103 | #[cfg(feature = "std")] 104 | impl Error for ParseError {} 105 | 106 | /// The cause of the failure to parse the integer. 107 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 108 | #[non_exhaustive] 109 | pub enum ParseErrorKind { 110 | /// No digits were found in the input string. 111 | /// 112 | /// This happens when the input is an empty string, or when it only contains a `+` or `-`. 113 | #[non_exhaustive] 114 | NoDigits, 115 | /// An invalid digit was found in the input. 116 | #[non_exhaustive] 117 | InvalidDigit, 118 | /// The integer is too high to fit in the bounded integer's range. 119 | #[non_exhaustive] 120 | AboveMax, 121 | /// The integer is too low to fit in the bounded integer's range. 122 | #[non_exhaustive] 123 | BelowMin, 124 | } 125 | 126 | #[must_use] 127 | pub const fn error_below_min() -> ParseError { 128 | ParseError { 129 | kind: ParseErrorKind::BelowMin, 130 | } 131 | } 132 | #[must_use] 133 | pub const fn error_above_max() -> ParseError { 134 | ParseError { 135 | kind: ParseErrorKind::AboveMax, 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | { 2 | "on": ["push", "pull_request"], 3 | "name": "CI", 4 | "jobs": { 5 | "clippy": { 6 | "name": "Clippy", 7 | "runs-on": "ubuntu-latest", 8 | "steps": [ 9 | { "uses": "actions/checkout@v2" }, 10 | { 11 | "uses": "actions-rs/toolchain@v1", 12 | "with": { 13 | "toolchain": "stable", 14 | "profile": "minimal", 15 | "components": "clippy", 16 | "override": true, 17 | }, 18 | }, 19 | { 20 | "uses": "actions-rs/cargo@v1", 21 | "with": { 22 | "command": "clippy", 23 | "args": "--workspace --all-targets --features macro -- -Dwarnings", 24 | }, 25 | }, 26 | { 27 | "uses": "actions-rs/cargo@v1", 28 | "with": { 29 | "command": "clippy", 30 | "args": "--workspace --all-targets --features macro,arbitrary1,bytemuck1,num-traits02,serde1,zerocopy,std -- -Dwarnings", 31 | }, 32 | }, 33 | ], 34 | }, 35 | "test": { 36 | "name": "Test", 37 | "runs-on": "ubuntu-latest", 38 | "steps": [ 39 | { "uses": "actions/checkout@v2" }, 40 | { 41 | "uses": "actions-rs/toolchain@v1", 42 | "with": { 43 | "profile": "minimal", 44 | "toolchain": "stable", 45 | "override": true, 46 | }, 47 | }, 48 | { 49 | "uses": "actions-rs/cargo@v1", 50 | "with": { 51 | "command": "test", 52 | "args": "--workspace --features macro,arbitrary1,bytemuck1,num-traits02,serde1,zerocopy,std", 53 | }, 54 | }, 55 | ], 56 | }, 57 | "check_nightly": { 58 | "name": "Check Nightly", 59 | "runs-on": "ubuntu-latest", 60 | "env": { "RUSTFLAGS": "-Dwarnings" }, 61 | "steps": [ 62 | { "uses": "actions/checkout@v2" }, 63 | { 64 | "uses": "actions-rs/toolchain@v1", 65 | "with": { 66 | "toolchain": "nightly", 67 | "profile": "minimal", 68 | "override": true, 69 | }, 70 | }, 71 | { 72 | "uses": "actions-rs/cargo@v1", 73 | "with": { 74 | "command": "check", 75 | "args": "--workspace --all-targets --all-features", 76 | }, 77 | }, 78 | ], 79 | }, 80 | "test_nightly": { 81 | "name": "Test Nightly", 82 | "runs-on": "ubuntu-latest", 83 | "steps": [ 84 | { "uses": "actions/checkout@v2" }, 85 | { 86 | "uses": "actions-rs/toolchain@v1", 87 | "with": { 88 | "profile": "minimal", 89 | "toolchain": "nightly", 90 | "components": "miri", 91 | "override": true, 92 | }, 93 | }, 94 | { 95 | "uses": "actions-rs/cargo@v1", 96 | "with": { 97 | "command": "test", 98 | "args": "--workspace --all-features", 99 | }, 100 | }, 101 | { 102 | "uses": "actions-rs/cargo@v1", 103 | "with": { 104 | "command": "miri", 105 | "args": "test --workspace --all-features", 106 | }, 107 | }, 108 | ], 109 | }, 110 | "fmt": { 111 | "name": "Rustfmt", 112 | "runs-on": "ubuntu-latest", 113 | "steps": [ 114 | { "uses": "actions/checkout@v2" }, 115 | { 116 | "uses": "actions-rs/toolchain@v1", 117 | "with": { 118 | "toolchain": "stable", 119 | "profile": "minimal", 120 | "components": "rustfmt", 121 | "override": true, 122 | }, 123 | }, 124 | { 125 | "uses": "actions-rs/cargo@v1", 126 | "with": { 127 | "command": "fmt", 128 | "args": "--all -- --check", 129 | }, 130 | }, 131 | ], 132 | }, 133 | }, 134 | } 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | This crate provides two types of bounded integer. 4 | 5 | # Macro-generated bounded integers 6 | 7 | The [`bounded_integer!`] macro allows you to define your own bounded integer type, given a 8 | specific (inclusive) range it inhabits. For example: 9 | 10 | ```rust 11 | bounded_integer! { 12 | struct MyInteger(0, 7); 13 | } 14 | let num = MyInteger::new(5).unwrap(); 15 | assert_eq!(num, 5); 16 | ``` 17 | 18 | This macro supports both `struct`s and `enum`s. See the [`examples`] module for the 19 | documentation of generated types. 20 | 21 | # Const generics-based bounded integers 22 | 23 | You can also create ad-hoc bounded integers via types in this library that use const generics, 24 | for example: 25 | 26 | ```rust 27 | let num = >::new(5).unwrap(); 28 | assert_eq!(num, 5); 29 | ``` 30 | 31 | These integers are shorter to use as they don't require a type declaration or explicit name. 32 | However due to the limits of const generics, they may not implement some traits – 33 | namely [`Default`], bytemuck’s [`Zeroable`] and zerocopy’s [`FromZeros`]. 34 | Also, unlike their macro counterparts they will not be subject to niche layout optimizations. 35 | 36 | # `no_std` 37 | 38 | All the integers in this crate depend only on libcore and so work in `#![no_std]` environments. 39 | 40 | # Crate Features 41 | 42 | By default, no crate features are enabled. 43 | - `std`: Interopate with `std` — implies `alloc`. Enables the following things: 44 | - An implementation of [`Error`] for [`ParseError`]. 45 | - `alloc`: Interopate with `alloc`. Has no effect currently. 46 | - `macro`: Enable the [`bounded_integer!`] macro. 47 | - `arbitrary1`: Implement [`Arbitrary`] for the bounded integers. This is useful when using 48 | bounded integers as fuzzing inputs. 49 | - `bytemuck1`: Implement [`Contiguous`] and [`NoUninit`] for all bounded integers, 50 | and [`Zeroable`] for macro-generated bounded integers that support it. 51 | - `num-traits02`: Implement [`Bounded`], [`AsPrimitive`], [`FromPrimitive`], [`NumCast`], 52 | [`ToPrimitive`], [`CheckedAdd`], [`CheckedDiv`], [`CheckedMul`], [`CheckedNeg`], 53 | [`CheckedRem`], [`CheckedSub`], [`MulAdd`], [`SaturatingAdd`], [`SaturatingMul`] and 54 | [`SaturatingSub`] for all bounded integers. 55 | - `serde1`: Implement [`Serialize`] and [`Deserialize`] for the bounded integers, making sure all 56 | values will never be out of bounds. 57 | - `zerocopy`: Implement [`IntoBytes`] and [`Immutable`] for all bounded integers, 58 | [`Unaligned`] for ones backed by `u8` or `i8`, 59 | and [`FromZeros`] for suitable macro-generated ones. 60 | - `step_trait`: Implement the [`Step`] trait which allows the bounded integers to be easily used 61 | in ranges. This will require you to use nightly and place `#![feature(step_trait)]` in your 62 | crate root if you use the macro. 63 | 64 | [`bounded_integer!`]: https://docs.rs/bounded-integer/*/bounded_integer/macro.bounded_integer.html 65 | [`examples`]: https://docs.rs/bounded-integer/*/bounded_integer/examples/ 66 | [`Arbitrary`]: https://docs.rs/arbitrary/1/arbitrary/trait.Arbitrary.html 67 | [`Contiguous`]: https://docs.rs/bytemuck/1/bytemuck/trait.Contiguous.html 68 | [`NoUninit`]: https://docs.rs/bytemuck/1/bytemuck/trait.NoUninit.html 69 | [`Zeroable`]: https://docs.rs/bytemuck/1/bytemuck/trait.Zeroable.html 70 | [`Bounded`]: https://docs.rs/num-traits/0.2/num_traits/bounds/trait.Bounded.html 71 | [`AsPrimitive`]: https://docs.rs/num-traits/0.2/num_traits/cast/trait.AsPrimitive.html 72 | [`FromPrimitive`]: https://docs.rs/num-traits/0.2/num_traits/cast/trait.FromPrimitive.html 73 | [`NumCast`]: https://docs.rs/num-traits/0.2/num_traits/cast/trait.NumCast.html 74 | [`ToPrimitive`]: https://docs.rs/num-traits/0.2/num_traits/cast/trait.ToPrimitive.html 75 | [`CheckedAdd`]: https://docs.rs/num-traits/0.2/num_traits/ops/checked/trait.CheckedAdd.html 76 | [`CheckedDiv`]: https://docs.rs/num-traits/0.2/num_traits/ops/checked/trait.CheckedDiv.html 77 | [`CheckedMul`]: https://docs.rs/num-traits/0.2/num_traits/ops/checked/trait.CheckedMul.html 78 | [`CheckedNeg`]: https://docs.rs/num-traits/0.2/num_traits/ops/checked/trait.CheckedNeg.html 79 | [`CheckedRem`]: https://docs.rs/num-traits/0.2/num_traits/ops/checked/trait.CheckedRem.html 80 | [`CheckedSub`]: https://docs.rs/num-traits/0.2/num_traits/ops/checked/trait.CheckedSub.html 81 | [`MulAdd`]: https://docs.rs/num-traits/0.2/num_traits/ops/mul_add/trait.MulAdd.html 82 | [`SaturatingAdd`]: https://docs.rs/num-traits/0.2/num_traits/ops/saturating/trait.SaturatingAdd.html 83 | [`SaturatingMul`]: https://docs.rs/num-traits/0.2/num_traits/ops/saturating/trait.SaturatingMul.html 84 | [`SaturatingSub`]: https://docs.rs/num-traits/0.2/num_traits/ops/saturating/trait.SaturatingSub.html 85 | [`Serialize`]: https://docs.rs/serde/1/serde/trait.Serialize.html 86 | [`Deserialize`]: https://docs.rs/serde/1/serde/trait.Deserialize.html 87 | [`IntoBytes`]: https://docs.rs/zerocopy/0.8/zerocopy/trait.IntoBytes.html 88 | [`FromZeros`]: https://docs.rs/zerocopy/0.8/zerocopy/trait.FromZeros.html 89 | [`Immutable`]: https://docs.rs/zerocopy/0.8/zerocopy/trait.Immutable.html 90 | [`Unaligned`]: https://docs.rs/zerocopy/0.8/zerocopy/trait.Unaligned.html 91 | [`Step`]: https://doc.rust-lang.org/nightly/core/iter/trait.Step.html 92 | [`Error`]: https://doc.rust-lang.org/stable/std/error/trait.Error.html 93 | [`ParseError`]: https://docs.rs/bounded-integer/*/bounded_integer/struct.ParseError.html 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides two types of bounded integer. 2 | //! 3 | //! # Macro-generated bounded integers 4 | //! 5 | //! The [`bounded_integer!`] macro allows you to define your own bounded integer type, given a 6 | //! specific (inclusive) range it inhabits. For example: 7 | //! 8 | //! ```rust 9 | #![cfg_attr(not(feature = "macro"), doc = "# #[cfg(any())] {")] 10 | #![cfg_attr(feature = "step_trait", doc = "# #![feature(step_trait)]")] 11 | //! # use bounded_integer::bounded_integer; 12 | //! bounded_integer! { 13 | //! struct MyInteger(0, 7); 14 | //! } 15 | //! let num = MyInteger::new(5).unwrap(); 16 | //! assert_eq!(num, 5); 17 | #![cfg_attr(not(feature = "macro"), doc = "# }")] 18 | //! ``` 19 | //! 20 | //! This macro supports both `struct`s and `enum`s. See the [`examples`] module for the 21 | //! documentation of generated types. 22 | //! 23 | //! # Const generics-based bounded integers 24 | //! 25 | //! You can also create ad-hoc bounded integers via types in this library that use const generics, 26 | //! for example: 27 | //! 28 | //! ```rust 29 | #![cfg_attr(feature = "step_trait", doc = "# #![feature(step_trait)]")] 30 | //! # use bounded_integer::BoundedU8; 31 | //! let num = >::new(5).unwrap(); 32 | //! assert_eq!(num, 5); 33 | //! ``` 34 | //! 35 | //! These integers are shorter to use as they don't require a type declaration or explicit name. 36 | //! However due to the limits of const generics, they may not implement some traits – 37 | //! namely [`Default`], bytemuck’s [`Zeroable`] and zerocopy’s [`FromZeros`]. 38 | //! Also, unlike their macro counterparts they will not be subject to niche layout optimizations. 39 | //! 40 | //! # `no_std` 41 | //! 42 | //! All the integers in this crate depend only on libcore and so work in `#![no_std]` environments. 43 | //! 44 | //! # Crate Features 45 | //! 46 | //! By default, no crate features are enabled. 47 | //! - `std`: Interopate with `std` — implies `alloc`. Enables the following things: 48 | //! - An implementation of [`Error`] for [`ParseError`]. 49 | //! - `alloc`: Interopate with `alloc`. Has no effect currently. 50 | //! - `macro`: Enable the [`bounded_integer!`] macro. 51 | //! - `arbitrary1`: Implement [`Arbitrary`] for the bounded integers. This is useful when using 52 | //! bounded integers as fuzzing inputs. 53 | //! - `bytemuck1`: Implement [`Contiguous`] and [`NoUninit`] for all bounded integers, 54 | //! and [`Zeroable`] for macro-generated bounded integers that support it. 55 | //! - `num-traits02`: Implement [`Bounded`], [`AsPrimitive`], [`FromPrimitive`], [`NumCast`], 56 | //! [`ToPrimitive`], [`CheckedAdd`], [`CheckedDiv`], [`CheckedMul`], [`CheckedNeg`], 57 | //! [`CheckedRem`], [`CheckedSub`], [`MulAdd`], [`SaturatingAdd`], [`SaturatingMul`] and 58 | //! [`SaturatingSub`] for all bounded integers. 59 | //! - `serde1`: Implement [`Serialize`] and [`Deserialize`] for the bounded integers, making sure all 60 | //! values will never be out of bounds. 61 | //! - `zerocopy`: Implement [`IntoBytes`] and [`Immutable`] for all bounded integers, 62 | //! [`Unaligned`] for ones backed by `u8` or `i8`, 63 | //! and [`FromZeros`] for suitable macro-generated ones. 64 | //! - `step_trait`: Implement the [`Step`] trait which allows the bounded integers to be easily used 65 | //! in ranges. This will require you to use nightly and place `#![feature(step_trait)]` in your 66 | //! crate root if you use the macro. 67 | //! 68 | //! [`bounded_integer!`]: https://docs.rs/bounded-integer/*/bounded_integer/macro.bounded_integer.html 69 | //! [`examples`]: https://docs.rs/bounded-integer/*/bounded_integer/examples/ 70 | //! [`Arbitrary`]: https://docs.rs/arbitrary/1/arbitrary/trait.Arbitrary.html 71 | //! [`Contiguous`]: https://docs.rs/bytemuck/1/bytemuck/trait.Contiguous.html 72 | //! [`NoUninit`]: https://docs.rs/bytemuck/1/bytemuck/trait.NoUninit.html 73 | //! [`Zeroable`]: https://docs.rs/bytemuck/1/bytemuck/trait.Zeroable.html 74 | //! [`Bounded`]: https://docs.rs/num-traits/0.2/num_traits/bounds/trait.Bounded.html 75 | //! [`AsPrimitive`]: https://docs.rs/num-traits/0.2/num_traits/cast/trait.AsPrimitive.html 76 | //! [`FromPrimitive`]: https://docs.rs/num-traits/0.2/num_traits/cast/trait.FromPrimitive.html 77 | //! [`NumCast`]: https://docs.rs/num-traits/0.2/num_traits/cast/trait.NumCast.html 78 | //! [`ToPrimitive`]: https://docs.rs/num-traits/0.2/num_traits/cast/trait.ToPrimitive.html 79 | //! [`CheckedAdd`]: https://docs.rs/num-traits/0.2/num_traits/ops/checked/trait.CheckedAdd.html 80 | //! [`CheckedDiv`]: https://docs.rs/num-traits/0.2/num_traits/ops/checked/trait.CheckedDiv.html 81 | //! [`CheckedMul`]: https://docs.rs/num-traits/0.2/num_traits/ops/checked/trait.CheckedMul.html 82 | //! [`CheckedNeg`]: https://docs.rs/num-traits/0.2/num_traits/ops/checked/trait.CheckedNeg.html 83 | //! [`CheckedRem`]: https://docs.rs/num-traits/0.2/num_traits/ops/checked/trait.CheckedRem.html 84 | //! [`CheckedSub`]: https://docs.rs/num-traits/0.2/num_traits/ops/checked/trait.CheckedSub.html 85 | //! [`MulAdd`]: https://docs.rs/num-traits/0.2/num_traits/ops/mul_add/trait.MulAdd.html 86 | //! [`SaturatingAdd`]: https://docs.rs/num-traits/0.2/num_traits/ops/saturating/trait.SaturatingAdd.html 87 | //! [`SaturatingMul`]: https://docs.rs/num-traits/0.2/num_traits/ops/saturating/trait.SaturatingMul.html 88 | //! [`SaturatingSub`]: https://docs.rs/num-traits/0.2/num_traits/ops/saturating/trait.SaturatingSub.html 89 | //! [`Serialize`]: https://docs.rs/serde/1/serde/trait.Serialize.html 90 | //! [`Deserialize`]: https://docs.rs/serde/1/serde/trait.Deserialize.html 91 | //! [`IntoBytes`]: https://docs.rs/zerocopy/0.8/zerocopy/trait.IntoBytes.html 92 | //! [`FromZeros`]: https://docs.rs/zerocopy/0.8/zerocopy/trait.FromZeros.html 93 | //! [`Immutable`]: https://docs.rs/zerocopy/0.8/zerocopy/trait.Immutable.html 94 | //! [`Unaligned`]: https://docs.rs/zerocopy/0.8/zerocopy/trait.Unaligned.html 95 | //! [`Step`]: https://doc.rust-lang.org/nightly/core/iter/trait.Step.html 96 | //! [`Error`]: https://doc.rust-lang.org/stable/std/error/trait.Error.html 97 | //! [`ParseError`]: https://docs.rs/bounded-integer/*/bounded_integer/struct.ParseError.html 98 | #![warn(clippy::pedantic, rust_2018_idioms, unused_qualifications)] 99 | #![allow(clippy::items_after_statements, clippy::missing_errors_doc)] 100 | #![cfg_attr(feature = "step_trait", feature(step_trait))] 101 | #![cfg_attr(feature = "__doc", feature(doc_cfg))] 102 | #![no_std] 103 | 104 | #[cfg(feature = "std")] 105 | extern crate std; 106 | 107 | #[cfg(feature = "alloc")] 108 | #[cfg(test)] 109 | extern crate alloc; 110 | 111 | mod types; 112 | pub use types::*; 113 | 114 | #[cfg(feature = "macro")] 115 | mod r#macro; 116 | 117 | mod parse; 118 | pub use parse::{ParseError, ParseErrorKind}; 119 | 120 | mod prim_int; 121 | pub use prim_int::TryFromError; 122 | 123 | // Not public API. 124 | #[doc(hidden)] 125 | pub mod __private { 126 | #[cfg(feature = "arbitrary1")] 127 | pub use ::arbitrary1; 128 | 129 | #[cfg(feature = "bytemuck1")] 130 | pub use ::bytemuck1; 131 | 132 | #[cfg(feature = "num-traits02")] 133 | pub use ::num_traits02; 134 | 135 | #[cfg(feature = "serde1")] 136 | pub use ::serde1; 137 | 138 | #[cfg(feature = "zerocopy")] 139 | pub use ::zerocopy; 140 | 141 | #[cfg(feature = "macro")] 142 | pub use bounded_integer_macro::bounded_integer as proc_macro; 143 | 144 | // Helper to allow type-driven dispatch in `const fn`. 145 | pub struct Dispatch(T, core::convert::Infallible); 146 | 147 | pub trait NewWrapping { 148 | #[doc(hidden)] 149 | fn new_wrapping(n: T) -> Self; 150 | } 151 | 152 | pub use crate::parse::{error_above_max, error_below_min}; 153 | pub use crate::prim_int::{Signed, Unsigned, Wide, try_from_error}; 154 | 155 | feature_flags! { $ 156 | __cfg_arbitrary1 "arbitrary1", 157 | __cfg_bytemuck1 "bytemuck1", 158 | __cfg_num_traits02 "num-traits02", 159 | __cfg_serde1 "serde1", 160 | __cfg_step_trait "step_trait", 161 | __cfg_zerocopy "zerocopy", 162 | } 163 | macro_rules! feature_flags { 164 | ($d:tt $($cfg:ident $flag:literal,)*) => { $( 165 | #[macro_export] 166 | #[cfg(feature = $flag)] 167 | #[doc(hidden)] 168 | #[cfg(not(feature = "__doc"))] 169 | macro_rules! $cfg { 170 | ($d ($tt:tt)*) => { $d ($tt)* }; 171 | } 172 | // If the `__doc` feature flag is enabled, we are building for the current crate, and 173 | // thus we forward the `cfg` so that `doc_cfg` sees it. 174 | #[macro_export] 175 | #[cfg(feature = $flag)] 176 | #[doc(hidden)] 177 | #[cfg(feature = "__doc")] 178 | macro_rules! $cfg { 179 | ($d ($item:item)*) => { $d (#[cfg(feature = $flag)] $item)* }; 180 | } 181 | #[macro_export] 182 | #[cfg(not(feature = $flag))] 183 | #[doc(hidden)] 184 | macro_rules! $cfg { 185 | ($d ($tt:tt)*) => {}; 186 | } 187 | pub use $cfg; 188 | )* }; 189 | } 190 | use feature_flags; 191 | } 192 | 193 | #[cfg(feature = "__doc")] 194 | pub mod examples; 195 | 196 | #[test] 197 | #[cfg(feature = "macro")] 198 | // Don’t test on Nightly because the output is different to stable. 199 | #[cfg(not(feature = "__doc"))] 200 | fn ui() { 201 | let t = trybuild::TestCases::new(); 202 | t.pass("tests/force_build.rs"); 203 | t.compile_fail("ui/*.rs"); 204 | } 205 | 206 | mod unsafe_api; 207 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "arbitrary" 7 | version = "1.4.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "1.4.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 16 | 17 | [[package]] 18 | name = "bounded-integer" 19 | version = "0.6.1" 20 | dependencies = [ 21 | "arbitrary", 22 | "bounded-integer-macro", 23 | "bytemuck", 24 | "num-traits", 25 | "serde", 26 | "trybuild", 27 | "zerocopy", 28 | ] 29 | 30 | [[package]] 31 | name = "bounded-integer-macro" 32 | version = "0.6.0" 33 | dependencies = [ 34 | "proc-macro2", 35 | "quote", 36 | ] 37 | 38 | [[package]] 39 | name = "bytemuck" 40 | version = "1.21.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" 43 | 44 | [[package]] 45 | name = "equivalent" 46 | version = "1.0.2" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 49 | 50 | [[package]] 51 | name = "glob" 52 | version = "0.3.2" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 55 | 56 | [[package]] 57 | name = "hashbrown" 58 | version = "0.15.5" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 61 | 62 | [[package]] 63 | name = "indexmap" 64 | version = "2.10.0" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" 67 | dependencies = [ 68 | "equivalent", 69 | "hashbrown", 70 | ] 71 | 72 | [[package]] 73 | name = "itoa" 74 | version = "1.0.15" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 77 | 78 | [[package]] 79 | name = "memchr" 80 | version = "2.7.5" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 83 | 84 | [[package]] 85 | name = "num-traits" 86 | version = "0.2.19" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 89 | dependencies = [ 90 | "autocfg", 91 | ] 92 | 93 | [[package]] 94 | name = "optimization-tests" 95 | version = "0.1.0" 96 | dependencies = [ 97 | "bounded-integer", 98 | ] 99 | 100 | [[package]] 101 | name = "proc-macro2" 102 | version = "1.0.93" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 105 | dependencies = [ 106 | "unicode-ident", 107 | ] 108 | 109 | [[package]] 110 | name = "quote" 111 | version = "1.0.38" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 114 | dependencies = [ 115 | "proc-macro2", 116 | ] 117 | 118 | [[package]] 119 | name = "ryu" 120 | version = "1.0.20" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 123 | 124 | [[package]] 125 | name = "serde" 126 | version = "1.0.217" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 129 | dependencies = [ 130 | "serde_derive", 131 | ] 132 | 133 | [[package]] 134 | name = "serde_derive" 135 | version = "1.0.217" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 138 | dependencies = [ 139 | "proc-macro2", 140 | "quote", 141 | "syn", 142 | ] 143 | 144 | [[package]] 145 | name = "serde_json" 146 | version = "1.0.142" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" 149 | dependencies = [ 150 | "itoa", 151 | "memchr", 152 | "ryu", 153 | "serde", 154 | ] 155 | 156 | [[package]] 157 | name = "serde_spanned" 158 | version = "1.0.0" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" 161 | dependencies = [ 162 | "serde", 163 | ] 164 | 165 | [[package]] 166 | name = "syn" 167 | version = "2.0.96" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" 170 | dependencies = [ 171 | "proc-macro2", 172 | "quote", 173 | "unicode-ident", 174 | ] 175 | 176 | [[package]] 177 | name = "target-triple" 178 | version = "0.1.4" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" 181 | 182 | [[package]] 183 | name = "termcolor" 184 | version = "1.4.1" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 187 | dependencies = [ 188 | "winapi-util", 189 | ] 190 | 191 | [[package]] 192 | name = "toml" 193 | version = "0.9.5" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" 196 | dependencies = [ 197 | "indexmap", 198 | "serde", 199 | "serde_spanned", 200 | "toml_datetime", 201 | "toml_parser", 202 | "toml_writer", 203 | "winnow", 204 | ] 205 | 206 | [[package]] 207 | name = "toml_datetime" 208 | version = "0.7.0" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" 211 | dependencies = [ 212 | "serde", 213 | ] 214 | 215 | [[package]] 216 | name = "toml_parser" 217 | version = "1.0.2" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" 220 | dependencies = [ 221 | "winnow", 222 | ] 223 | 224 | [[package]] 225 | name = "toml_writer" 226 | version = "1.0.2" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" 229 | 230 | [[package]] 231 | name = "trybuild" 232 | version = "1.0.110" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "32e257d7246e7a9fd015fb0b28b330a8d4142151a33f03e6a497754f4b1f6a8e" 235 | dependencies = [ 236 | "glob", 237 | "serde", 238 | "serde_derive", 239 | "serde_json", 240 | "target-triple", 241 | "termcolor", 242 | "toml", 243 | ] 244 | 245 | [[package]] 246 | name = "unicode-ident" 247 | version = "1.0.14" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 250 | 251 | [[package]] 252 | name = "winapi-util" 253 | version = "0.1.9" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 256 | dependencies = [ 257 | "windows-sys", 258 | ] 259 | 260 | [[package]] 261 | name = "windows-sys" 262 | version = "0.59.0" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 265 | dependencies = [ 266 | "windows-targets", 267 | ] 268 | 269 | [[package]] 270 | name = "windows-targets" 271 | version = "0.52.6" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 274 | dependencies = [ 275 | "windows_aarch64_gnullvm", 276 | "windows_aarch64_msvc", 277 | "windows_i686_gnu", 278 | "windows_i686_gnullvm", 279 | "windows_i686_msvc", 280 | "windows_x86_64_gnu", 281 | "windows_x86_64_gnullvm", 282 | "windows_x86_64_msvc", 283 | ] 284 | 285 | [[package]] 286 | name = "windows_aarch64_gnullvm" 287 | version = "0.52.6" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 290 | 291 | [[package]] 292 | name = "windows_aarch64_msvc" 293 | version = "0.52.6" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 296 | 297 | [[package]] 298 | name = "windows_i686_gnu" 299 | version = "0.52.6" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 302 | 303 | [[package]] 304 | name = "windows_i686_gnullvm" 305 | version = "0.52.6" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 308 | 309 | [[package]] 310 | name = "windows_i686_msvc" 311 | version = "0.52.6" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 314 | 315 | [[package]] 316 | name = "windows_x86_64_gnu" 317 | version = "0.52.6" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 320 | 321 | [[package]] 322 | name = "windows_x86_64_gnullvm" 323 | version = "0.52.6" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 326 | 327 | [[package]] 328 | name = "windows_x86_64_msvc" 329 | version = "0.52.6" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 332 | 333 | [[package]] 334 | name = "winnow" 335 | version = "0.7.12" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" 338 | 339 | [[package]] 340 | name = "zerocopy" 341 | version = "0.8.14" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468" 344 | dependencies = [ 345 | "zerocopy-derive", 346 | ] 347 | 348 | [[package]] 349 | name = "zerocopy-derive" 350 | version = "0.8.14" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1" 353 | dependencies = [ 354 | "proc-macro2", 355 | "quote", 356 | "syn", 357 | ] 358 | -------------------------------------------------------------------------------- /src/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` 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] 90 | macro_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)] 152 | macro_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)] 169 | macro_rules! __dispatch_zerocopy { 170 | ($($t:tt)*) => { 171 | $crate::__helper! { vis [-] $($t)* } 172 | }; 173 | } 174 | 175 | #[macro_export] 176 | #[doc(hidden)] 177 | macro_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)] 214 | mod 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() 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() {} 340 | #[expect(unused)] 341 | trait NotZeroable {} 342 | impl NotZeroable<0> for T {} 343 | #[cfg(feature = "bytemuck1")] 344 | impl NotZeroable<1> for T {} 345 | #[cfg(feature = "zerocopy")] 346 | impl NotZeroable<2> for T {} 347 | #[cfg(feature = "num-traits02")] 348 | impl 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::(); 363 | 364 | bounded_integer!(struct B(-459, 3);); 365 | assert_zeroable::(); 366 | 367 | bounded_integer!(struct C(1, 5);); 368 | not_zeroable!(C); 369 | assert_eq!(size_of::>(), size_of::()); 370 | 371 | bounded_integer!(struct D(-5, -1);); 372 | not_zeroable!(D); 373 | assert_eq!(size_of::>(), size_of::()); 374 | 375 | bounded_integer!(struct E(-(5), 0_i8);); 376 | not_zeroable!(E); 377 | assert_ne!(size_of::>(), size_of::()); 378 | } 379 | 380 | #[test] 381 | #[cfg(feature = "num-traits02")] 382 | fn one() { 383 | fn assert_one() {} 384 | bounded_integer!(struct A(1, 1);); 385 | assert_one::(); 386 | } 387 | 388 | #[test] 389 | #[cfg(feature = "zerocopy")] 390 | fn unaligned() { 391 | fn assert_unaligned() {} 392 | bounded_integer!(struct A(0, 255);); 393 | assert_unaligned::(); 394 | bounded_integer!(struct B(-128, 127);); 395 | assert_unaligned::(); 396 | 397 | #[expect(unused)] 398 | trait NotUnaligned {} 399 | impl 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 | } 453 | -------------------------------------------------------------------------------- /macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A macro for generating bounded integer structs and enums. 2 | //! 3 | //! This crate is unstable and must not be used directly. 4 | #![warn(clippy::pedantic, rust_2018_idioms, unused_qualifications)] 5 | #![allow(clippy::single_match_else, clippy::match_bool)] 6 | 7 | use std::array; 8 | use std::fmt::Debug; 9 | 10 | use proc_macro2::{Delimiter, Ident, Literal, Span, TokenStream, TokenTree}; 11 | use quote::{ToTokens, quote, quote_spanned}; 12 | 13 | #[proc_macro] 14 | #[doc(hidden)] 15 | #[expect(clippy::too_many_lines)] 16 | pub fn bounded_integer(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 17 | let input = TokenStream::from(input).into_iter().map(|t| { 18 | let TokenTree::Group(group) = t else { 19 | panic!("non-group in input") 20 | }; 21 | assert_eq!(group.delimiter(), Delimiter::Bracket); 22 | group.stream() 23 | }); 24 | let [ 25 | zerocopy, 26 | outer_attr, 27 | mut attrs, 28 | vis, 29 | super_vis, 30 | is_named, 31 | item_kind, 32 | name, 33 | min_or_variants, 34 | max_or_none, 35 | crate_path, 36 | ] = to_array(input); 37 | 38 | let zerocopy = match to_array(zerocopy) { 39 | [TokenTree::Punct(p)] if p.as_char() == '-' => false, 40 | [TokenTree::Punct(p)] if p.as_char() == '+' => true, 41 | [t] => panic!("zerocopy ({t})"), 42 | }; 43 | 44 | let [TokenTree::Ident(item_kind)] = to_array(item_kind) else { 45 | panic!("item kind") 46 | }; 47 | let is_enum = match &*item_kind.to_string() { 48 | "struct" => false, 49 | "enum" => true, 50 | s => panic!("unknown item kind {s}"), 51 | }; 52 | let [TokenTree::Ident(name)] = to_array(name) else { 53 | panic!("name") 54 | }; 55 | 56 | let mut new_attrs = TokenStream::new(); 57 | let mut import_attrs = TokenStream::new(); 58 | let mut maybe_repr = None; 59 | for attr in attrs { 60 | let TokenTree::Group(group) = &attr else { 61 | panic!("attr ({attr})") 62 | }; 63 | let tokens = group.stream().into_iter().collect::>(); 64 | if let Some(TokenTree::Ident(i)) = tokens.first() { 65 | let name = i.to_string(); 66 | 67 | if name == "repr" 68 | && let [_, TokenTree::Group(g)] = &*tokens 69 | && g.delimiter() == Delimiter::Parenthesis 70 | { 71 | if maybe_repr.is_some() { 72 | return error!(i.span(), "duplicate `repr` attribute"); 73 | } 74 | maybe_repr = Some(g.stream()); 75 | continue; 76 | } else if ["allow", "expect", "warn", "deny", "forbid"].contains(&&*name) 77 | && let [_, TokenTree::Group(g)] = &*tokens 78 | && g.delimiter() == Delimiter::Parenthesis 79 | && let [Some(TokenTree::Ident(lint)), None] = { 80 | let mut iter = g.stream().into_iter(); 81 | [iter.next(), iter.next()] 82 | } 83 | && (lint == "unused" || lint == "unused_imports") 84 | { 85 | import_attrs.extend(quote!(# #attr)); 86 | continue; 87 | } 88 | } 89 | // Just using `quote!` sometimes triggers a false positive in the `clippy::doc_markdown` 90 | // lint when `#[cfg_attr(cond, doc = "SomeCode")]` is used inside a code block. 91 | new_attrs.extend(quote_spanned!(group.span()=> # #attr)); 92 | } 93 | attrs = new_attrs; 94 | 95 | let (variants, min, max, min_val, max_val); 96 | match to_array(is_named) { 97 | // Unnamed 98 | [TokenTree::Punct(p)] if p.as_char() == '-' => { 99 | [min, max] = [min_or_variants, max_or_none].map(ungroup_none); 100 | [min_val, max_val] = [&min, &max].map(|lit| { 101 | parse_literal(lit.clone()).map(|(lit, repr)| { 102 | // if there is an existing repr, Rust will cause an error anyway later on 103 | if let Some(repr) = repr 104 | && maybe_repr.is_none() 105 | { 106 | maybe_repr = Some(quote!(#repr)); 107 | } 108 | lit 109 | }) 110 | }); 111 | 112 | variants = match is_enum { 113 | false => None, 114 | true => { 115 | let Some(min_val) = min_val else { 116 | return error!(min, "`enum` requires bound to be statically known"); 117 | }; 118 | let Some(max_val) = max_val else { 119 | return error!(max, "`enum` requires bound to be statically known"); 120 | }; 121 | let Some(range) = range(min_val, max_val) else { 122 | return error!(min, "refusing to generate this many `enum` variants"); 123 | }; 124 | let mut variants = TokenStream::new(); 125 | let min_span = stream_span(min.clone()); 126 | for int in range { 127 | let enum_variant_name = int.enum_variant_name(min_span); 128 | if int == min_val { 129 | variants.extend(quote!(#[allow(dead_code)] #enum_variant_name = #min,)); 130 | } else { 131 | variants.extend(quote!(#[allow(dead_code)] #enum_variant_name,)); 132 | } 133 | } 134 | Some(variants) 135 | } 136 | }; 137 | } 138 | // Named 139 | [TokenTree::Punct(p)] if p.as_char() == '+' => { 140 | assert!(is_enum); 141 | assert!(max_or_none.into_iter().next().is_none()); 142 | 143 | // ((min_val, min), current_val, current_span) 144 | let mut min_current = None::<((Int, TokenStream), Int, Span)>; 145 | let mut variant_list = TokenStream::new(); 146 | for variant in min_or_variants { 147 | let TokenTree::Group(variant) = variant else { 148 | panic!("variant") 149 | }; 150 | let [ 151 | TokenTree::Group(attrs), 152 | TokenTree::Ident(variant_name), 153 | TokenTree::Group(variant_val), 154 | ] = to_array(variant.stream()) 155 | else { 156 | panic!("variant inner") 157 | }; 158 | let attrs = attrs.stream(); 159 | let variant_val = variant_val.stream(); 160 | min_current = Some(if variant_val.is_empty() { 161 | variant_list.extend(quote!(#attrs #variant_name,)); 162 | match min_current { 163 | Some((min, current, current_span)) => match current.succ() { 164 | Some(current) => (min, current, current_span), 165 | None => { 166 | return error!( 167 | variant_name.span(), 168 | "too many variants (overflows a u128)" 169 | ); 170 | } 171 | }, 172 | None => ( 173 | (Int::new(true, 0), quote_spanned!(variant_name.span()=> 0)), 174 | Int::new(true, 0), 175 | variant_name.span(), 176 | ), 177 | } 178 | } else { 179 | variant_list.extend(quote!(#attrs #variant_name = #variant_val,)); 180 | let variant_val = ungroup_none(variant_val); 181 | let Some((int, _)) = parse_literal(variant_val.clone()) else { 182 | return error!(variant_val, "could not parse variant value"); 183 | }; 184 | match min_current { 185 | Some((min, current, _)) if current.succ() == Some(int) => { 186 | (min, int, stream_span(variant_val)) 187 | } 188 | Some(_) => return error!(variant_val, "enum not contiguous"), 189 | None => ((int, variant_val.clone()), int, stream_span(variant_val)), 190 | } 191 | }); 192 | } 193 | variants = Some(variant_list); 194 | [(min_val, min), (max_val, max)] = match min_current { 195 | Some(((min_val, min), current, current_span)) => [ 196 | (Some(min_val), min), 197 | (Some(current), current.literal(current_span)), 198 | ], 199 | None => [ 200 | (Some(Int::new(true, 1)), quote!(1)), 201 | (Some(Int::new(true, 0)), quote!(0)), 202 | ], 203 | }; 204 | } 205 | [t] => panic!("named ({t})"), 206 | } 207 | 208 | let zero = min_val 209 | .zip(max_val) 210 | .map(|(min, max)| (min..=max).contains(&Int::new(true, 0))); 211 | let one = min_val 212 | .zip(max_val) 213 | .map(|(min, max)| (min..=max).contains(&Int::new(true, 1))); 214 | if zero == Some(true) && zerocopy { 215 | attrs.extend(quote!(#[derive(#crate_path::__private::zerocopy::FromZeros)])); 216 | } 217 | let zero_token = match zero { 218 | Some(true) => quote!(zero,), 219 | Some(false) | None => quote!(), 220 | }; 221 | let one_token = match one { 222 | Some(true) => quote!(one,), 223 | Some(false) | None => quote!(), 224 | }; 225 | 226 | let repr = match (maybe_repr, min_val, max_val) { 227 | (Some(repr), _, _) => repr, 228 | (None, Some(min_val), Some(max_val)) => match infer_repr(min_val, max_val) { 229 | Some(repr) => { 230 | let repr = Ident::new(&repr, stream_span(min.clone())); 231 | quote!(#repr) 232 | } 233 | None => return error!(min, "range too large for any integer type"), 234 | }, 235 | (None, _, _) => { 236 | let msg = "no #[repr] attribute found, and could not infer"; 237 | return error!(min, "{msg}"); 238 | } 239 | }; 240 | 241 | match is_enum { 242 | false => attrs.extend(quote!(#[repr(transparent)])), 243 | true => attrs.extend(quote!(#[repr(#repr)])), 244 | } 245 | 246 | if matches!(repr.to_string().trim(), "u8" | "i8") && zerocopy { 247 | attrs.extend(quote!(#[derive(#crate_path::__private::zerocopy::Unaligned)])); 248 | } 249 | 250 | let item = match variants { 251 | Some(variants) => quote!({ #variants }), 252 | None if zero == Some(false) => quote!((::core::num::NonZero<#repr>);), 253 | None => quote!((#repr);), 254 | }; 255 | 256 | // Hide in a module to prevent access to private parts. 257 | let module_name = Ident::new(&format!("__bounded_integer_private_{name}"), name.span()); 258 | 259 | let res = quote!( 260 | #[allow(non_snake_case)] 261 | #outer_attr 262 | mod #module_name { 263 | #attrs 264 | #super_vis #item_kind #name #item 265 | 266 | #crate_path::unsafe_api! { 267 | for #name, 268 | unsafe repr: #repr, 269 | min: #min, 270 | max: #max, 271 | #zero_token 272 | #one_token 273 | } 274 | } 275 | #import_attrs #vis use #module_name::#name; 276 | ); 277 | 278 | res.into() 279 | } 280 | 281 | fn to_array, const N: usize>(iter: I) -> [I::Item; N] { 282 | let mut iter = iter.into_iter(); 283 | let array = array::from_fn(|_| iter.next().expect("iterator too short")); 284 | if let Some(item) = iter.next() { 285 | panic!("iterator too long: found {item:?}"); 286 | } 287 | array 288 | } 289 | 290 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 291 | struct Int { 292 | nonnegative: bool, 293 | magnitude: u128, 294 | } 295 | 296 | impl Int { 297 | fn new(nonnegative: bool, magnitude: u128) -> Self { 298 | Self { 299 | nonnegative, 300 | magnitude, 301 | } 302 | } 303 | fn succ(self) -> Option { 304 | Some(match self.nonnegative { 305 | true => Self::new(true, self.magnitude.checked_add(1)?), 306 | false if self.magnitude == 1 => Self::new(true, 0), 307 | false => Self::new(false, self.magnitude - 1), 308 | }) 309 | } 310 | fn enum_variant_name(self, span: Span) -> Ident { 311 | if self.magnitude == 0 { 312 | Ident::new("Z", span) 313 | } else if self.nonnegative { 314 | Ident::new(&format!("P{}", self.magnitude), span) 315 | } else { 316 | Ident::new(&format!("N{}", self.magnitude), span) 317 | } 318 | } 319 | fn literal(self, span: Span) -> TokenStream { 320 | let mut magnitude = Literal::u128_unsuffixed(self.magnitude); 321 | magnitude.set_span(span); 322 | match self.nonnegative { 323 | true => quote!(#magnitude), 324 | false => quote!(-#magnitude), 325 | } 326 | } 327 | } 328 | 329 | impl PartialOrd for Int { 330 | fn partial_cmp(&self, other: &Self) -> Option { 331 | Some(self.cmp(other)) 332 | } 333 | } 334 | 335 | impl Ord for Int { 336 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 337 | match (self.nonnegative, other.nonnegative) { 338 | (true, true) => self.magnitude.cmp(&other.magnitude), 339 | (true, false) => std::cmp::Ordering::Greater, 340 | (false, true) => std::cmp::Ordering::Less, 341 | (false, false) => other.magnitude.cmp(&self.magnitude), 342 | } 343 | } 344 | } 345 | 346 | fn parse_literal(e: TokenStream) -> Option<(Int, Option)> { 347 | let mut tokens = e.into_iter().peekable(); 348 | let minus = tokens 349 | .next_if(|t| matches!(t, TokenTree::Punct(p) if p.as_char() == '-')) 350 | .is_some(); 351 | let Some(TokenTree::Literal(lit)) = tokens.next() else { 352 | return None; 353 | }; 354 | if tokens.next().is_some() { 355 | return None; 356 | } 357 | 358 | // Algorithm reference: 359 | // https://docs.rs/syn/2.0.104/src/syn/lit.rs.html#1679-1767 360 | 361 | let mut lit_chars = &*lit.to_string(); 362 | 363 | let (base, base_len) = match lit_chars.get(..2) { 364 | Some("0x") => (16, 2), 365 | Some("0o") => (8, 2), 366 | Some("0b") => (2, 2), 367 | _ => (10, 0), 368 | }; 369 | lit_chars = &lit_chars[base_len..]; 370 | 371 | let mut magnitude = 0_u128; 372 | let mut has_digit = None; 373 | 374 | let suffix = loop { 375 | lit_chars = lit_chars.trim_start_matches('_'); 376 | let Some(c) = lit_chars.chars().next() else { 377 | has_digit?; 378 | break None; 379 | }; 380 | if let 'i' | 'u' = c { 381 | let ("8" | "16" | "32" | "64" | "128" | "size") = &lit_chars[1..] else { 382 | return None; 383 | }; 384 | break Some(Ident::new(lit_chars, lit.span())); 385 | } 386 | let digit = c.to_digit(base)?; 387 | lit_chars = &lit_chars[1..]; 388 | magnitude = magnitude 389 | .checked_mul(base.into())? 390 | .checked_add(digit.into())?; 391 | has_digit = Some(()); 392 | }; 393 | 394 | let lit = Int::new(!minus || magnitude == 0, magnitude); 395 | Some((lit, suffix)) 396 | } 397 | 398 | fn range(min: Int, max: Int) -> Option> { 399 | let range_minus_one = match (max.nonnegative, min.nonnegative) { 400 | (true, true) => max.magnitude.saturating_sub(min.magnitude), 401 | (true, false) => max.magnitude.saturating_add(min.magnitude), 402 | (false, true) => 0, 403 | (false, false) => min.magnitude.saturating_sub(max.magnitude), 404 | }; 405 | if 100_000 <= range_minus_one { 406 | return None; 407 | } 408 | #[expect(clippy::reversed_empty_ranges)] 409 | let (negative_part, nonnegative_part) = match (min.nonnegative, max.nonnegative) { 410 | (true, true) => (1..=0, min.magnitude..=max.magnitude), 411 | (false, true) => (1..=min.magnitude, 0..=max.magnitude), 412 | (true, false) => (1..=0, 1..=0), 413 | (false, false) => (max.magnitude..=min.magnitude, 1..=0), 414 | }; 415 | let negative_part = negative_part.map(|i| Int::new(false, i)); 416 | let nonnegative_part = nonnegative_part.map(|i| Int::new(true, i)); 417 | Some(negative_part.rev().chain(nonnegative_part)) 418 | } 419 | 420 | fn infer_repr(min: Int, max: Int) -> Option { 421 | for bits in [8, 16, 32, 64, 128] { 422 | let fits_unsigned = 423 | |lit: Int| lit.nonnegative && lit.magnitude <= (u128::MAX >> (128 - bits)); 424 | let fits_signed = |lit: Int| { 425 | (lit.nonnegative && lit.magnitude < (1 << (bits - 1))) 426 | || (!lit.nonnegative && lit.magnitude <= (1 << (bits - 1))) 427 | }; 428 | if fits_unsigned(min) && fits_unsigned(max) { 429 | return Some(format!("u{bits}")); 430 | } else if fits_signed(min) && fits_signed(max) { 431 | return Some(format!("i{bits}")); 432 | } 433 | } 434 | None 435 | } 436 | 437 | fn ungroup_none(tokens: TokenStream) -> TokenStream { 438 | let mut tokens = tokens.into_iter().peekable(); 439 | if let Some(TokenTree::Group(g)) = 440 | tokens.next_if(|t| matches!(t, TokenTree::Group(g) if g.delimiter() == Delimiter::None)) 441 | { 442 | return g.stream(); 443 | } 444 | // Sigh… make it opportunistic to get it to work on rust-analyzer 445 | // https://github.com/rust-lang/rust-analyzer/issues/18211 446 | tokens.collect() 447 | } 448 | 449 | macro_rules! error { 450 | ($span:expr, $($fmt:tt)*) => {{ 451 | let span = SpanHelper($span).span_helper(); 452 | let msg = format!($($fmt)*); 453 | proc_macro::TokenStream::from(quote_spanned!(span=> compile_error!(#msg);)) 454 | }}; 455 | } 456 | use error; 457 | 458 | struct SpanHelper(T); 459 | impl SpanHelper { 460 | fn span_helper(self) -> Span { 461 | stream_span(self.0.into_token_stream()) 462 | } 463 | } 464 | trait SpanHelperTrait { 465 | fn span_helper(self) -> Span; 466 | } 467 | impl SpanHelperTrait for SpanHelper { 468 | fn span_helper(self) -> Span { 469 | self.0 470 | } 471 | } 472 | 473 | fn stream_span(stream: TokenStream) -> Span { 474 | stream 475 | .into_iter() 476 | .next() 477 | .map_or_else(Span::call_site, |token| token.span()) 478 | } 479 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | define_bounded_integers! { 2 | BoundedU8(u8, unaligned, unsigned), 3 | BoundedU16(u16, unsigned), 4 | BoundedU32(u32, unsigned), 5 | BoundedU64(u64, unsigned), 6 | BoundedU128(u128, unsigned), 7 | BoundedUsize(usize, unsigned), 8 | BoundedI8(i8, unaligned, unsigned), 9 | BoundedI16(i16), 10 | BoundedI32(i32), 11 | BoundedI64(i64), 12 | BoundedI128(i128), 13 | BoundedIsize(isize), 14 | } 15 | 16 | macro_rules! define_bounded_integers { 17 | ($($name:ident( 18 | $inner:ident 19 | $(, unaligned $([$unaligned:ident])?)? 20 | $(, unsigned $([$unsigned:ident])?)? 21 | ),)*) => { $( 22 | #[doc = "An"] 23 | #[doc = concat!("[`", stringify!($inner), "`]")] 24 | #[doc = "constrained to be in the range `MIN..=MAX`."] 25 | #[repr(transparent)] 26 | #[cfg_attr(feature = "zerocopy", derive(zerocopy::IntoBytes, zerocopy::Immutable))] 27 | #[cfg_attr( 28 | all(feature = "zerocopy", any($($(if $unaligned)? true)?)), 29 | derive(zerocopy::Unaligned), 30 | )] 31 | pub struct $name($inner); 32 | 33 | $crate::unsafe_api! { 34 | [const MIN: $inner, const MAX: $inner] for $name, 35 | unsafe repr: $inner, 36 | min: MIN, 37 | max: MAX, 38 | } 39 | 40 | #[cfg(any($($(if $unsigned)? true)?))] 41 | impl Default for $name<0, MAX> { 42 | fn default() -> Self { 43 | Self::const_new::<0>() 44 | } 45 | } 46 | 47 | #[cfg(feature = "bytemuck1")] 48 | #[cfg(any($($($unsigned)? true)?))] 49 | unsafe impl bytemuck1::Zeroable for $name<0, MAX> {} 50 | )* } 51 | } 52 | use define_bounded_integers; 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use super::*; 57 | 58 | #[test] 59 | fn range() { 60 | type Bounded = BoundedI8<3, 10>; 61 | assert_eq!(Bounded::MIN_VALUE, 3); 62 | assert_eq!(Bounded::MAX_VALUE, 10); 63 | assert_eq!(Bounded::MIN.get(), Bounded::MIN_VALUE); 64 | assert_eq!(Bounded::MAX.get(), Bounded::MAX_VALUE); 65 | 66 | assert!(Bounded::in_range(3)); 67 | assert!(!Bounded::in_range(2)); 68 | assert!(Bounded::in_range(10)); 69 | assert!(!Bounded::in_range(11)); 70 | } 71 | 72 | #[test] 73 | fn new_saturating() { 74 | type Bounded = BoundedI8<3, 10>; 75 | assert_eq!(Bounded::new_saturating(i8::MIN), Bounded::MIN); 76 | assert_eq!(Bounded::new_saturating(i8::MAX), Bounded::MAX); 77 | assert_eq!(Bounded::new_saturating(11).get(), 10); 78 | assert_eq!(Bounded::new_saturating(10).get(), 10); 79 | assert_eq!(Bounded::new_saturating(3).get(), 3); 80 | assert_eq!(Bounded::new_saturating(2).get(), 3); 81 | } 82 | 83 | #[test] 84 | fn wrapping_unwiden() { 85 | type Bounded = BoundedU8<2, 20>; 86 | 87 | assert_eq!(Bounded::new_wrapping(0_u8), 19); 88 | assert_eq!(Bounded::new_wrapping(2_u8), 2); 89 | assert_eq!(Bounded::new_wrapping(20_u8), 20); 90 | assert_eq!(Bounded::new_wrapping(21_u8), 2); 91 | assert_eq!(Bounded::new_wrapping(255_u8), 8); 92 | 93 | assert_eq!(Bounded::new_wrapping(12_u16), 12); 94 | assert_eq!(Bounded::new_wrapping(21_u16), 2); 95 | assert_eq!(Bounded::new_wrapping(12_i16), 12); 96 | assert_eq!(Bounded::new_wrapping(21_i16), 2); 97 | 98 | assert_eq!(Bounded::new_wrapping(0_u16), 19); 99 | assert_eq!(Bounded::new_wrapping(65535_u16), 4); 100 | assert_eq!(Bounded::new_wrapping(65533_u16), 2); 101 | assert_eq!(Bounded::new_wrapping(65532_u16), 20); 102 | 103 | assert_eq!(Bounded::new_wrapping(-32768_i16), 7); 104 | assert_eq!(Bounded::new_wrapping(-32755_i16), 20); 105 | assert_eq!(Bounded::new_wrapping(-32754_i16), 2); 106 | assert_eq!(Bounded::new_wrapping(32767_i16), 11); 107 | } 108 | 109 | #[test] 110 | fn wrapping_full() { 111 | type Bounded = BoundedI8<{ i8::MIN }, { i8::MAX }>; 112 | assert_eq!(Bounded::new_wrapping(37_i8), 37); 113 | assert_eq!(Bounded::new_wrapping(-128_i8), -128); 114 | assert_eq!(Bounded::new_wrapping(127_i8), 127); 115 | assert_eq!(Bounded::new_wrapping(128_i16), -128); 116 | assert_eq!(Bounded::new_wrapping(-200_i16), 56); 117 | assert_eq!(Bounded::new(25).unwrap().wrapping_pow(20), 97); 118 | } 119 | 120 | #[test] 121 | fn wrapping_signed() { 122 | type Bounded = BoundedI8<-5, 2>; 123 | assert_eq!(Bounded::new_wrapping(0_i8).get(), 0); 124 | assert_eq!(Bounded::new_wrapping(-5_i8).get(), -5); 125 | assert_eq!(Bounded::new_wrapping(2_i8).get(), 2); 126 | assert_eq!(Bounded::new_wrapping(-128_i8).get(), 0); 127 | } 128 | 129 | #[test] 130 | fn arithmetic() { 131 | #![expect(clippy::modulo_one)] 132 | type Bounded = BoundedI8<-5, 20>; 133 | assert_eq!(Bounded::new(2).unwrap() + 3, 5); 134 | assert_eq!(Bounded::new(2).unwrap() - 5, -3); 135 | assert_eq!(Bounded::new(3).unwrap() * 5, 15); 136 | assert_eq!(Bounded::new(11).unwrap() / 3, 3); 137 | assert_eq!(Bounded::new(11).unwrap() % 3, 2); 138 | assert_eq!(Bounded::new(-3).unwrap() / 2, -1); 139 | assert_eq!(Bounded::new(-3).unwrap() % 2, -1); 140 | assert_eq!(-Bounded::new(-3).unwrap(), 3); 141 | assert_eq!(!Bounded::new(-3).unwrap(), 2); 142 | assert_eq!(-&Bounded::new(-3).unwrap(), 3); 143 | assert_eq!(!&Bounded::new(-3).unwrap(), 2); 144 | assert_eq!(Bounded::new(3).unwrap() << 1, 6); 145 | assert_eq!(Bounded::new(3).unwrap() >> 1, 1); 146 | variations!(Bounded, i8, + += - -= * *= / /= % %=); 147 | 148 | assert_eq!(Bounded::new(2).unwrap().pow(3).get(), 8); 149 | assert_eq!(Bounded::new(-3).unwrap().div_euclid(2).get(), -2); 150 | assert_eq!(Bounded::new(-3).unwrap().rem_euclid(2).get(), 1); 151 | assert_eq!(Bounded::new(-3).unwrap().abs().get(), 3); 152 | assert_eq!(Bounded::new(4).unwrap().abs().get(), 4); 153 | 154 | macro_rules! variations { 155 | ($ty:ty, $inner:ty, $($op:tt $op_assign:tt)*) => { 156 | $( 157 | let _: $ty = <$ty>::new(0).unwrap() $op 1; 158 | let _: $ty = &<$ty>::new(0).unwrap() $op 1; 159 | let _: $ty = <$ty>::new(0).unwrap() $op &1; 160 | let _: $ty = &<$ty>::new(0).unwrap() $op &1; 161 | let _: $inner = 0 $op <$ty>::new(1).unwrap(); 162 | let _: $inner = 0 $op &<$ty>::new(1).unwrap(); 163 | let _: $inner = &0 $op <$ty>::new(1).unwrap(); 164 | let _: $inner = &0 $op &<$ty>::new(1).unwrap(); 165 | let _: $ty = <$ty>::new(0).unwrap() $op <$ty>::new(1).unwrap(); 166 | let _: $ty = &<$ty>::new(0).unwrap() $op <$ty>::new(1).unwrap(); 167 | let _: $ty = <$ty>::new(0).unwrap() $op &<$ty>::new(1).unwrap(); 168 | let _: $ty = &<$ty>::new(0).unwrap() $op &<$ty>::new(1).unwrap(); 169 | *&mut <$ty>::new(0).unwrap() $op_assign 1; 170 | *&mut <$ty>::new(0).unwrap() $op_assign &1; 171 | *&mut <$ty>::new(0).unwrap() $op_assign <$ty>::new(1).unwrap(); 172 | *&mut <$ty>::new(0).unwrap() $op_assign &<$ty>::new(1).unwrap(); 173 | *&mut 0 $op_assign <$ty>::new(1).unwrap(); 174 | *&mut 0 $op_assign &<$ty>::new(1).unwrap(); 175 | )* 176 | }; 177 | } 178 | use variations; 179 | } 180 | 181 | #[test] 182 | fn saturating() { 183 | type Bounded = BoundedI8<-5, 20>; 184 | assert_eq!(Bounded::new(13).unwrap().saturating_add(1).get(), 14); 185 | assert_eq!(Bounded::new(14).unwrap().saturating_add(7).get(), 20); 186 | assert_eq!(Bounded::new(-2).unwrap().saturating_sub(-1).get(), -1); 187 | assert_eq!(Bounded::new(-2).unwrap().saturating_sub(127).get(), -5); 188 | assert_eq!(Bounded::new(2).unwrap().saturating_mul(3).get(), 6); 189 | assert_eq!(Bounded::new(15).unwrap().saturating_mul(-1).get(), -5); 190 | assert_eq!(Bounded::new(3).unwrap().saturating_pow(2).get(), 9); 191 | assert_eq!(Bounded::new(3).unwrap().saturating_pow(3).get(), 20); 192 | assert_eq!(Bounded::new(-4).unwrap().saturating_neg().get(), 4); 193 | assert_eq!(Bounded::new(8).unwrap().saturating_neg().get(), -5); 194 | assert_eq!(Bounded::new(8).unwrap().saturating_abs().get(), 8); 195 | assert_eq!(>::new(-6).unwrap().saturating_abs(), 5); 196 | } 197 | 198 | #[test] 199 | fn checked() { 200 | type Bounded = BoundedI8<-5, 20>; 201 | assert_eq!(Bounded::new(13).unwrap().checked_add(2).unwrap().get(), 15); 202 | assert_eq!(Bounded::new(14).unwrap().checked_add(7), None); 203 | assert_eq!(Bounded::new(-2).unwrap().checked_sub(-1).unwrap().get(), -1); 204 | assert_eq!(Bounded::new(-2).unwrap().checked_sub(127), None); 205 | assert_eq!(Bounded::new(2).unwrap().checked_mul(3).unwrap().get(), 6); 206 | assert_eq!(Bounded::new(15).unwrap().checked_mul(-1), None); 207 | assert_eq!(Bounded::new(3).unwrap().checked_pow(2).unwrap().get(), 9); 208 | assert_eq!(Bounded::new(3).unwrap().checked_pow(3), None); 209 | assert_eq!(Bounded::new(2).unwrap().checked_shl(3).unwrap().get(), 16); 210 | assert_eq!(Bounded::new(3).unwrap().checked_shl(3), None); 211 | assert_eq!(Bounded::new(9).unwrap().checked_shr(2).unwrap().get(), 2); 212 | assert_eq!(>::new(8).unwrap().checked_shr(2), None); 213 | assert_eq!(Bounded::new(11).unwrap().checked_div(3).unwrap().get(), 3); 214 | assert_eq!(Bounded::new(11).unwrap().checked_rem(3).unwrap().get(), 2); 215 | assert_eq!(>::new(11).unwrap().checked_div(3), None); 216 | assert_eq!(>::new(11).unwrap().checked_rem(3), None); 217 | assert_eq!(Bounded::new(11).unwrap().checked_div_euclid(3).unwrap(), 3); 218 | assert_eq!(Bounded::new(11).unwrap().checked_rem_euclid(3).unwrap(), 2); 219 | assert_eq!( 220 | >::new(11).unwrap().checked_div_euclid(3), 221 | None 222 | ); 223 | assert_eq!( 224 | >::new(11).unwrap().checked_rem_euclid(3), 225 | None 226 | ); 227 | assert_eq!(Bounded::new(-3).unwrap().checked_neg().unwrap().get(), 3); 228 | assert_eq!(Bounded::new(6).unwrap().checked_neg(), None); 229 | assert_eq!(Bounded::new(-3).unwrap().checked_abs().unwrap().get(), 3); 230 | assert_eq!(Bounded::new(6).unwrap().checked_abs().unwrap().get(), 6); 231 | assert_eq!(>::new(-6).unwrap().checked_abs(), None); 232 | } 233 | 234 | #[test] 235 | fn wrapping() { 236 | type Bounded = BoundedI8<-5, 20>; 237 | assert_eq!(Bounded::new(0).unwrap().wrapping_add(0).get(), 0); 238 | assert_eq!(Bounded::new(-5).unwrap().wrapping_add(-128), -3); 239 | assert_eq!(Bounded::new(-5).unwrap().wrapping_sub(127), -2); 240 | assert_eq!(Bounded::new(20).unwrap().wrapping_add(127), 17); 241 | assert_eq!(Bounded::new(15).unwrap().wrapping_mul(17), -5); 242 | assert_eq!(Bounded::new(-5).unwrap().wrapping_div(2), -2); 243 | assert_eq!(Bounded::new(-5).unwrap().wrapping_rem(2), -1); 244 | assert_eq!(Bounded::new(-5).unwrap().wrapping_div_euclid(2), -3); 245 | assert_eq!(Bounded::new(-5).unwrap().wrapping_rem_euclid(2), 1); 246 | assert_eq!(Bounded::new(-5).unwrap().wrapping_neg(), 5); 247 | assert_eq!(Bounded::new(6).unwrap().wrapping_neg(), 20); 248 | assert_eq!(Bounded::new(-5).unwrap().wrapping_abs(), 5); 249 | assert_eq!(Bounded::new(6).unwrap().wrapping_abs(), 6); 250 | assert_eq!(>::new(-6).unwrap().wrapping_abs(), -20); 251 | assert_eq!(Bounded::new(5).unwrap().wrapping_pow(607), -5); 252 | } 253 | 254 | #[test] 255 | fn wrapping_div() { 256 | type Bounded = BoundedI32<{ i32::MIN }, -1>; 257 | assert_eq!(Bounded::new(i32::MIN).unwrap().wrapping_div(-1), i32::MIN); 258 | } 259 | 260 | #[test] 261 | fn iter() { 262 | type Bounded = BoundedI8<-8, 8>; 263 | #[expect(clippy::trivially_copy_pass_by_ref)] 264 | fn b(&n: &i8) -> Bounded { 265 | Bounded::new(n).unwrap() 266 | } 267 | assert_eq!([3, 2, 1].iter().map(b).sum::().get(), 6); 268 | assert_eq!([-8, 3, 7, 5, -2].iter().map(b).sum::().get(), 5); 269 | assert_eq!([7, 6, 4].iter().map(b).sum::(), 17); 270 | assert_eq!([-8, 3, 7, 5, -2].iter().map(b).sum::(), 5); 271 | 272 | assert_eq!([1, 3, 2, 1].iter().map(b).product::().get(), 6); 273 | assert_eq!([1, 3, 2, 1, 0].iter().map(b).product::().get(), 0); 274 | assert_eq!([-2, -3, -1].iter().map(b).product::().get(), -6); 275 | assert_eq!([3, 3].iter().map(b).product::(), 9); 276 | } 277 | 278 | #[test] 279 | fn parse() { 280 | use crate::ParseErrorKind::*; 281 | 282 | type Bounded = BoundedI8<3, 11>; 283 | 284 | assert_eq!("3".parse::().unwrap().get(), 3); 285 | assert_eq!("10".parse::().unwrap().get(), 10); 286 | assert_eq!("+11".parse::().unwrap().get(), 11); 287 | assert_eq!(Bounded::from_str_radix("1010", 2).unwrap().get(), 10); 288 | assert_eq!(Bounded::from_str_radix("B", 0xC).unwrap().get(), 11); 289 | assert_eq!(Bounded::from_str_radix("11", 7).unwrap().get(), 8); 290 | assert_eq!(Bounded::from_str_radix("7", 36).unwrap().get(), 7); 291 | 292 | assert_eq!("".parse::().unwrap_err().kind(), NoDigits); 293 | assert_eq!("+".parse::().unwrap_err().kind(), NoDigits); 294 | assert_eq!("-".parse::().unwrap_err().kind(), NoDigits); 295 | assert_eq!("2".parse::().unwrap_err().kind(), BelowMin); 296 | assert_eq!("12".parse::().unwrap_err().kind(), AboveMax); 297 | assert_eq!("-5".parse::().unwrap_err().kind(), BelowMin); 298 | assert_eq!("128".parse::().unwrap_err().kind(), AboveMax); 299 | assert_eq!("-129".parse::().unwrap_err().kind(), BelowMin); 300 | 301 | assert_eq!("++0".parse::().unwrap_err().kind(), InvalidDigit); 302 | assert_eq!("--0".parse::().unwrap_err().kind(), InvalidDigit); 303 | assert_eq!("O".parse::().unwrap_err().kind(), InvalidDigit); 304 | assert_eq!("C".parse::().unwrap_err().kind(), InvalidDigit); 305 | assert_eq!( 306 | Bounded::from_str_radix("3", 2).unwrap_err().kind(), 307 | InvalidDigit 308 | ); 309 | } 310 | 311 | #[test] 312 | #[cfg(feature = "alloc")] 313 | fn fmt() { 314 | use alloc::format; 315 | use alloc::string::ToString; 316 | type Bounded = BoundedU8<3, 210>; 317 | assert_eq!(Bounded::new(3).unwrap().to_string(), "3"); 318 | assert_eq!(format!("{:b}", Bounded::new(5).unwrap()), "101"); 319 | assert_eq!(format!("{:x}", Bounded::new(200).unwrap()), "c8"); 320 | assert_eq!(format!("{:X}", Bounded::new(200).unwrap()), "C8"); 321 | assert_eq!(format!("{:o}", Bounded::new(200).unwrap()), "310"); 322 | } 323 | 324 | #[test] 325 | fn default() { 326 | assert_eq!(>::default().get(), 0); 327 | } 328 | 329 | #[test] 330 | fn conversions() { 331 | assert_eq!(i8::from(>::new(3).unwrap()), 3); 332 | assert_eq!(i16::from(>::new(3).unwrap()), 3); 333 | assert_eq!(i32::from(>::new(3).unwrap()), 3); 334 | assert_eq!(u32::from(>::new(3).unwrap()), 3); 335 | assert_eq!(i64::from(>::new(3).unwrap()), 3); 336 | assert_eq!(usize::from(>::new(3).unwrap()), 3); 337 | assert_eq!( 338 | u8::try_from(>::new(3).unwrap()).unwrap(), 339 | 3 340 | ); 341 | u8::try_from(>::new(-1).unwrap()).unwrap_err(); 342 | assert_eq!(>::try_from(3_u16).unwrap(), 3); 343 | >::try_from(8_u16).unwrap_err(); 344 | } 345 | 346 | #[test] 347 | #[cfg(feature = "num-traits02")] 348 | #[expect(clippy::too_many_lines)] 349 | fn num() { 350 | use num_traits02::{ 351 | AsPrimitive, Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, 352 | CheckedShl, CheckedShr, CheckedSub, FromPrimitive, NumCast, ToPrimitive, 353 | }; 354 | 355 | type B = BoundedI8<2, 8>; 356 | type BNeg = BoundedI8<-4, 8>; 357 | 358 | fn b(n: i8) -> B { 359 | B::new(n).unwrap() 360 | } 361 | 362 | fn bneg(n: i8) -> BNeg { 363 | BNeg::new(n).unwrap() 364 | } 365 | 366 | assert_eq!(B::min_value(), 2); 367 | assert_eq!(B::max_value(), 8); 368 | 369 | assert_eq!(BNeg::min_value(), -4); 370 | assert_eq!(BNeg::max_value(), 8); 371 | 372 | assert_eq!(>::as_(b(4)), 4u8); 373 | assert_eq!(>::as_(b(4)), 4u16); 374 | assert_eq!(>::as_(b(4)), 4u32); 375 | assert_eq!(>::as_(b(4)), 4u64); 376 | assert_eq!(>::as_(b(4)), 4u128); 377 | assert_eq!(>::as_(b(4)), 4usize); 378 | assert_eq!(>::as_(b(4)), 4i8); 379 | assert_eq!(>::as_(b(4)), 4i16); 380 | assert_eq!(>::as_(b(4)), 4i32); 381 | assert_eq!(>::as_(b(4)), 4i64); 382 | assert_eq!(>::as_(b(4)), 4i128); 383 | assert_eq!(>::as_(b(4)), 4isize); 384 | #[expect(clippy::float_cmp)] 385 | let () = assert_eq!(>::as_(b(4)), 4f32); 386 | #[expect(clippy::float_cmp)] 387 | let () = assert_eq!(>::as_(b(4)), 4f64); 388 | 389 | assert_eq!(B::from_u8(4u8), Some(b(4))); 390 | assert_eq!(B::from_u16(4u16), Some(b(4))); 391 | assert_eq!(B::from_u32(4u32), Some(b(4))); 392 | assert_eq!(B::from_u64(4u64), Some(b(4))); 393 | assert_eq!(B::from_u128(4u128), Some(b(4))); 394 | assert_eq!(B::from_usize(4usize), Some(b(4))); 395 | assert_eq!(B::from_i8(4i8), Some(b(4))); 396 | assert_eq!(B::from_i16(4i16), Some(b(4))); 397 | assert_eq!(B::from_i32(4i32), Some(b(4))); 398 | assert_eq!(B::from_i64(4i64), Some(b(4))); 399 | assert_eq!(B::from_i128(4i128), Some(b(4))); 400 | assert_eq!(B::from_isize(4isize), Some(b(4))); 401 | assert_eq!(B::from_f32(4f32), Some(b(4))); 402 | assert_eq!(B::from_f64(4f64), Some(b(4))); 403 | 404 | assert_eq!(B::from_u8(16u8), None); 405 | assert_eq!(B::from_u16(16u16), None); 406 | assert_eq!(B::from_u32(16u32), None); 407 | assert_eq!(B::from_u64(16u64), None); 408 | assert_eq!(B::from_u128(16u128), None); 409 | assert_eq!(B::from_usize(16usize), None); 410 | assert_eq!(B::from_i8(16i8), None); 411 | assert_eq!(B::from_i16(16i16), None); 412 | assert_eq!(B::from_i32(16i32), None); 413 | assert_eq!(B::from_i64(16i64), None); 414 | assert_eq!(B::from_i128(16i128), None); 415 | assert_eq!(B::from_isize(16isize), None); 416 | assert_eq!(B::from_f32(16f32), None); 417 | assert_eq!(B::from_f64(16f64), None); 418 | 419 | assert_eq!(::from(4u8), Some(b(4))); 420 | assert_eq!(::from(4u16), Some(b(4))); 421 | assert_eq!(::from(4u32), Some(b(4))); 422 | assert_eq!(::from(4u64), Some(b(4))); 423 | assert_eq!(::from(4u128), Some(b(4))); 424 | assert_eq!(::from(4usize), Some(b(4))); 425 | assert_eq!(::from(4i8), Some(b(4))); 426 | assert_eq!(::from(4i16), Some(b(4))); 427 | assert_eq!(::from(4i32), Some(b(4))); 428 | assert_eq!(::from(4i64), Some(b(4))); 429 | assert_eq!(::from(4i128), Some(b(4))); 430 | assert_eq!(::from(4isize), Some(b(4))); 431 | assert_eq!(::from(4f32), Some(b(4))); 432 | assert_eq!(::from(4f64), Some(b(4))); 433 | 434 | assert_eq!(::from(16u8), None); 435 | assert_eq!(::from(16u16), None); 436 | assert_eq!(::from(16u32), None); 437 | assert_eq!(::from(16u64), None); 438 | assert_eq!(::from(16u128), None); 439 | assert_eq!(::from(16usize), None); 440 | assert_eq!(::from(16i8), None); 441 | assert_eq!(::from(16i16), None); 442 | assert_eq!(::from(16i32), None); 443 | assert_eq!(::from(16i64), None); 444 | assert_eq!(::from(16i128), None); 445 | assert_eq!(::from(16isize), None); 446 | assert_eq!(::from(16f32), None); 447 | assert_eq!(::from(16f64), None); 448 | 449 | assert_eq!(b(4).to_u8(), Some(4u8)); 450 | assert_eq!(b(4).to_u16(), Some(4u16)); 451 | assert_eq!(b(4).to_u32(), Some(4u32)); 452 | assert_eq!(b(4).to_u64(), Some(4u64)); 453 | assert_eq!(b(4).to_u128(), Some(4u128)); 454 | assert_eq!(b(4).to_usize(), Some(4usize)); 455 | assert_eq!(b(4).to_i8(), Some(4i8)); 456 | assert_eq!(b(4).to_i16(), Some(4i16)); 457 | assert_eq!(b(4).to_i32(), Some(4i32)); 458 | assert_eq!(b(4).to_i64(), Some(4i64)); 459 | assert_eq!(b(4).to_i128(), Some(4i128)); 460 | assert_eq!(b(4).to_isize(), Some(4isize)); 461 | assert_eq!(b(4).to_f32(), Some(4f32)); 462 | assert_eq!(b(4).to_f64(), Some(4f64)); 463 | 464 | assert_eq!(::checked_add(&b(4), &b(4)), Some(b(8))); 465 | assert_eq!(::checked_add(&b(4), &b(8)), None); 466 | 467 | assert_eq!(::checked_div(&b(8), &b(2)), Some(b(4))); 468 | assert_eq!(::checked_div(&b(4), &b(4)), None); 469 | 470 | assert_eq!(::checked_mul(&b(2), &b(2)), Some(b(4))); 471 | assert_eq!(::checked_mul(&b(2), &b(8)), None); 472 | 473 | assert_eq!(::checked_neg(&bneg(2)), Some(bneg(-2))); 474 | 475 | assert_eq!(::checked_neg(&bneg(8)), None); 476 | 477 | assert_eq!(::checked_rem(&b(8), &b(6)), Some(b(2))); 478 | assert_eq!(::checked_rem(&b(8), &b(7)), None); 479 | 480 | assert_eq!(::checked_sub(&b(4), &b(2)), Some(b(2))); 481 | assert_eq!(::checked_sub(&b(4), &b(4)), None); 482 | 483 | assert_eq!(::checked_shl(&b(4), 1u32), Some(b(8))); 484 | assert_eq!(::checked_shl(&b(4), 2u32), None); 485 | 486 | assert_eq!(::checked_shr(&b(4), 1u32), Some(b(2))); 487 | assert_eq!(::checked_shr(&b(4), 2u32), None); 488 | } 489 | } 490 | -------------------------------------------------------------------------------- /src/unsafe_api.rs: -------------------------------------------------------------------------------- 1 | /// Unsafely provide the bounded integer API for a custom type. 2 | /// 3 | /// This is used by the higher-level const generic types and by 4 | /// [the `bounded_integer!` macro](crate::bounded_integer). It is preferable to use those APIs when 5 | /// possible. 6 | /// 7 | /// Takes in a `ty` and a `repr`. `ty` must be a type whose layout is identical to `repr`. 8 | /// 9 | /// `min` and `max` are const expressions giving the bounds of the type (inclusive). 10 | /// If `zero` is provided, various traits like [`Default`] will be implemented; 11 | /// if `one` is provided, `num_traits::One` will be implemented. 12 | /// 13 | /// # Safety 14 | /// 15 | /// The given type must be `repr(transparent)` over its claimed `repr`. 16 | /// 17 | /// # Examples 18 | /// 19 | /// ``` 20 | #[cfg_attr(feature = "step_trait", doc = "# #![feature(step_trait)]")] 21 | /// #[repr(transparent)] 22 | /// struct MyInteger(u8); 23 | /// 24 | /// bounded_integer::unsafe_api! { 25 | /// for MyInteger, 26 | /// unsafe repr: u8, 27 | /// min: 0, 28 | /// max: 37, 29 | /// zero, 30 | /// one, 31 | /// } 32 | /// 33 | /// assert_eq!(MyInteger::new(0).unwrap(), 0); 34 | /// assert_eq!(MyInteger::new(38), None); 35 | /// ``` 36 | /// 37 | /// # Reprs 38 | /// 39 | /// `repr` may either be a primitive integer type, or a descriptor: 40 | /// 41 | /// ```text 42 | /// unsafe repr: { 43 | /// type: u32, // a primitive integer type, or something that resolves to one (e.g. c_int) 44 | /// signed: false, 45 | /// supersets: [u32, u64, u128, i64, i128], // Types implementing `From` 46 | /// non_supersets: [u8, u16, usize, i8, i16, i32, isize], // All the other integer types 47 | /// has_wide, // Include for all except `u128` and `i128`. 48 | /// } 49 | /// ``` 50 | #[macro_export] 51 | macro_rules! unsafe_api { 52 | ( 53 | $(#![$($attr:tt)*])* 54 | $([$($generics:tt)*])? for $ty:ty $(where { $($where:tt)* })?, 55 | unsafe repr: $repr:tt, 56 | min: $min:expr, 57 | max: $max:expr, 58 | $(zero $([$($_:tt)* $zero:tt])?,)? 59 | $(one $([$($__:tt)* $one:tt])?,)? 60 | ) => { 61 | $crate::__unsafe_api_internal! { 62 | @repr $repr, 63 | $(#![$($attr)*])* 64 | [$($($generics)*)?] for $ty where { $($($where)*)? }, 65 | ([$($($generics)*)?] where $($($where)*)?), 66 | min: $min, 67 | max: $max, 68 | $(zero, $($zero)?)? 69 | $(one, $($one)?)? 70 | } 71 | }; 72 | } 73 | 74 | #[macro_export] 75 | #[doc(hidden)] 76 | macro_rules! __unsafe_api_internal { 77 | ( 78 | @repr { 79 | type: $inner:ty, 80 | signed: $(true $([$($_:tt)* $signed:tt])?)? $(false)?, 81 | supersets: [$($super:ty),* $(,)?], 82 | non_supersets: [$($non_super:ty),* $(,)?], 83 | $(has_wide $([$($__:tt)* $has_wide:tt])?,)? 84 | }, 85 | $(#![$($attr:tt)*])* 86 | [$($generics:tt)*] for $ty:ty where { $($where:tt)* }, 87 | $generics_single_token:tt, 88 | min: $min:expr, 89 | max: $max:expr, 90 | $(zero $([$zero:tt])?,)? 91 | $(one $([$one:tt])?,)? 92 | ) => { $(#[$($attr)*])* const _: () = { 93 | // The presence of these imports is somewhat unhygienic: it means users cannot name their 94 | // type any of these things. This can always be changed if the need arises. 95 | use ::core::assert; 96 | use ::core::hash::Hash; 97 | use ::core::hash::Hasher; 98 | use ::core::fmt; 99 | use ::core::borrow::Borrow; 100 | use ::core::cmp; 101 | use ::core::debug_assert; 102 | use ::core::iter; 103 | use ::core::num::NonZero; 104 | use ::core::prelude::rust_2024::*; 105 | use ::core::primitive::*; 106 | use ::core::str::FromStr; 107 | 108 | use $crate::ParseError; 109 | #[cfg(any($($(if $has_wide)? true)?))] 110 | use $crate::__private::Wide; 111 | #[cfg(any($($(if $has_wide)? true)?))] 112 | use $crate::__private::Signed; 113 | use $crate::__private::Unsigned; 114 | use $crate::__private::Dispatch; 115 | 116 | #[allow(dead_code)] 117 | #[allow(clippy::double_must_use)] 118 | impl<$($generics)*> $ty where $($where)* { 119 | /// The smallest value this bounded integer can contain. 120 | pub const MIN_VALUE: $inner = $min; 121 | /// The largest value that this bounded integer can contain. 122 | pub const MAX_VALUE: $inner = $max; 123 | 124 | /// The smallest value of the bounded integer. 125 | pub const MIN: Self = Self::new(Self::MIN_VALUE) 126 | .expect("range minimum should be less than maximum"); 127 | /// The largest value of the bounded integer. 128 | pub const MAX: Self = Self::new(Self::MAX_VALUE) 129 | .expect("range minimum should be less than maximum"); 130 | 131 | /// Creates a bounded integer if the given value is within the range 132 | /// [[`MIN`](Self::MIN), [`MAX`](Self::MAX)]. 133 | #[must_use] 134 | #[inline] 135 | pub const fn new(n: $inner) -> Option { 136 | if Self::in_range(n) { 137 | Some(unsafe { Self::new_unchecked(n) }) 138 | } else { 139 | None 140 | } 141 | } 142 | 143 | /// Creates a bounded integer whose value is known at compile time. 144 | /// 145 | /// Causes a compile-time error if `N` is not in the valid range. 146 | #[must_use] 147 | #[inline] 148 | pub const fn const_new() -> Self { 149 | const { Self::new(N).expect("value passed to `const_new` is out of range") } 150 | } 151 | 152 | /// Creates a reference to a bounded integer from a reference to a primitive if the 153 | /// given value is within the range [[`MIN`](Self::MIN), [`MAX`](Self::MAX)]. 154 | #[must_use] 155 | #[inline] 156 | pub const fn new_ref(n: &$inner) -> Option<&Self> { 157 | if Self::in_range(*n) { 158 | // SAFETY: We just asserted that the value is in range. 159 | Some(unsafe { Self::new_ref_unchecked(n) }) 160 | } else { 161 | None 162 | } 163 | } 164 | 165 | /// Creates a mutable reference to a bounded integer from a mutable reference to a 166 | /// primitive if the given value is within the range 167 | /// [[`MIN`](Self::MIN), [`MAX`](Self::MAX)]. 168 | #[must_use] 169 | #[inline] 170 | pub const fn new_mut(n: &mut $inner) -> Option<&mut Self> { 171 | if Self::in_range(*n) { 172 | // SAFETY: We just asserted that the value is in range. 173 | Some(unsafe { Self::new_mut_unchecked(n) }) 174 | } else { 175 | None 176 | } 177 | } 178 | 179 | /// Creates a bounded integer without checking the value. 180 | /// 181 | /// # Safety 182 | /// 183 | /// The value must not be outside the valid range of values; it must not be less than 184 | /// [`MIN_VALUE`](Self::MIN_VALUE) or greater than [`MAX_VALUE`](Self::MAX_VALUE). 185 | #[must_use] 186 | pub const unsafe fn new_unchecked(n: $inner) -> Self { 187 | debug_assert!(Self::in_range(n)); 188 | unsafe { ::core::mem::transmute(n) } 189 | } 190 | 191 | /// Creates a shared reference to a bounded integer from a shared reference to a 192 | /// primitive. 193 | /// 194 | /// # Safety 195 | /// 196 | /// The value must not be outside the valid range of values; it must not be less than 197 | /// [`MIN_VALUE`](Self::MIN_VALUE) or greater than [`MAX_VALUE`](Self::MAX_VALUE). 198 | #[must_use] 199 | pub const unsafe fn new_ref_unchecked(n: &$inner) -> &Self { 200 | debug_assert!(Self::in_range(*n)); 201 | unsafe { &*<*const _>::cast(n) } 202 | } 203 | 204 | /// Creates a mutable reference to a bounded integer from a mutable reference to a 205 | /// primitive. 206 | /// 207 | /// # Safety 208 | /// 209 | /// The value must not be outside the valid range of values; it must not be less than 210 | /// [`MIN_VALUE`](Self::MIN_VALUE) or greater than [`MAX_VALUE`](Self::MAX_VALUE). 211 | #[must_use] 212 | pub const unsafe fn new_mut_unchecked(n: &mut $inner) -> &mut Self { 213 | debug_assert!(Self::in_range(*n)); 214 | unsafe { &mut *<*mut _>::cast(n) } 215 | } 216 | 217 | /// Checks whether the given value is in the range of the bounded integer. 218 | #[must_use] 219 | #[inline] 220 | pub const fn in_range(n: $inner) -> bool { 221 | n >= Self::MIN_VALUE && n <= Self::MAX_VALUE 222 | } 223 | 224 | /// Creates a bounded integer by setting the value to [`MIN`](Self::MIN) or 225 | /// [`MAX`](Self::MAX) if it is too low or too high respectively. 226 | #[must_use] 227 | #[inline] 228 | pub const fn new_saturating(n: $inner) -> Self { 229 | if n < Self::MIN_VALUE { 230 | Self::MIN 231 | } else if n > Self::MAX_VALUE { 232 | Self::MAX 233 | } else { 234 | unsafe { Self::new_unchecked(n) } 235 | } 236 | } 237 | 238 | /// Creates a bounded integer by wrapping using modular arithmetic. 239 | /// 240 | /// For `n` in range, this is an identity function, and it wraps for `n` out of range. 241 | /// 242 | /// The type parameter `Z` must be any integer type that is a superset of this one. 243 | #[must_use] 244 | #[inline] 245 | pub const fn new_wrapping<__Z: LargerInt>(n: __Z) -> Self { 246 | const { assert!(Self::MIN_VALUE < Self::MAX_VALUE) }; 247 | let range_sub_one: Unsigned<$inner> = Self::MAX_VALUE.abs_diff(Self::MIN_VALUE); 248 | let offsets = match range_sub_one.checked_add(1) { 249 | Some(range) => { 250 | let range = NonZero::new(range).unwrap(); 251 | 252 | // The smallest nonnegative value satisfying `left ≡ MIN [MOD range]`. 253 | let left = >::rem_euclid_unsigned(Self::MIN_VALUE, range); 254 | 255 | // The smallest nonnegative value satisfying `−right ≡ MIN [MOD range]`. 256 | let right = match left.checked_sub(1) { 257 | None => 0, 258 | Some(left_sub_one) => range_sub_one - left_sub_one, 259 | }; 260 | 261 | Some((range, left, right)) 262 | }, 263 | None => None, 264 | }; 265 | 266 | const { 267 | let mut n: u32 = 0; 268 | $(n += str_eq(__Z::KIND, stringify!($super)) as u32;)* 269 | assert!(n == 1); 270 | } 271 | 272 | $(if str_eq(__Z::KIND, stringify!($super)) { 273 | let n = unsafe { ::core::mem::transmute_copy::<__Z, $super>(&n) }; 274 | 275 | let Some((range, left, right)) = offsets else { 276 | // In the case where the range spans this entire type, truncating is 277 | // equivalent to taking modulo. 278 | #[allow(clippy::cast_possible_truncation)] 279 | #[allow(clippy::cast_sign_loss)] 280 | return unsafe { Self::new_unchecked(n as _) }; 281 | }; 282 | 283 | // At least one of `n − left` and `n + right` fits in a `__Z`. We calculate 284 | // this value. 285 | let shifted = match >::checked_add_unsigned(n, right as _) { 286 | Some(n) => n, 287 | None => >::checked_sub_unsigned(n, left as _).unwrap(), 288 | }; 289 | 290 | let range_t = NonZero::new(range.get() as _).unwrap(); 291 | 292 | // Calculate `shifted mod range`. Since `range` fits in an `Unsigned`, we 293 | // know the result will too. 294 | #[allow(clippy::cast_possible_truncation)] 295 | let rem = >::rem_euclid_unsigned(shifted, range_t) as _; 296 | 297 | let inner = >::checked_add_unsigned(Self::MIN_VALUE, rem).unwrap(); 298 | 299 | return unsafe { Self::new_unchecked(inner) }; 300 | })* 301 | 302 | const fn str_eq(a: &str, b: &str) -> bool { 303 | if a.len() != b.len() { 304 | return false; 305 | } 306 | let mut i = 0; 307 | while i < a.len() { 308 | if a.as_bytes()[i] != b.as_bytes()[i] { 309 | return false; 310 | } 311 | i += 1; 312 | } 313 | true 314 | } 315 | 316 | ::core::unreachable!() 317 | } 318 | 319 | /// Converts a string slice in a given base to the bounded integer. 320 | /// 321 | /// # Panics 322 | /// 323 | /// Panics if `radix` is below 2 or above 36. 324 | pub const fn from_str_radix(src: &str, radix: u32) -> Result { 325 | let value = match >::from_ascii_radix(src.as_bytes(), radix) { 326 | Ok(value) => value, 327 | Err(e) => return Err(e), 328 | }; 329 | if value < Self::MIN_VALUE { 330 | Err($crate::__private::error_below_min()) 331 | } else if value > Self::MAX_VALUE { 332 | Err($crate::__private::error_above_max()) 333 | } else { 334 | Ok(unsafe { Self::new_unchecked(value) }) 335 | } 336 | } 337 | 338 | #[inline] 339 | const fn assert_range(&self) { 340 | // Safety: As this type cannot be constructed unless the inner value is within 341 | // the given range, we can use `assert_unchecked` to ensure that LLVM always 342 | // maintains the range information no matter what. 343 | unsafe { 344 | ::core::hint::assert_unchecked( 345 | Self::in_range(::core::mem::transmute::(*self)), 346 | ); 347 | } 348 | } 349 | 350 | /// Returns the value of the bounded integer as a primitive type. 351 | #[must_use] 352 | #[inline] 353 | pub const fn get(self) -> $inner { 354 | self.assert_range(); 355 | unsafe { ::core::mem::transmute(self) } 356 | } 357 | 358 | /// Returns a shared reference to the value of the bounded integer. 359 | #[must_use] 360 | #[inline] 361 | pub const fn get_ref(&self) -> &$inner { 362 | self.assert_range(); 363 | unsafe { &*<*const _>::cast(self) } 364 | } 365 | 366 | /// Returns a mutable reference to the value of the bounded integer. 367 | /// 368 | /// # Safety 369 | /// 370 | /// This value must never be set to a value beyond the range of the bounded integer. 371 | #[must_use] 372 | #[inline] 373 | pub const unsafe fn get_mut(&mut self) -> &mut $inner { 374 | self.assert_range(); 375 | unsafe { &mut *<*mut _>::cast(self) } 376 | } 377 | 378 | $($(if $signed)? 379 | /// Computes the absolute value of `self`, panicking if it is out of range. 380 | #[must_use] 381 | #[inline] 382 | pub const fn abs(self) -> Self { 383 | Self::new(self.get().abs()).expect("Absolute value out of range") 384 | } 385 | )* 386 | 387 | /// Raises `self` to the power of `exp`, using exponentiation by squaring. Panics if it 388 | /// is out of range. 389 | #[must_use] 390 | #[inline] 391 | pub const fn pow(self, exp: u32) -> Self { 392 | Self::new(self.get().pow(exp)).expect("Value raised to power out of range") 393 | } 394 | 395 | /// Calculates the quotient of Euclidean division of `self` by `rhs`. Panics if `rhs` 396 | /// is 0 or the result is out of range. 397 | #[must_use] 398 | #[inline] 399 | pub const fn div_euclid(self, rhs: $inner) -> Self { 400 | Self::new(self.get().div_euclid(rhs)).expect("Attempted to divide out of range") 401 | } 402 | 403 | /// Calculates the least nonnegative remainder of `self (mod rhs)`. Panics if `rhs` is 0 404 | /// or the result is out of range. 405 | #[must_use] 406 | #[inline] 407 | pub const fn rem_euclid(self, rhs: $inner) -> Self { 408 | Self::new(self.get().rem_euclid(rhs)) 409 | .expect("Attempted to divide with remainder out of range") 410 | } 411 | 412 | /// Checked integer addition. 413 | /// 414 | /// Returns `None` if the result would be out of range. 415 | #[must_use] 416 | #[inline] 417 | pub const fn checked_add(self, rhs: $inner) -> Option { 418 | match self.get().checked_add(rhs) { 419 | Some(val) => Self::new(val), 420 | None => None, 421 | } 422 | } 423 | 424 | /// Saturating integer addition. 425 | #[must_use] 426 | #[inline] 427 | pub const fn saturating_add(self, rhs: $inner) -> Self { 428 | Self::new_saturating(self.get().saturating_add(rhs)) 429 | } 430 | 431 | /// Wrapping (modular) integer addition. 432 | #[must_use] 433 | #[inline] 434 | #[cfg(not(all($($(if $has_wide)? false)?)))] 435 | pub const fn wrapping_add(self, rhs: $inner) -> Self { 436 | Self::new_wrapping((self.get() as Wide<$inner>) + (rhs as Wide<$inner>)) 437 | } 438 | 439 | /// Checked integer subtraction. 440 | /// 441 | /// Returns `None` if the result would be out of range. 442 | #[must_use] 443 | #[inline] 444 | pub const fn checked_sub(self, rhs: $inner) -> Option { 445 | match self.get().checked_sub(rhs) { 446 | Some(val) => Self::new(val), 447 | None => None, 448 | } 449 | } 450 | 451 | /// Saturating integer subtraction. 452 | #[must_use] 453 | #[inline] 454 | pub const fn saturating_sub(self, rhs: $inner) -> Self { 455 | Self::new_saturating(self.get().saturating_sub(rhs)) 456 | } 457 | 458 | /// Wrapping (modular) integer subtraction. 459 | #[must_use] 460 | #[inline] 461 | #[cfg(not(all($($(if $has_wide)? false)?)))] 462 | pub const fn wrapping_sub(self, rhs: $inner) -> Self { 463 | Self::new_wrapping( 464 | (self.get() as Signed>) - (rhs as Signed>) 465 | ) 466 | } 467 | 468 | /// Checked integer multiplication. 469 | /// 470 | /// Returns `None` if the result would be out of range. 471 | #[must_use] 472 | #[inline] 473 | pub const fn checked_mul(self, rhs: $inner) -> Option { 474 | match self.get().checked_mul(rhs) { 475 | Some(val) => Self::new(val), 476 | None => None, 477 | } 478 | } 479 | 480 | /// Saturating integer multiplication. 481 | #[must_use] 482 | #[inline] 483 | pub const fn saturating_mul(self, rhs: $inner) -> Self { 484 | Self::new_saturating(self.get().saturating_mul(rhs)) 485 | } 486 | 487 | /// Wrapping (modular) integer multiplication. 488 | #[must_use] 489 | #[inline] 490 | #[cfg(not(all($($(if $has_wide)? false)?)))] 491 | pub const fn wrapping_mul(self, rhs: $inner) -> Self { 492 | Self::new_wrapping((self.get() as Wide<$inner>) * (rhs as Wide<$inner>)) 493 | } 494 | 495 | /// Checked integer division. 496 | /// 497 | /// Returns `None` if the result would be out of range, or if `rhs` is zero. 498 | #[must_use] 499 | #[inline] 500 | pub const fn checked_div(self, rhs: $inner) -> Option { 501 | match self.get().checked_div(rhs) { 502 | Some(val) => Self::new(val), 503 | None => None, 504 | } 505 | } 506 | 507 | /// Wrapping (modular) integer division. 508 | #[must_use] 509 | #[inline] 510 | #[cfg(not(all($($(if $has_wide)? false)?)))] 511 | pub const fn wrapping_div(self, rhs: $inner) -> Self { 512 | // We need to widen for the case of `$inner::MIN / −1`. 513 | Self::new_wrapping((self.get() as Wide<$inner>) / (rhs as Wide<$inner>)) 514 | } 515 | 516 | /// Checked Euclidean division. 517 | /// 518 | /// Returns `None` if the result would be out of range, or if `rhs` is zero. 519 | #[must_use] 520 | #[inline] 521 | pub const fn checked_div_euclid(self, rhs: $inner) -> Option { 522 | match self.get().checked_div_euclid(rhs) { 523 | Some(val) => Self::new(val), 524 | None => None, 525 | } 526 | } 527 | 528 | /// Wrapping (modular) Euclidean division. 529 | #[must_use] 530 | #[inline] 531 | #[cfg(not(all($($(if $has_wide)? false)?)))] 532 | pub const fn wrapping_div_euclid(self, rhs: $inner) -> Self { 533 | // We need to widen for the case of `$inner::MIN / −1`. 534 | Self::new_wrapping((self.get() as Wide<$inner>).div_euclid(rhs as Wide<$inner>)) 535 | } 536 | 537 | /// Checked integer remainder. 538 | /// 539 | /// Returns `None` if the result would be out of range, or if `rhs` is zero. 540 | #[must_use] 541 | #[inline] 542 | pub const fn checked_rem(self, rhs: $inner) -> Option { 543 | match self.get().checked_rem(rhs) { 544 | Some(val) => Self::new(val), 545 | None => None, 546 | } 547 | } 548 | 549 | /// Wrapping (modular) integer remainder. 550 | #[must_use] 551 | #[inline] 552 | #[cfg(not(all($($(if $has_wide)? false)?)))] 553 | pub const fn wrapping_rem(self, rhs: $inner) -> Self { 554 | // We need to widen for the case of `$inner::MIN % −1`. 555 | Self::new_wrapping((self.get() as Wide<$inner>) % (rhs as Wide<$inner>)) 556 | } 557 | 558 | /// Checked Euclidean remainder. 559 | /// 560 | /// Returns `None` if the result would be out of range, or if `rhs` is zero. 561 | #[must_use] 562 | #[inline] 563 | pub const fn checked_rem_euclid(self, rhs: $inner) -> Option { 564 | match self.get().checked_rem_euclid(rhs) { 565 | Some(val) => Self::new(val), 566 | None => None, 567 | } 568 | } 569 | 570 | /// Wrapping (modular) Euclidean remainder. 571 | #[must_use] 572 | #[inline] 573 | #[cfg(not(all($($(if $has_wide)? false)?)))] 574 | pub const fn wrapping_rem_euclid(self, rhs: $inner) -> Self { 575 | // We need to widen for the case of `$inner::MIN % −1`. 576 | Self::new_wrapping((self.get() as Wide<$inner>).rem_euclid(rhs as Wide<$inner>)) 577 | } 578 | 579 | /// Checked negation. 580 | /// 581 | /// Returns `None` if the result would be out of range. 582 | #[must_use] 583 | #[inline] 584 | pub const fn checked_neg(self) -> Option { 585 | match self.get().checked_neg() { 586 | Some(val) => Self::new(val), 587 | None => None, 588 | } 589 | } 590 | 591 | /// Saturating negation. 592 | #[must_use] 593 | #[inline] 594 | #[cfg(not(all($($(if $signed)? false)?)))] 595 | pub const fn saturating_neg(self) -> Self { 596 | Self::new_saturating(self.get().saturating_neg()) 597 | } 598 | 599 | /// Wrapping (modular) negation. 600 | #[must_use] 601 | #[inline] 602 | #[cfg(not(all($($(if $signed)? false)?)))] 603 | #[cfg(not(all($($(if $has_wide)? false)?)))] 604 | pub const fn wrapping_neg(self) -> Self { 605 | Self::new_wrapping(-(self.get() as Wide<$inner>)) 606 | } 607 | 608 | /// Checked absolute value. 609 | #[must_use] 610 | #[inline] 611 | #[cfg(not(all($($(if $signed)? false)?)))] 612 | pub const fn checked_abs(self) -> Option { 613 | match self.get().checked_abs() { 614 | Some(val) => Self::new(val), 615 | None => None, 616 | } 617 | } 618 | 619 | /// Saturating absolute value. 620 | #[must_use] 621 | #[inline] 622 | #[cfg(not(all($($(if $signed)? false)?)))] 623 | pub const fn saturating_abs(self) -> Self { 624 | Self::new_saturating(self.get().saturating_abs()) 625 | } 626 | 627 | /// Wrapping (modular) absolute value. 628 | #[must_use] 629 | #[inline] 630 | #[cfg(not(all($($(if $signed)? false)?)))] 631 | #[cfg(not(all($($(if $has_wide)? false)?)))] 632 | pub const fn wrapping_abs(self) -> Self { 633 | Self::new_wrapping((self.get() as Wide<$inner>).abs()) 634 | } 635 | 636 | /// Checked exponentiation. 637 | #[must_use] 638 | #[inline] 639 | pub const fn checked_pow(self, rhs: u32) -> Option { 640 | match self.get().checked_pow(rhs) { 641 | Some(val) => Self::new(val), 642 | None => None, 643 | } 644 | } 645 | 646 | /// Saturating exponentiation. 647 | #[must_use] 648 | #[inline] 649 | pub const fn saturating_pow(self, rhs: u32) -> Self { 650 | Self::new_saturating(self.get().saturating_pow(rhs)) 651 | } 652 | 653 | /// Wrapping (modular) exponentiation. 654 | #[must_use] 655 | #[inline] 656 | #[cfg(not(all($($(if $has_wide)? false)?)))] 657 | pub const fn wrapping_pow(self, mut exp: u32) -> Self { 658 | let range_sub_one = Self::MAX_VALUE.abs_diff(Self::MIN_VALUE); 659 | let Some(range) = range_sub_one.checked_add(1) else { 660 | return unsafe { Self::new_unchecked(self.get().wrapping_pow(exp)) }; 661 | }; 662 | 663 | // Exponentiation by squaring (same algorithm as used in std), but taking modulo 664 | // each time. We keep our values in the range [0, MAX − MIN]. 665 | if exp == 0 { 666 | return Self::new_wrapping::<$inner>(1); 667 | } 668 | let mut base = self.get() as Wide<$inner>; 669 | let mut acc: Wide<$inner> = 1; 670 | let range = range as Wide<$inner>; 671 | loop { 672 | if (exp & 1) == 1 { 673 | acc = (acc * base).rem_euclid(range); 674 | if exp == 1 { 675 | return Self::new_wrapping(acc); 676 | } 677 | } 678 | exp /= 2; 679 | base = (base * base).rem_euclid(range); 680 | } 681 | } 682 | 683 | /// Checked shift left. 684 | #[must_use] 685 | #[inline] 686 | pub const fn checked_shl(self, rhs: u32) -> Option { 687 | match self.get().checked_shl(rhs) { 688 | Some(val) => Self::new(val), 689 | None => None, 690 | } 691 | } 692 | 693 | /// Checked shift right. 694 | #[must_use] 695 | #[inline] 696 | pub const fn checked_shr(self, rhs: u32) -> Option { 697 | match self.get().checked_shr(rhs) { 698 | Some(val) => Self::new(val), 699 | None => None, 700 | } 701 | } 702 | } 703 | 704 | pub trait LargerInt: Copy { 705 | const KIND: &'static str; 706 | } 707 | 708 | $(#[automatically_derived] 709 | impl LargerInt for $super { const KIND: &'static str = stringify!($super); })* 710 | 711 | $crate::__unsafe_api_internal!(@with_super($ty, $inner) 712 | $({ super: $super, generics: $generics_single_token })* 713 | ); 714 | $crate::__unsafe_api_internal!(@with_non_super($ty, $inner) 715 | $({ non_super: $non_super, generics: $generics_single_token })* 716 | ); 717 | $crate::__unsafe_api_internal!(@with_all_int($ty, $inner) 718 | { int: u8, generics: $generics_single_token } 719 | { int: u16, generics: $generics_single_token } 720 | { int: u32, generics: $generics_single_token } 721 | { int: u64, generics: $generics_single_token } 722 | { int: u128, generics: $generics_single_token } 723 | { int: usize, generics: $generics_single_token } 724 | { int: i8, generics: $generics_single_token } 725 | { int: i16, generics: $generics_single_token } 726 | { int: i32, generics: $generics_single_token } 727 | { int: i64, generics: $generics_single_token } 728 | { int: i128, generics: $generics_single_token } 729 | { int: isize, generics: $generics_single_token } 730 | ); 731 | 732 | // === Clone / Copy === 733 | 734 | #[automatically_derived] 735 | impl<$($generics)*> Clone for $ty where $($where)* { 736 | fn clone(&self) -> Self { *self } 737 | } 738 | #[automatically_derived] 739 | impl<$($generics)*> Copy for $ty where $($where)* {} 740 | 741 | // === Default === 742 | 743 | #[cfg(not(all($($($zero)? false)?)))] 744 | #[automatically_derived] 745 | impl<$($generics)*> Default for $ty where $($where)* { 746 | fn default() -> Self { 747 | const { 748 | Self::new(0).expect("used `zero` on a type whose range does not include zero") 749 | } 750 | } 751 | } 752 | 753 | // Use a function to force post-mono errors even if `num-traits02` is disabled. 754 | #[cfg(not(all($($($one)? false)?)))] 755 | impl<$($generics)*> $ty where $($where)* { 756 | #[allow(unused)] 757 | fn one() -> Self { 758 | const { 759 | Self::new(1).expect("used `one` on a type whose range does not include one") 760 | } 761 | } 762 | } 763 | 764 | // === Operators === 765 | 766 | $crate::__unsafe_api_internal!(@bin_ops($ty, $inner) 767 | ($generics_single_token, Add::add/AddAssign::add_assign, "add"), 768 | ($generics_single_token, Sub::sub/SubAssign::sub_assign, "subtract"), 769 | ($generics_single_token, Mul::mul/MulAssign::mul_assign, "multiply"), 770 | ($generics_single_token, Div::div/DivAssign::div_assign, "divide"), 771 | ($generics_single_token, Rem::rem/RemAssign::rem_assign, "take remainder"), 772 | ($generics_single_token, BitAnd::bitand/BitAndAssign::bitand_assign, "binary and"), 773 | ($generics_single_token, BitOr::bitor/BitOrAssign::bitor_assign, "binary or"), 774 | ($generics_single_token, BitXor::bitxor/BitXorAssign::bitxor_assign, "binary xor"), 775 | ); 776 | use ::core::ops::{Shl, Shr, ShlAssign, ShrAssign}; 777 | #[automatically_derived] 778 | impl<$($generics)*> Shl for $ty where $($where)* { 779 | type Output = Self; 780 | #[inline] 781 | fn shl(self, rhs: u32) -> Self::Output { 782 | Self::new(self.get().shl(rhs)) 783 | .expect("Attempted to shift left out of range") 784 | } 785 | } 786 | $crate::__unsafe_api_internal!(@bin_op_variations 787 | $generics_single_token, 788 | $ty, u32, Shl::shl/ShlAssign::shl_assign 789 | ); 790 | #[automatically_derived] 791 | impl<$($generics)*> Shr for $ty where $($where)* { 792 | type Output = Self; 793 | #[inline] 794 | fn shr(self, rhs: u32) -> Self::Output { 795 | Self::new(self.get().shr(rhs)) 796 | .expect("Attempted to shift right out of range") 797 | } 798 | } 799 | $crate::__unsafe_api_internal!(@bin_op_variations 800 | $generics_single_token, 801 | $ty, u32, Shr::shr/ShrAssign::shr_assign 802 | ); 803 | 804 | #[cfg(not(all($($(if $signed)? false)?)))] 805 | use ::core::ops::Neg; 806 | 807 | #[cfg(not(all($($(if $signed)? false)?)))] 808 | #[automatically_derived] 809 | impl<$($generics)*> Neg for $ty where $($where)* { 810 | type Output = Self; 811 | #[inline] 812 | fn neg(self) -> Self::Output { 813 | Self::new(-self.get()) 814 | .expect("Attempted to negate out of range") 815 | } 816 | } 817 | #[cfg(not(all($($(if $signed)? false)?)))] 818 | #[automatically_derived] 819 | impl<$($generics)*> Neg for &$ty where $($where)* { 820 | type Output = $ty; 821 | #[inline] 822 | fn neg(self) -> Self::Output { 823 | -*self 824 | } 825 | } 826 | 827 | use ::core::ops::Not; 828 | 829 | #[automatically_derived] 830 | impl<$($generics)*> Not for $ty where $($where)* { 831 | type Output = Self; 832 | #[inline] 833 | fn not(self) -> Self::Output { 834 | Self::new(!self.get()) 835 | .expect("Attempted to invert bits out of range") 836 | } 837 | } 838 | #[automatically_derived] 839 | impl<$($generics)*> Not for &$ty where $($where)* { 840 | type Output = $ty; 841 | #[inline] 842 | fn not(self) -> Self::Output { 843 | !*self 844 | } 845 | } 846 | 847 | // === Comparisons and Hash === 848 | 849 | #[automatically_derived] 850 | impl<$($generics)*> PartialEq<$inner> for $ty where $($where)* { 851 | #[inline] 852 | fn eq(&self, other: &$inner) -> bool { 853 | self.get() == *other 854 | } 855 | } 856 | #[automatically_derived] 857 | impl<$($generics)*> PartialEq<$ty> for $inner where $($where)* { 858 | #[inline] 859 | fn eq(&self, other: &$ty) -> bool { 860 | *self == other.get() 861 | } 862 | } 863 | #[automatically_derived] 864 | impl<$($generics)*> PartialEq for $ty where $($where)* { 865 | #[inline] 866 | fn eq(&self, other: &$ty) -> bool { 867 | self.get() == other.get() 868 | } 869 | } 870 | #[automatically_derived] 871 | impl<$($generics)*> Eq for $ty where $($where)* {} 872 | 873 | #[automatically_derived] 874 | impl<$($generics)*> PartialOrd<$inner> for $ty where $($where)* { 875 | #[inline] 876 | fn partial_cmp(&self, other: &$inner) -> Option { 877 | self.get().partial_cmp(other) 878 | } 879 | } 880 | #[automatically_derived] 881 | impl<$($generics)*> PartialOrd<$ty> for $inner where $($where)* { 882 | #[inline] 883 | fn partial_cmp(&self, other: &$ty) -> Option { 884 | self.partial_cmp(&other.get()) 885 | } 886 | } 887 | #[automatically_derived] 888 | impl<$($generics)*> PartialOrd for $ty where $($where)* { 889 | #[inline] 890 | fn partial_cmp(&self, other: &$ty) -> Option { 891 | Some(self.cmp(other)) 892 | } 893 | } 894 | #[automatically_derived] 895 | impl<$($generics)*> Ord for $ty where $($where)* { 896 | #[inline] 897 | fn cmp(&self, other: &$ty) -> cmp::Ordering { 898 | self.get().cmp(&other.get()) 899 | } 900 | } 901 | 902 | #[automatically_derived] 903 | impl<$($generics)*> Hash for $ty where $($where)* { 904 | #[inline] 905 | fn hash(&self, state: &mut H) { 906 | self.get().hash(state); 907 | } 908 | } 909 | 910 | // === AsRef, Borrow === 911 | 912 | #[automatically_derived] 913 | impl<$($generics)*> AsRef<$inner> for $ty where $($where)* { 914 | #[inline] 915 | fn as_ref(&self) -> &$inner { 916 | self.get_ref() 917 | } 918 | } 919 | #[automatically_derived] 920 | impl<$($generics)*> Borrow<$inner> for $ty where $($where)* { 921 | #[inline] 922 | fn borrow(&self) -> &$inner { 923 | self.get_ref() 924 | } 925 | } 926 | 927 | // === Iterator traits === 928 | 929 | // Sum bounded to bounded 930 | #[automatically_derived] 931 | impl<$($generics)*> iter::Sum for $ty where $($where)* { 932 | fn sum>(iter: I) -> Self { 933 | iter.reduce(Add::add) 934 | .unwrap_or_else(|| Self::new(0).expect("Attempted to sum to zero")) 935 | } 936 | } 937 | #[automatically_derived] 938 | impl<'__a, $($generics)*> iter::Sum<&'__a Self> for $ty where $($where)* { 939 | fn sum>(iter: I) -> Self { 940 | iter.copied().sum() 941 | } 942 | } 943 | 944 | // Sum bounded to primitive 945 | #[automatically_derived] 946 | impl<$($generics)*> iter::Sum<$ty> for $inner where $($where)* { 947 | fn sum>(iter: I) -> Self { 948 | iter.map(<$ty>::get).sum() 949 | } 950 | } 951 | #[automatically_derived] 952 | impl<'__a, $($generics)*> iter::Sum<&'__a $ty> for $inner where $($where)* { 953 | fn sum>(iter: I) -> Self { 954 | iter.copied().sum() 955 | } 956 | } 957 | 958 | // Take product of bounded to bounded 959 | #[automatically_derived] 960 | impl<$($generics)*> iter::Product for $ty where $($where)* { 961 | fn product>(iter: I) -> Self { 962 | iter.reduce(Mul::mul) 963 | .unwrap_or_else(|| Self::new(1).expect("Attempted to take product to one")) 964 | } 965 | } 966 | #[automatically_derived] 967 | impl<'__a, $($generics)*> iter::Product<&'__a Self> for $ty where $($where)* { 968 | fn product>(iter: I) -> Self { 969 | iter.copied().product() 970 | } 971 | } 972 | 973 | // Take product of bounded to primitive 974 | #[automatically_derived] 975 | impl<$($generics)*> iter::Product<$ty> for $inner where $($where)* { 976 | fn product>(iter: I) -> Self { 977 | iter.map(<$ty>::get).product() 978 | } 979 | } 980 | #[automatically_derived] 981 | impl<'__a, $($generics)*> iter::Product<&'__a $ty> for $inner where $($where)* { 982 | fn product>(iter: I) -> Self { 983 | iter.copied().product() 984 | } 985 | } 986 | 987 | $crate::__private::__cfg_step_trait! { 988 | #[automatically_derived] 989 | impl<$($generics)*> iter::Step for $ty where $($where)* { 990 | #[inline] 991 | fn steps_between(start: &Self, end: &Self) -> (usize, Option) { 992 | iter::Step::steps_between(&start.get(), &end.get()) 993 | } 994 | #[inline] 995 | fn forward_checked(start: Self, count: usize) -> Option { 996 | iter::Step::forward_checked(start.get(), count).and_then(Self::new) 997 | } 998 | #[inline] 999 | fn backward_checked(start: Self, count: usize) -> Option { 1000 | iter::Step::backward_checked(start.get(), count).and_then(Self::new) 1001 | } 1002 | } 1003 | } 1004 | 1005 | // === Parsing === 1006 | 1007 | #[automatically_derived] 1008 | impl<$($generics)*> FromStr for $ty where $($where)* { 1009 | type Err = ParseError; 1010 | fn from_str(s: &str) -> Result { 1011 | Self::from_str_radix(s, 10) 1012 | } 1013 | } 1014 | 1015 | // === Formatting === 1016 | 1017 | #[automatically_derived] 1018 | impl<$($generics)*> fmt::Debug for $ty where $($where)* { 1019 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1020 | f.debug_tuple("Bounded").field(&self.get()).finish() 1021 | } 1022 | } 1023 | 1024 | $crate::__unsafe_api_internal!(@fmt_traits($ty, $inner) 1025 | $generics_single_token Binary, 1026 | $generics_single_token Display, 1027 | $generics_single_token LowerExp, 1028 | $generics_single_token LowerHex, 1029 | $generics_single_token Octal, 1030 | $generics_single_token UpperExp, 1031 | $generics_single_token UpperHex, 1032 | ); 1033 | 1034 | // === Arbitrary === 1035 | 1036 | $crate::__private::__cfg_arbitrary1! { 1037 | use $crate::__private::arbitrary1::{self, Arbitrary, Unstructured}; 1038 | 1039 | #[automatically_derived] 1040 | impl<'__a, $($generics)*> Arbitrary<'__a> for $ty where $($where)* { 1041 | fn arbitrary(u: &mut Unstructured<'__a>) -> arbitrary1::Result { 1042 | Self::new(u.arbitrary()?).ok_or(arbitrary1::Error::IncorrectFormat) 1043 | } 1044 | 1045 | #[inline] 1046 | fn size_hint(depth: usize) -> (usize, Option) { 1047 | <$inner as Arbitrary<'__a>>::size_hint(depth) 1048 | } 1049 | } 1050 | } 1051 | 1052 | // === Bytemuck === 1053 | 1054 | $crate::__private::__cfg_bytemuck1! { 1055 | use $crate::__private::bytemuck1; 1056 | 1057 | #[automatically_derived] 1058 | unsafe impl<$($generics)*> bytemuck1::Contiguous for $ty 1059 | where 1060 | Self: 'static, 1061 | $($where)* 1062 | { 1063 | type Int = $inner; 1064 | const MAX_VALUE: $inner = Self::MAX_VALUE; 1065 | const MIN_VALUE: $inner = Self::MIN_VALUE; 1066 | } 1067 | 1068 | #[automatically_derived] 1069 | unsafe impl<$($generics)*> bytemuck1::NoUninit for $ty 1070 | where 1071 | Self: 'static, 1072 | $($where)* 1073 | {} 1074 | 1075 | #[cfg(not(all($($(if $zero)? false)?)))] 1076 | #[automatically_derived] 1077 | unsafe impl<$($generics)*> bytemuck1::Zeroable for $ty where $($where)* {} 1078 | } 1079 | 1080 | // === Num === 1081 | 1082 | $crate::__private::__cfg_num_traits02! { 1083 | use $crate::__private::num_traits02; 1084 | 1085 | #[automatically_derived] 1086 | impl<$($generics)*> num_traits02::Bounded for $ty where $($where)* { 1087 | fn min_value() -> Self { 1088 | Self::MIN 1089 | } 1090 | fn max_value() -> Self { 1091 | Self::MAX 1092 | } 1093 | } 1094 | 1095 | #[automatically_derived] 1096 | impl<__T, $($generics)*> num_traits02::AsPrimitive<__T> for $ty 1097 | where 1098 | $inner: num_traits02::AsPrimitive<__T>, 1099 | __T: 'static + Copy, 1100 | Self: 'static, 1101 | $($where)* 1102 | { 1103 | fn as_(self) -> __T { 1104 | self.get().as_() 1105 | } 1106 | } 1107 | 1108 | #[automatically_derived] 1109 | impl<$($generics)*> num_traits02::FromPrimitive for $ty 1110 | where 1111 | $inner: num_traits02::FromPrimitive, 1112 | $($where)* 1113 | { 1114 | fn from_i64(n: i64) -> Option { 1115 | <$inner>::from_i64(n).and_then(Self::new) 1116 | } 1117 | fn from_u64(n: u64) -> Option { 1118 | <$inner>::from_u64(n).and_then(Self::new) 1119 | } 1120 | fn from_isize(n: isize) -> Option { 1121 | <$inner>::from_isize(n).and_then(Self::new) 1122 | } 1123 | fn from_i8(n: i8) -> Option { 1124 | <$inner>::from_i8(n).and_then(Self::new) 1125 | } 1126 | fn from_i16(n: i16) -> Option { 1127 | <$inner>::from_i16(n).and_then(Self::new) 1128 | } 1129 | fn from_i32(n: i32) -> Option { 1130 | <$inner>::from_i32(n).and_then(Self::new) 1131 | } 1132 | fn from_i128(n: i128) -> Option { 1133 | <$inner>::from_i128(n).and_then(Self::new) 1134 | } 1135 | fn from_usize(n: usize) -> Option { 1136 | <$inner>::from_usize(n).and_then(Self::new) 1137 | } 1138 | fn from_u8(n: u8) -> Option { 1139 | <$inner>::from_u8(n).and_then(Self::new) 1140 | } 1141 | fn from_u16(n: u16) -> Option { 1142 | <$inner>::from_u16(n).and_then(Self::new) 1143 | } 1144 | fn from_u32(n: u32) -> Option { 1145 | <$inner>::from_u32(n).and_then(Self::new) 1146 | } 1147 | fn from_u128(n: u128) -> Option { 1148 | <$inner>::from_u128(n).and_then(Self::new) 1149 | } 1150 | fn from_f32(n: f32) -> Option { 1151 | <$inner>::from_f32(n).and_then(Self::new) 1152 | } 1153 | fn from_f64(n: f64) -> Option { 1154 | <$inner>::from_f64(n).and_then(Self::new) 1155 | } 1156 | } 1157 | 1158 | #[automatically_derived] 1159 | impl<$($generics)*> num_traits02::NumCast for $ty 1160 | where 1161 | $inner: num_traits02::NumCast, 1162 | $($where)* 1163 | { 1164 | fn from<__T: num_traits02::ToPrimitive>(n: __T) -> Option { 1165 | <$inner as num_traits02::NumCast>::from(n).map(Self::new).flatten() 1166 | } 1167 | } 1168 | 1169 | #[automatically_derived] 1170 | impl<$($generics)*> num_traits02::ToPrimitive for $ty 1171 | where 1172 | $inner: num_traits02::ToPrimitive, 1173 | $($where)* 1174 | { 1175 | fn to_i64(&self) -> Option { 1176 | self.get().to_i64() 1177 | } 1178 | fn to_u64(&self) -> Option { 1179 | self.get().to_u64() 1180 | } 1181 | fn to_isize(&self) -> Option { 1182 | self.get().to_isize() 1183 | } 1184 | fn to_i8(&self) -> Option { 1185 | self.get().to_i8() 1186 | } 1187 | fn to_i16(&self) -> Option { 1188 | self.get().to_i16() 1189 | } 1190 | fn to_i32(&self) -> Option { 1191 | self.get().to_i32() 1192 | } 1193 | fn to_i128(&self) -> Option { 1194 | self.get().to_i128() 1195 | } 1196 | fn to_usize(&self) -> Option { 1197 | self.get().to_usize() 1198 | } 1199 | fn to_u8(&self) -> Option { 1200 | self.get().to_u8() 1201 | } 1202 | fn to_u16(&self) -> Option { 1203 | self.get().to_u16() 1204 | } 1205 | fn to_u32(&self) -> Option { 1206 | self.get().to_u32() 1207 | } 1208 | fn to_u128(&self) -> Option { 1209 | self.get().to_u128() 1210 | } 1211 | fn to_f32(&self) -> Option { 1212 | self.get().to_f32() 1213 | } 1214 | fn to_f64(&self) -> Option { 1215 | self.get().to_f64() 1216 | } 1217 | } 1218 | 1219 | #[automatically_derived] 1220 | impl<$($generics)*> num_traits02::CheckedAdd for $ty where $($where)* { 1221 | fn checked_add(&self, v: &Self) -> Option { 1222 | Self::checked_add(*self, v.get()) 1223 | } 1224 | } 1225 | 1226 | #[automatically_derived] 1227 | impl<$($generics)*> num_traits02::CheckedDiv for $ty where $($where)* { 1228 | fn checked_div(&self, v: &Self) -> Option { 1229 | Self::checked_div(*self, v.get()) 1230 | } 1231 | } 1232 | 1233 | #[automatically_derived] 1234 | impl<$($generics)*> num_traits02::CheckedMul for $ty where $($where)* { 1235 | fn checked_mul(&self, v: &Self) -> Option { 1236 | Self::checked_mul(*self, v.get()) 1237 | } 1238 | } 1239 | 1240 | #[automatically_derived] 1241 | impl<$($generics)*> num_traits02::CheckedNeg for $ty where $($where)* { 1242 | fn checked_neg(&self) -> Option { 1243 | Self::checked_neg(*self) 1244 | } 1245 | } 1246 | 1247 | #[automatically_derived] 1248 | impl<$($generics)*> num_traits02::CheckedRem for $ty where $($where)* { 1249 | fn checked_rem(&self, v: &Self) -> Option { 1250 | Self::checked_rem(*self, v.get()) 1251 | } 1252 | } 1253 | 1254 | #[automatically_derived] 1255 | impl<$($generics)*> num_traits02::CheckedShl for $ty where $($where)* { 1256 | fn checked_shl(&self, v: u32) -> Option { 1257 | Self::checked_shl(*self, v) 1258 | } 1259 | } 1260 | 1261 | #[automatically_derived] 1262 | impl<$($generics)*> num_traits02::CheckedShr for $ty where $($where)* { 1263 | fn checked_shr(&self, v: u32) -> Option { 1264 | Self::checked_shr(*self, v) 1265 | } 1266 | } 1267 | 1268 | #[automatically_derived] 1269 | impl<$($generics)*> num_traits02::CheckedSub for $ty where $($where)* { 1270 | fn checked_sub(&self, v: &Self) -> Option { 1271 | Self::checked_sub(*self, v.get()) 1272 | } 1273 | } 1274 | 1275 | #[automatically_derived] 1276 | impl<__A, __B, $($generics)*> num_traits02::MulAdd<__A, __B> for $ty 1277 | where 1278 | $inner: num_traits02::MulAdd<__A, __B, Output = $inner>, 1279 | $($where)* 1280 | { 1281 | type Output = $inner; 1282 | 1283 | fn mul_add(self, a: __A, b: __B) -> Self::Output { 1284 | self.get().mul_add(a, b) 1285 | } 1286 | } 1287 | 1288 | #[automatically_derived] 1289 | impl<$($generics)*> num_traits02::SaturatingAdd for $ty where $($where)* { 1290 | fn saturating_add(&self, v: &Self) -> Self { 1291 | Self::saturating_add(*self, v.get()) 1292 | } 1293 | } 1294 | 1295 | #[automatically_derived] 1296 | impl<$($generics)*> num_traits02::SaturatingMul for $ty where $($where)* { 1297 | fn saturating_mul(&self, v: &Self) -> Self { 1298 | Self::saturating_mul(*self, v.get()) 1299 | } 1300 | } 1301 | 1302 | #[automatically_derived] 1303 | impl<$($generics)*> num_traits02::SaturatingSub for $ty where $($where)* { 1304 | fn saturating_sub(&self, v: &Self) -> Self { 1305 | Self::saturating_sub(*self, v.get()) 1306 | } 1307 | } 1308 | 1309 | #[cfg(not(all($($($zero)? false)?)))] 1310 | #[automatically_derived] 1311 | impl<$($generics)*> num_traits02::Zero for $ty where $($where)* { 1312 | fn zero() -> Self { 1313 | Self::default() 1314 | } 1315 | fn is_zero(&self) -> bool { 1316 | self.get() == 0 1317 | } 1318 | } 1319 | 1320 | #[cfg(not(all($($($one)? false)?)))] 1321 | #[automatically_derived] 1322 | impl<$($generics)*> num_traits02::One for $ty where $($where)* { 1323 | fn one() -> Self { 1324 | Self::one() 1325 | } 1326 | } 1327 | } 1328 | 1329 | // === Serde === 1330 | 1331 | $crate::__private::__cfg_serde1! { 1332 | use $crate::__private::serde1::{self, Deserialize, Deserializer, Serialize, Serializer}; 1333 | 1334 | #[automatically_derived] 1335 | impl<$($generics)*> Serialize for $ty where $($where)* { 1336 | fn serialize(&self, serializer: S) -> Result { 1337 | self.get().serialize(serializer) 1338 | } 1339 | } 1340 | 1341 | // Disable this to prevent triggering `clippy::unsafe_derive_deserialize`. I couldn’t 1342 | // figure out how to `#[allow]` it. 1343 | // #[automatically_derived] 1344 | impl<'__de, $($generics)*> Deserialize<'__de> for $ty where $($where)* { 1345 | fn deserialize>(deserializer: D) -> Result { 1346 | Self::new(<$inner>::deserialize(deserializer)?) 1347 | .ok_or_else(|| { 1348 | ::custom(format_args!( 1349 | "integer out of range, expected it to be between {} and {}", 1350 | Self::MIN_VALUE, 1351 | Self::MAX_VALUE, 1352 | )) 1353 | }) 1354 | } 1355 | } 1356 | } 1357 | }; }; 1358 | 1359 | (@repr u8, $($rest:tt)*) => { $crate::__unsafe_api_internal! { 1360 | @repr { 1361 | type: u8, 1362 | signed: false, 1363 | supersets: [u8, u16, u32, u64, u128, usize, i16, i32, i64, i128, isize], 1364 | non_supersets: [i8], 1365 | has_wide, 1366 | }, 1367 | $($rest)* 1368 | } }; 1369 | (@repr u16, $($rest:tt)*) => { $crate::__unsafe_api_internal! { 1370 | @repr { 1371 | type: u16, 1372 | signed: false, 1373 | supersets: [u16, u32, u64, u128, usize, i32, i64, i128], 1374 | non_supersets: [u8, i8, i16, isize], 1375 | has_wide, 1376 | }, 1377 | $($rest)* 1378 | } }; 1379 | (@repr u32, $($rest:tt)*) => { $crate::__unsafe_api_internal! { 1380 | @repr { 1381 | type: u32, 1382 | signed: false, 1383 | supersets: [u32, u64, u128, i64, i128], 1384 | non_supersets: [u8, u16, usize, i8, i16, i32, isize], 1385 | has_wide, 1386 | }, 1387 | $($rest)* 1388 | } }; 1389 | (@repr u64, $($rest:tt)*) => { $crate::__unsafe_api_internal! { 1390 | @repr { 1391 | type: u64, 1392 | signed: false, 1393 | supersets: [u64, u128, i128], 1394 | non_supersets: [u8, u16, u32, usize, i8, i16, i32, i64, isize], 1395 | has_wide, 1396 | }, 1397 | $($rest)* 1398 | } }; 1399 | (@repr u128, $($rest:tt)*) => { $crate::__unsafe_api_internal! { 1400 | @repr { 1401 | type: u128, 1402 | signed: false, 1403 | supersets: [u128], 1404 | non_supersets: [u8, u16, u32, u64, usize, i8, i16, i32, i64, i128, isize], 1405 | }, 1406 | $($rest)* 1407 | } }; 1408 | (@repr usize, $($rest:tt)*) => { $crate::__unsafe_api_internal! { 1409 | @repr { 1410 | type: usize, 1411 | signed: false, 1412 | supersets: [usize], 1413 | non_supersets: [u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, isize], 1414 | }, 1415 | $($rest)* 1416 | } }; 1417 | (@repr i8, $($rest:tt)*) => { $crate::__unsafe_api_internal! { 1418 | @repr { 1419 | type: i8, 1420 | signed: true, 1421 | supersets: [i8, i16, i32, i64, i128, isize], 1422 | non_supersets: [u8, u16, u32, u64, u128, usize], 1423 | has_wide, 1424 | }, 1425 | $($rest)* 1426 | } }; 1427 | (@repr i16, $($rest:tt)*) => { $crate::__unsafe_api_internal! { 1428 | @repr { 1429 | type: i16, 1430 | signed: true, 1431 | supersets: [i16, i32, i64, i128, isize], 1432 | non_supersets: [u8, u16, u32, u64, u128, usize, i8], 1433 | has_wide, 1434 | }, 1435 | $($rest)* 1436 | } }; 1437 | (@repr i32, $($rest:tt)*) => { $crate::__unsafe_api_internal! { 1438 | @repr { 1439 | type: i32, 1440 | signed: true, 1441 | supersets: [i32, i64, i128], 1442 | non_supersets: [u8, u16, u32, u64, u128, usize, i8, i16, isize], 1443 | has_wide, 1444 | }, 1445 | $($rest)* 1446 | } }; 1447 | (@repr i64, $($rest:tt)*) => { $crate::__unsafe_api_internal! { 1448 | @repr { 1449 | type: i64, 1450 | signed: true, 1451 | supersets: [i64, i128], 1452 | non_supersets: [u8, u16, u32, u64, u128, usize, i8, i16, i32, isize], 1453 | has_wide, 1454 | }, 1455 | $($rest)* 1456 | } }; 1457 | (@repr i128, $($rest:tt)*) => { $crate::__unsafe_api_internal! { 1458 | @repr { 1459 | type: i128, 1460 | signed: true, 1461 | supersets: [i128], 1462 | non_supersets: [u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, isize], 1463 | }, 1464 | $($rest)* 1465 | } }; 1466 | (@repr isize, $($rest:tt)*) => { $crate::__unsafe_api_internal! { 1467 | @repr { 1468 | type: isize, 1469 | signed: true, 1470 | supersets: [isize], 1471 | non_supersets: [u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128], 1472 | }, 1473 | $($rest)* 1474 | } }; 1475 | 1476 | (@with_super($ty:ty, $inner:ty) 1477 | $({ super: $super:ty, generics: ([$($generics:tt)*] where $($where:tt)*) })* 1478 | ) => { $( 1479 | #[automatically_derived] 1480 | impl<$($generics)*> From<$ty> for $super where $($where)* { 1481 | fn from(bounded: $ty) -> Self { 1482 | Self::from(bounded.get()) 1483 | } 1484 | } 1485 | )* }; 1486 | 1487 | (@with_non_super($ty:ty, $inner:ty) 1488 | $({ non_super: $non_super:ty, generics: ([$($generics:tt)*] where $($where:tt)*) })* 1489 | ) => { $( 1490 | #[automatically_derived] 1491 | impl<$($generics)*> TryFrom<$ty> for $non_super where $($where)* { 1492 | type Error = ::core::num::TryFromIntError; 1493 | fn try_from(n: $ty) -> Result { 1494 | <$non_super as TryFrom<$inner>>::try_from(n.get()) 1495 | } 1496 | } 1497 | )* }; 1498 | 1499 | (@with_all_int($ty:ty, $inner:ty) 1500 | $({ int: $int:ty, generics: ([$($generics:tt)*] where $($where:tt)*) })* 1501 | ) => { $( 1502 | #[automatically_derived] 1503 | impl<$($generics)*> TryFrom<$int> for $ty where $($where)* { 1504 | type Error = $crate::TryFromError; 1505 | fn try_from(n: $int) -> Result { 1506 | <$inner as TryFrom<$int>>::try_from(n) 1507 | .ok() 1508 | .and_then(Self::new) 1509 | .ok_or_else($crate::__private::try_from_error) 1510 | } 1511 | } 1512 | )* }; 1513 | 1514 | (@bin_ops($ty:ty, $inner:ty) 1515 | $(( 1516 | ([$($generics:tt)*] where $($where:tt)*), 1517 | $op:ident::$method:ident/$op_assign:ident::$method_assign:ident, $desc:literal 1518 | ),)* 1519 | ) => { $( 1520 | use ::core::ops::{$op, $op_assign}; 1521 | 1522 | #[automatically_derived] 1523 | impl<$($generics)*> $op<$inner> for $ty where $($where)* { 1524 | type Output = Self; 1525 | #[inline] 1526 | fn $method(self, rhs: $inner) -> Self::Output { 1527 | Self::new(self.get().$method(rhs)) 1528 | .expect(concat!("Attempted to ", $desc, " out of range")) 1529 | } 1530 | } 1531 | $crate::__unsafe_api_internal!(@bin_op_variations 1532 | ([$($generics)*] where $($where)*), 1533 | $ty, $inner, $op::$method/$op_assign::$method_assign 1534 | ); 1535 | 1536 | #[automatically_derived] 1537 | impl<$($generics)*> $op<$ty> for $inner where $($where)* { 1538 | type Output = Self; 1539 | #[inline] 1540 | fn $method(self, rhs: $ty) -> Self::Output { 1541 | self.$method(rhs.get()) 1542 | } 1543 | } 1544 | $crate::__unsafe_api_internal!(@bin_op_variations 1545 | ([$($generics)*] where $($where)*), 1546 | $inner, $ty, $op::$method/$op_assign::$method_assign 1547 | ); 1548 | 1549 | #[automatically_derived] 1550 | impl<$($generics)*> $op<$ty> for $ty where $($where)* { 1551 | type Output = Self; 1552 | #[inline] 1553 | fn $method(self, rhs: $ty) -> Self::Output { 1554 | self.$method(rhs.get()) 1555 | } 1556 | } 1557 | $crate::__unsafe_api_internal!(@bin_op_variations 1558 | ([$($generics)*] where $($where)*), 1559 | $ty, $ty, $op::$method/$op_assign::$method_assign 1560 | ); 1561 | )* }; 1562 | 1563 | (@bin_op_variations 1564 | ([$($generics:tt)*] where $($where:tt)*), 1565 | $lhs:ty, $rhs:ty, $op:ident::$method:ident/$op_assign:ident::$method_assign:ident 1566 | ) => { 1567 | #[automatically_derived] 1568 | impl<$($generics)*> $op<$rhs> for &$lhs where $($where)* { 1569 | type Output = $lhs; 1570 | #[inline] 1571 | fn $method(self, rhs: $rhs) -> Self::Output { 1572 | <$lhs as $op<$rhs>>::$method(*self, rhs) 1573 | } 1574 | } 1575 | #[automatically_derived] 1576 | impl<$($generics)*> $op<&$rhs> for $lhs where $($where)* { 1577 | type Output = $lhs; 1578 | #[inline] 1579 | fn $method(self, rhs: &$rhs) -> Self::Output { 1580 | <$lhs as $op<$rhs>>::$method(self, *rhs) 1581 | } 1582 | } 1583 | #[automatically_derived] 1584 | impl<$($generics)*> $op<&$rhs> for &$lhs where $($where)* { 1585 | type Output = $lhs; 1586 | #[inline] 1587 | fn $method(self, rhs: &$rhs) -> Self::Output { 1588 | <$lhs as $op<$rhs>>::$method(*self, *rhs) 1589 | } 1590 | } 1591 | 1592 | #[automatically_derived] 1593 | impl<$($generics)*> $op_assign<$rhs> for $lhs where $($where)* { 1594 | #[inline] 1595 | fn $method_assign(&mut self, rhs: $rhs) { 1596 | *self = >::$method(*self, rhs); 1597 | } 1598 | } 1599 | #[automatically_derived] 1600 | impl<$($generics)*> $op_assign<&$rhs> for $lhs where $($where)* { 1601 | #[inline] 1602 | fn $method_assign(&mut self, rhs: &$rhs) { 1603 | *self = >::$method(*self, *rhs); 1604 | } 1605 | } 1606 | }; 1607 | 1608 | (@fmt_traits($ty:ty, $inner:ty) 1609 | $(([$($generics:tt)*] where $($where:tt)*) $trait:ident,)* 1610 | ) => { $( 1611 | #[automatically_derived] 1612 | impl<$($generics)*> fmt::$trait for $ty where $($where)* { 1613 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1614 | fmt::$trait::fmt(&self.get(), f) 1615 | } 1616 | } 1617 | )* } 1618 | } 1619 | 1620 | // Most functionality is tested in `types.rs`. But there are some things that API cannot do. 1621 | #[cfg(test)] 1622 | mod tests { 1623 | use crate::unsafe_api; 1624 | use core::ffi::c_int; 1625 | use core::marker::PhantomData; 1626 | 1627 | #[test] 1628 | fn c_int() { 1629 | #[repr(transparent)] 1630 | struct S(c_int); 1631 | 1632 | unsafe_api! { 1633 | for S, 1634 | unsafe repr: { 1635 | type: c_int, 1636 | signed: true, 1637 | supersets: [i32, i64, i128], 1638 | non_supersets: [], 1639 | has_wide, 1640 | }, 1641 | min: -5, 1642 | max: 5, 1643 | zero, 1644 | } 1645 | } 1646 | 1647 | #[test] 1648 | fn where_clause() { 1649 | #[repr(transparent)] 1650 | struct S(i32, PhantomData); 1651 | 1652 | unsafe_api! { 1653 | [T] for S where { T: Copy }, 1654 | unsafe repr: i32, 1655 | min: -1, 1656 | max: i32::MAX, 1657 | zero, 1658 | one, 1659 | } 1660 | } 1661 | 1662 | #[test] 1663 | fn attr() { 1664 | #[deprecated] 1665 | #[repr(transparent)] 1666 | struct S(i32); 1667 | 1668 | unsafe_api! { 1669 | #![allow(deprecated)] 1670 | for S, 1671 | unsafe repr: i32, 1672 | min: 0, 1673 | max: 1, 1674 | zero, 1675 | one, 1676 | } 1677 | } 1678 | } 1679 | --------------------------------------------------------------------------------