diff --git a/Cargo.lock b/Cargo.lock index f62091b..35481f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,10 +33,17 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + [[package]] name = "oxc_index" version = "3.1.0" dependencies = [ + "nonmax", "rayon", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index cd1a460..b5291b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,3 +53,4 @@ doctest = false [dependencies] rayon = { version = "1", optional = true } serde = { version = "1", optional = true } +nonmax = { version = "0.5", optional = true } diff --git a/src/lib.rs b/src/lib.rs index a888c6e..1f62d15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,6 +112,18 @@ //! //! Yes, but only if you turn on the `serialize` feature. //! +//! #### Does it support NonMaxU32? +//! +//! Yes! With the `nonmax` feature enabled, you can use the `define_nonmax_index_type!` macro +//! to create index types backed by `NonMaxU32` from the `nonmax` crate. This is useful for +//! memory-efficient `Option` representations. +//! +//! ```rust,ignore +//! oxc_index::define_nonmax_index_type! { +//! pub struct MyIndex; +//! } +//! ``` +//! //! #### What features are planned? //! //! Planned is a bit strong but here are the things I would find useful. @@ -125,7 +137,6 @@ //! - Allow use of indices for string types (the primary benefit here would //! probably be the ability to e.g. use u32 without too much pain rather than //! mixing up indices from different strings -- but you never know!) -//! - Allow index types such as NonZeroU32 and such, if it can be done sanely. //! - ... //! #![allow(clippy::inline_always)] diff --git a/src/macros.rs b/src/macros.rs index 42aebc8..9b2eb25 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -154,7 +154,7 @@ /// ``` #[macro_export] macro_rules! define_index_type { - // public api + // public api for primitive types (u8, u16, u32, usize, etc.) ( $(#[$attrs:meta])* $v:vis struct $type:ident = $raw:ident; @@ -170,6 +170,22 @@ macro_rules! define_index_type { @no_check_max [false] } }; + // public api for complex types (NonMaxU32, etc.) - requires explicit MAX_INDEX + ( + $(#[$attrs:meta])* + $v:vis struct $type:ident = $raw:ty; + $($CONFIG_NAME:ident = $value:expr_2021;)+ $(;)? + ) => { + $crate::__define_index_type_inner!{ + @configs [$(($CONFIG_NAME; $value))*] + @attrs [$(#[$attrs])*] + @derives [#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]] + @decl [$v struct $type ($raw)] + @debug_fmt ["{}"] + @max [(usize::MAX)] + @no_check_max [false] + } + }; } #[macro_export] @@ -178,6 +194,239 @@ macro_rules! unknown_define_index_type_option { () => {}; } +/// Generate the boilerplate for a newtyped index struct using NonMaxU32. +/// This is a specialized version of `define_index_type!` for use with `NonMaxU32` from the `nonmax` crate. +/// +/// ## Usage +/// +/// ```rust,ignore +/// oxc_index::define_nonmax_index_type! { +/// pub struct MyIndex; +/// } +/// ``` +/// +/// This creates an index type backed by `NonMaxU32`, which has the same size as `u32` but +/// can represent values from `0` to `u32::MAX - 1`. +#[cfg(feature = "nonmax")] +#[macro_export] +macro_rules! define_nonmax_index_type { + ( + $(#[$attrs:meta])* + $v:vis struct $type:ident; + $($CONFIG_NAME:ident = $value:expr_2021;)* $(;)? + ) => { + $(#[$attrs])* + #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] + #[repr(transparent)] + $v struct $type { _raw: nonmax::NonMaxU32 } + + impl $type { + $v const MAX_INDEX: usize = (u32::MAX - 1) as usize; + $v const CHECKS_MAX_INDEX: bool = true; + + #[inline(always)] + $v fn new(value: usize) -> Self { + Self::from_usize(value) + } + + #[inline(always)] + $v fn from_raw(value: nonmax::NonMaxU32) -> Self { + Self { _raw: value } + } + + #[inline(always)] + $v fn from_foreign(value: F) -> Self { + Self::from_usize(value.index()) + } + + #[inline(always)] + $v const fn from_usize_unchecked(value: usize) -> Self { + // SAFETY: Caller must ensure value is valid + Self { _raw: unsafe { nonmax::NonMaxU32::new_unchecked(value as u32) } } + } + + #[inline(always)] + $v const fn from_raw_unchecked(raw: u32) -> Self { + // SAFETY: Caller must ensure value is valid + Self { _raw: unsafe { nonmax::NonMaxU32::new_unchecked(raw) } } + } + + #[inline] + $v fn from_usize(value: usize) -> Self { + Self::check_index(value); + nonmax::NonMaxU32::new(value as u32) + .map(|raw| Self { _raw: raw }) + .unwrap_or_else(|| panic!("index_vec index overflow: {} is outside the range [0, {})", value, Self::MAX_INDEX)) + } + + #[inline(always)] + $v const fn index(self) -> usize { + self._raw.get() as usize + } + + #[inline(always)] + $v const fn raw(self) -> nonmax::NonMaxU32 { + self._raw + } + + #[inline] + $v fn check_index(v: usize) { + if Self::CHECKS_MAX_INDEX && (v > Self::MAX_INDEX) { + $crate::__max_check_fail(v, Self::MAX_INDEX); + } + } + } + + impl core::fmt::Debug for $type { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.index()) + } + } + + impl core::cmp::PartialOrd for $type { + #[inline] + fn partial_cmp(&self, other: &usize) -> Option { + self.index().partial_cmp(other) + } + } + + impl core::cmp::PartialOrd<$type> for usize { + #[inline] + fn partial_cmp(&self, other: &$type) -> Option { + self.partial_cmp(&other.index()) + } + } + + impl PartialEq for $type { + #[inline] + fn eq(&self, other: &usize) -> bool { + self.index() == *other + } + } + + impl PartialEq<$type> for usize { + #[inline] + fn eq(&self, other: &$type) -> bool { + *self == other.index() + } + } + + impl core::ops::Add for $type { + type Output = Self; + #[inline] + fn add(self, other: usize) -> Self { + Self::new(self.index().wrapping_add(other)) + } + } + + impl core::ops::Sub for $type { + type Output = Self; + #[inline] + fn sub(self, other: usize) -> Self { + Self::new(self.index().wrapping_sub(other)) + } + } + + impl core::ops::AddAssign for $type { + #[inline] + fn add_assign(&mut self, other: usize) { + *self = *self + other + } + } + + impl core::ops::SubAssign for $type { + #[inline] + fn sub_assign(&mut self, other: usize) { + *self = *self - other; + } + } + + impl core::ops::Rem for $type { + type Output = Self; + #[inline] + fn rem(self, other: usize) -> Self { + Self::new(self.index() % other) + } + } + + impl core::ops::Add<$type> for usize { + type Output = $type; + #[inline] + fn add(self, other: $type) -> $type { + other + self + } + } + + impl core::ops::Sub<$type> for usize { + type Output = $type; + #[inline] + fn sub(self, other: $type) -> $type { + $type::new(self.wrapping_sub(other.index())) + } + } + + impl core::ops::Add for $type { + type Output = $type; + #[inline] + fn add(self, other: $type) -> $type { + $type::new(other.index() + self.index()) + } + } + + impl core::ops::Sub for $type { + type Output = $type; + #[inline] + fn sub(self, other: $type) -> $type { + $type::new(self.index().wrapping_sub(other.index())) + } + } + + impl core::ops::AddAssign for $type { + #[inline] + fn add_assign(&mut self, other: $type) { + *self = *self + other + } + } + + impl core::ops::SubAssign for $type { + #[inline] + fn sub_assign(&mut self, other: $type) { + *self = *self - other; + } + } + + impl $crate::Idx for $type { + const MAX: usize = Self::MAX_INDEX; + + #[inline] + unsafe fn from_usize_unchecked(idx: usize) -> Self { + Self::from_usize_unchecked(idx) + } + + #[inline] + fn index(self) -> usize { + usize::from(self) + } + } + + impl From<$type> for usize { + #[inline] + fn from(v: $type) -> usize { + v.index() + } + } + + impl From for $type { + #[inline] + fn from(value: usize) -> Self { + $type::from_usize(value) + } + } + + $crate::__internal_maybe_index_impl_serde!($type); + }; +} + #[cfg(feature = "serde")] #[macro_export] #[doc(hidden)] @@ -217,7 +466,7 @@ macro_rules! __define_index_type_inner { @configs [(DISABLE_MAX_INDEX_CHECK; $no_check_max:expr_2021) $(($CONFIG_NAME:ident; $value:expr_2021))*] @attrs [$(#[$attrs:meta])*] @derives [$(#[$derive:meta])*] - @decl [$v:vis struct $type:ident ($raw:ident)] + @decl [$v:vis struct $type:ident ($raw:ty)] @debug_fmt [$dbg:expr_2021] @max [$max:expr_2021] @no_check_max [$_old_no_check_max:expr_2021] @@ -238,7 +487,7 @@ macro_rules! __define_index_type_inner { @configs [(MAX_INDEX; $new_max:expr_2021) $(($CONFIG_NAME:ident; $value:expr_2021))*] @attrs [$(#[$attrs:meta])*] @derives [$(#[$derive:meta])*] - @decl [$v:vis struct $type:ident ($raw:ident)] + @decl [$v:vis struct $type:ident ($raw:ty)] @debug_fmt [$dbg:expr_2021] @max [$max:expr_2021] @no_check_max [$cm:expr_2021] @@ -259,7 +508,7 @@ macro_rules! __define_index_type_inner { @configs [(DEFAULT; $default_expr:expr_2021) $(($CONFIG_NAME:ident; $value:expr_2021))*] @attrs [$(#[$attrs:meta])*] @derives [$(#[$derive:meta])*] - @decl [$v:vis struct $type:ident ($raw:ident)] + @decl [$v:vis struct $type:ident ($raw:ty)] @debug_fmt [$dbg:expr_2021] @max [$max:expr_2021] @no_check_max [$no_check_max:expr_2021] @@ -286,7 +535,7 @@ macro_rules! __define_index_type_inner { @configs [(DEBUG_FORMAT; $dbg:expr_2021) $(($CONFIG_NAME:ident; $value:expr_2021))*] @attrs [$(#[$attrs:meta])*] @derives [$(#[$derive:meta])*] - @decl [$v:vis struct $type:ident ($raw:ident)] + @decl [$v:vis struct $type:ident ($raw:ty)] @debug_fmt [$old_dbg:expr_2021] @max [$max:expr_2021] @no_check_max [$no_check_max:expr_2021] @@ -307,7 +556,7 @@ macro_rules! __define_index_type_inner { @configs [(DISPLAY_FORMAT; $format:expr_2021) $(($CONFIG_NAME:ident; $value:expr_2021))*] @attrs [$(#[$attrs:meta])*] @derives [$(#[$derive:meta])*] - @decl [$v:vis struct $type:ident ($raw:ident)] + @decl [$v:vis struct $type:ident ($raw:ty)] @debug_fmt [$dbg:expr_2021] @max [$max:expr_2021] @no_check_max [$no_check_max:expr_2021] @@ -334,7 +583,7 @@ macro_rules! __define_index_type_inner { @configs [(IMPL_RAW_CONVERSIONS; $val:expr_2021) $(($CONFIG_NAME:ident; $value:expr_2021))*] @attrs [$(#[$attrs:meta])*] @derives [$(#[$derive:meta])*] - @decl [$v:vis struct $type:ident ($raw:ident)] + @decl [$v:vis struct $type:ident ($raw:ty)] @debug_fmt [$dbg:expr_2021] @max [$max:expr_2021] @no_check_max [$no_check_max:expr_2021] @@ -370,7 +619,7 @@ macro_rules! __define_index_type_inner { @configs [($other:ident; $format:expr_2021) $(($CONFIG_NAME:ident; $value:expr_2021))*] @attrs [$(#[$attrs:meta])*] @derives [$(#[$derive:meta])*] - @decl [$v:vis struct $type:ident ($raw:ident)] + @decl [$v:vis struct $type:ident ($raw:ty)] @debug_fmt [$dbg:expr_2021] @max [$max:expr_2021] @no_check_max [$no_check_max:expr_2021] @@ -382,7 +631,7 @@ macro_rules! __define_index_type_inner { @configs [] @attrs [$(#[$attrs:meta])*] @derives [$(#[$derive:meta])*] - @decl [$v:vis struct $type:ident ($raw:ident)] + @decl [$v:vis struct $type:ident ($raw:ty)] @debug_fmt [$dbg:expr_2021] @max [$max:expr_2021] @no_check_max [$no_check_max:expr_2021] @@ -461,8 +710,6 @@ macro_rules! __define_index_type_inner { $crate::__max_check_fail(v, Self::MAX_INDEX); } } - - const _ENSURE_RAW_IS_UNSIGNED: [(); 0] = [(); <$raw>::MIN as usize]; } impl core::fmt::Debug for $type { diff --git a/tests/test.rs b/tests/test.rs index a12af73..6817933 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -64,6 +64,11 @@ oxc_index::define_index_type! { MAX_INDEX = 0x7f; } +#[cfg(feature = "nonmax")] +oxc_index::define_nonmax_index_type! { + pub struct IdxNonMax; +} + #[test] fn test_idx_default_max() { assert_eq!(Idx32::MAX_INDEX, u32::MAX as usize); @@ -549,3 +554,45 @@ fn test_splits() { assert!(v.split_first_mut().is_none()); assert!(v.split_last_mut().is_none()); } + +#[test] +#[cfg(feature = "nonmax")] +fn test_nonmax() { + // Test basic construction + let idx = IdxNonMax::new(42); + assert_eq!(idx.index(), 42); + + // Test from_raw and raw() + let raw = nonmax::NonMaxU32::new(100).unwrap(); + let idx = IdxNonMax::from_raw(raw); + assert_eq!(idx.index(), 100); + assert_eq!(idx.raw().get(), 100); + + // Test arithmetic + let idx1 = IdxNonMax::new(10); + let idx2 = idx1 + 5; + assert_eq!(idx2.index(), 15); + + let idx3 = idx2 - 3; + assert_eq!(idx3.index(), 12); + + // Test with IndexVec + let mut vec: IndexVec = index_vec!["a", "b", "c"]; + assert_eq!(vec.len(), 3); + + let idx = vec.push("d"); + assert_eq!(vec[idx], "d"); + assert_eq!(idx.index(), 3); + + // Test max index + assert_eq!(IdxNonMax::MAX_INDEX, (u32::MAX - 1) as usize); + assert!(IdxNonMax::CHECKS_MAX_INDEX); +} + +#[test] +#[cfg(feature = "nonmax")] +#[should_panic] +fn test_nonmax_overflow() { + // This should panic because u32::MAX is not valid for NonMaxU32 + let _ = IdxNonMax::new(u32::MAX as usize); +}