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..f663a21 --- /dev/null +++ b/build.rs @@ -0,0 +1,127 @@ +#[derive(Clone, Copy)] +enum BitInfo { + Unsigned { + prim_max: u64, + }, + Signed { + signed_max: i64, + signed_min: i64, + prim_max: i64, + }, +} + +impl BitInfo { + fn prim_max(&self) -> i64 { + match self { + Self::Unsigned { prim_max } => *prim_max as i64, + Self::Signed { prim_max, .. } => *prim_max, + } + } +} + +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( + generated_file: &mut impl std::fmt::Write, + repr_name: &str, + bit_info: BitInfo, +) { + write!( + generated_file, + "#[derive(Clone, Copy, PartialEq, Eq, Hash)] + #[allow(dead_code)] + pub(crate) enum {repr_name} {{", + ) + .unwrap(); + + match bit_info { + BitInfo::Unsigned { prim_max } => { + for i in 0..prim_max { + write!(generated_file, "V{i}={i},").unwrap() + } + } + BitInfo::Signed { + signed_max, + signed_min, + prim_max, + } => { + for i in 0..signed_max { + write!(generated_file, "V{i}={i},").unwrap(); + } + + for (i, v) in (signed_max..prim_max).zip(signed_min..0) { + write!(generated_file, "MV{i}={v},").unwrap(); + } + } + } + + write!(generated_file, "}}").unwrap(); +} + +fn generate_impl(generated_file: &mut impl std::fmt::Write, repr_name: &str, ty_name: &str) { + write!( + generated_file, + "impl {repr_name} {{ + pub(crate) const fn new(value: {ty_name}) -> Option {{ + unsafe {{ std::mem::transmute(value) }} + }} + }}" + ) + .unwrap() +} + +fn main() { + let out_dir = std::env::var("OUT_DIR").unwrap(); + + for file in FILES { + let mut generated_file = String::with_capacity(file.bit_info.prim_max() as usize); + + 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); + + std::fs::write(format!("{out_dir}/{}", file.name), generated_file).unwrap(); + } +} 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 cb2bbb5..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(()); @@ -401,14 +411,27 @@ macro_rules! nonmax_impls { }; } -nonmax_impls!(def, signed, NonMaxI8, NonZeroI8, i8); +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!(def, unsigned, NonMaxU8, NonZeroU8, u8); +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);