diff --git a/Cargo.toml b/Cargo.toml index a155b73..50a6f89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ license = "MIT OR Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] +enum_repr_16 = [] default = ["std"] std = [] diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..e1a00d4 --- /dev/null +++ b/build.rs @@ -0,0 +1,127 @@ +use std::io::{BufWriter, Result, Write}; + +#[derive(Clone, Copy)] +enum BitInfo { + Unsigned { + prim_max: u64, + }, + Signed { + signed_max: i64, + signed_min: i64, + prim_max: i64, + }, +} + +struct EnumInfo { + ty_name: &'static str, + name: &'static str, + bit_info: BitInfo, +} + +const FILES: &[EnumInfo] = &[ + EnumInfo { + ty_name: "u8", + name: "u8_repr.rs", + bit_info: BitInfo::Unsigned { + prim_max: u8::MAX as _, + }, + }, + EnumInfo { + ty_name: "i8", + name: "i8_repr.rs", + bit_info: BitInfo::Signed { + signed_max: i8::MAX as _, + signed_min: i8::MIN as _, + prim_max: u8::MAX as _, + }, + }, + #[cfg(feature = "enum_repr_16")] + EnumInfo { + ty_name: "u16", + name: "u16_repr.rs", + bit_info: BitInfo::Unsigned { + prim_max: u16::MAX as _, + }, + }, + #[cfg(feature = "enum_repr_16")] + EnumInfo { + ty_name: "i16", + name: "i16_repr.rs", + bit_info: BitInfo::Signed { + signed_max: i16::MAX as _, + signed_min: i16::MIN as _, + prim_max: u16::MAX as _, + }, + }, +]; + +fn generate_variants( + mut generated_file: impl Write, + repr_name: &str, + bit_info: BitInfo, +) -> Result<()> { + write!( + generated_file, + "#[derive(Clone, Copy, PartialEq, Eq, Hash)] + #[allow(dead_code)] + pub(crate) enum {} {{", + repr_name + )?; + + match bit_info { + BitInfo::Unsigned { prim_max } => { + for i in 0..prim_max { + write!(generated_file, "V{},", i)? + } + } + BitInfo::Signed { + signed_max, + signed_min, + prim_max, + } => { + for i in 0..signed_max { + write!(generated_file, "V{},", i)?; + } + + for (i, v) in (signed_max..prim_max).zip(signed_min..0) { + write!(generated_file, "MV{}={},", i, v)?; + } + } + } + + write!(generated_file, "}}")?; + Ok(()) +} + +fn generate_impl(mut generated_file: impl Write, repr_name: &str, ty_name: &str) -> Result<()> { + write!( + generated_file, + "impl {} {{ + pub(crate) const fn new(value: {}) -> Option {{ + unsafe {{ std::mem::transmute(value) }} + }} + }}", + repr_name, ty_name + ) +} + +fn main() -> Result<()> { + let out_dir = std::env::var("OUT_DIR").unwrap(); + + let mut open_options = std::fs::OpenOptions::new(); + open_options.create(true).write(true).truncate(true); + + for file in FILES { + let file_path = format!("{}/{}", out_dir, file.name); + let mut generated_file = BufWriter::new(open_options.open(file_path)?); + + let repr_name = format!("{}Repr", file.ty_name.to_uppercase()); + + generate_variants(&mut generated_file, &repr_name, file.bit_info)?; + generate_impl(&mut generated_file, &repr_name, file.ty_name)?; + + generated_file.flush()?; + } + + Ok(()) +} diff --git a/src/enum_impl/mod.rs b/src/enum_impl/mod.rs new file mode 100644 index 0000000..ddcff07 --- /dev/null +++ b/src/enum_impl/mod.rs @@ -0,0 +1,56 @@ +#[cfg(feature = "enum_repr_16")] +pub(crate) mod i16_repr { + include!(concat!(env!("OUT_DIR"), "/i16_repr.rs")); +} +#[cfg(feature = "enum_repr_16")] +pub(crate) mod u16_repr { + include!(concat!(env!("OUT_DIR"), "/u16_repr.rs")); +} +pub(crate) mod i8_repr { + include!(concat!(env!("OUT_DIR"), "/i8_repr.rs")); +} +pub(crate) mod u8_repr { + include!(concat!(env!("OUT_DIR"), "/u8_repr.rs")); +} + +macro_rules! nonmax { + ( $nonmax: ident, $primitive: ident, $byte_repr: ident ) => { + /// An integer that is known not to equal its maximum value. + #[derive(Clone, Copy, PartialEq, Eq, Hash)] + #[repr(transparent)] + pub struct $nonmax($byte_repr); + + impl $nonmax { + /// Creates a new non-max if the given value is not the maximum + /// value. + pub const fn new(value: $primitive) -> Option { + match $byte_repr::new(value) { + Some(byte) => Some(Self(byte)), + None => None, + } + } + + /// Creates a new non-max without checking the value. + /// + /// # Safety + /// + /// The value must not equal the maximum representable value for the + /// primitive type. + #[inline] + pub const unsafe fn new_unchecked(value: $primitive) -> Self { + match Self::new(value) { + Some(this) => this, + None => unsafe { std::hint::unreachable_unchecked() }, + } + } + + /// Returns the value as a primitive type. + #[inline] + pub const fn get(&self) -> $primitive { + self.0 as $primitive + } + } + }; +} + +pub(crate) use nonmax; diff --git a/src/lib.rs b/src/lib.rs index 3babaf1..6dbc029 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,6 +56,16 @@ will only require minor version bumps, but will need significant justification. #![forbid(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] +#[macro_use] +mod enum_impl; + +#[cfg(feature = "enum_repr_16")] +use enum_impl::i16_repr::I16Repr; +#[cfg(feature = "enum_repr_16")] +use enum_impl::u16_repr::U16Repr; +use enum_impl::i8_repr::I8Repr; +use enum_impl::u8_repr::U8Repr; + /// An error type returned when a checked integral type conversion fails (mimics [std::num::TryFromIntError]) #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct TryFromIntError(()); @@ -118,8 +128,9 @@ macro_rules! impl_nonmax_fmt { }; } +/// Define the basic nonmax wrapper using NonZero inner macro_rules! nonmax { - ( common, $nonmax: ident, $non_zero: ident, $primitive: ident ) => { + ( $nonmax: ident, $non_zero: ident, $primitive: ident ) => { /// An integer that is known not to equal its maximum value. #[derive(Clone, Copy, PartialEq, Eq, Hash)] #[repr(transparent)] @@ -153,7 +164,14 @@ macro_rules! nonmax { pub const fn get(&self) -> $primitive { self.0.get() ^ $primitive::MAX } + } + }; +} +/// Provide convenience methods over an basic nonmax wrapper with `new_unchecked`` and `get`. +macro_rules! nonmax_impls { + ( common, $nonmax: ident, $non_zero: ident, $primitive: ident ) => { + impl $nonmax { /// Gets non-max with the value zero (0) pub const ZERO: $nonmax = unsafe { Self::new_unchecked(0) }; @@ -343,12 +361,12 @@ macro_rules! nonmax { }; ( signed, $nonmax: ident, $non_zero: ident, $primitive: ident ) => { - nonmax!(common, $nonmax, $non_zero, $primitive); + nonmax_impls!(common, $nonmax, $non_zero, $primitive); // Nothing unique to signed versions (yet) }; ( unsigned, $nonmax: ident, $non_zero: ident, $primitive: ident ) => { - nonmax!(common, $nonmax, $non_zero, $primitive); + nonmax_impls!(common, $nonmax, $non_zero, $primitive); impl core::ops::BitAnd<$nonmax> for $primitive { type Output = $nonmax; @@ -381,21 +399,44 @@ macro_rules! nonmax { } } }; + + ( def, signed, $nonmax: ident, $non_zero: ident, $primitive: ident ) => { + nonmax!($nonmax, $non_zero, $primitive); + nonmax_impls!(signed, $nonmax, $non_zero, $primitive); + }; + + ( def, unsigned, $nonmax: ident, $non_zero: ident, $primitive: ident ) => { + nonmax!($nonmax, $non_zero, $primitive); + nonmax_impls!(unsigned, $nonmax, $non_zero, $primitive); + }; } -nonmax!(signed, NonMaxI8, NonZeroI8, i8); -nonmax!(signed, NonMaxI16, NonZeroI16, i16); -nonmax!(signed, NonMaxI32, NonZeroI32, i32); -nonmax!(signed, NonMaxI64, NonZeroI64, i64); -nonmax!(signed, NonMaxI128, NonZeroI128, i128); -nonmax!(signed, NonMaxIsize, NonZeroIsize, isize); - -nonmax!(unsigned, NonMaxU8, NonZeroU8, u8); -nonmax!(unsigned, NonMaxU16, NonZeroU16, u16); -nonmax!(unsigned, NonMaxU32, NonZeroU32, u32); -nonmax!(unsigned, NonMaxU64, NonZeroU64, u64); -nonmax!(unsigned, NonMaxU128, NonZeroU128, u128); -nonmax!(unsigned, NonMaxUsize, NonZeroUsize, usize); +enum_impl::nonmax!(NonMaxI8, i8, I8Repr); +enum_impl::nonmax!(NonMaxU8, u8, U8Repr); +#[cfg(feature = "enum_repr_16")] +enum_impl::nonmax!(NonMaxI16, i16, I16Repr); +#[cfg(feature = "enum_repr_16")] +enum_impl::nonmax!(NonMaxU16, u16, U16Repr); + +nonmax_impls!(signed, NonMaxI8, NonZeroI8, i8); +#[cfg(feature = "enum_repr_16")] +nonmax_impls!(signed, NonMaxI16, NonZeroI16, i16); +#[cfg(not(feature = "enum_repr_16"))] +nonmax_impls!(def, signed, NonMaxI16, NonZeroI16, i16); +nonmax_impls!(def, signed, NonMaxI32, NonZeroI32, i32); +nonmax_impls!(def, signed, NonMaxI64, NonZeroI64, i64); +nonmax_impls!(def, signed, NonMaxI128, NonZeroI128, i128); +nonmax_impls!(def, signed, NonMaxIsize, NonZeroIsize, isize); + +nonmax_impls!(unsigned, NonMaxU8, NonZeroU8, u8); +#[cfg(feature = "enum_repr_16")] +nonmax_impls!(unsigned, NonMaxU16, NonZeroU16, u16); +#[cfg(not(feature = "enum_repr_16"))] +nonmax_impls!(def, unsigned, NonMaxU16, NonZeroU16, u16); +nonmax_impls!(def, unsigned, NonMaxU32, NonZeroU32, u32); +nonmax_impls!(def, unsigned, NonMaxU64, NonZeroU64, u64); +nonmax_impls!(def, unsigned, NonMaxU128, NonZeroU128, u128); +nonmax_impls!(def, unsigned, NonMaxUsize, NonZeroUsize, usize); // https://doc.rust-lang.org/1.47.0/src/core/convert/num.rs.html#383-407 macro_rules! impl_nonmax_from {