#[macro_export]
macro_rules! define_index_type {
(
$(#[$attrs:meta])*
$v:vis struct $type:ident = $raw:ident;
$($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 [(<$raw>::max_value() as usize)]
@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]
#[doc(hidden)]
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_u32_index_type! {
/// pub struct MyIndex;
/// }
/// ```
///
/// This creates a tuple struct `pub struct MyIndex(NonMaxU32)` with all the necessary trait
/// implementations. The type is backed by `NonMaxU32`, which has the same size as `u32` but
/// can represent values from `0` to `u32::MAX - 1`.
///
/// ## Custom Attributes and Proc Macros
///
/// You can add custom attributes, including proc macros, to the generated struct:
///
/// ```rust,ignore
/// oxc_index::define_nonmax_u32_index_type! {
/// /// Documentation for MyIndex
/// #[ast] // Proc macros work correctly
/// #[builder(default)]
/// #[allow(dead_code)]
/// pub struct MyIndex;
/// }
/// ```
///
/// **Attribute Ordering:** The macro applies attributes in this order:
/// 1. Built-in derives: `#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]`
/// 2. Your custom attributes (including proc macros)
///
/// This ordering ensures proc macros can properly process the struct with all standard derives already applied.
///
/// **Note:** The macro automatically provides:
/// - Derives: `Copy`, `Clone`, `PartialEq`, `Eq`, `Hash`, `PartialOrd`, `Ord`
/// - Manual implementations: `Debug`, `From<usize>`, `From<MyIndex> for usize`, and arithmetic ops
///
/// Do not add `#[derive(Debug)]` or other conflicting derives/impls as they are already provided.
#[cfg(feature = "nonmax")]
#[macro_export]
macro_rules! define_nonmax_u32_index_type {
(
$(#[$attrs:meta])*
$v:vis struct $type:ident;
$($CONFIG_NAME:ident = $value:expr_2021;)* $(;)?
) => {
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
$(#[$attrs])*
$v struct $type($crate::nonmax::NonMaxU32);
impl $type {
/// The maximum representable index value.
$v const MAX_INDEX: usize = (u32::MAX - 1) as usize;
/// Whether this index type performs bounds checking.
$v const CHECKS_MAX_INDEX: bool = true;
/// Create a new index from a `usize` value.
///
/// # Panics
/// Panics if `value > MAX_INDEX`.
#[inline(always)]
$v const fn new(value: usize) -> Self {
Self::from_usize(value)
}
/// Create an index from a raw `NonMaxU32` value.
#[inline(always)]
$v const fn from_raw(value: $crate::nonmax::NonMaxU32) -> Self {
Self(value)
}
/// Convert an index from another index type.
#[inline(always)]
$v fn from_foreign<F: $crate::Idx>(value: F) -> Self {
Self::from_usize(value.index())
}
/// Create an index from a `usize` without bounds checking.
///
/// # Safety
/// The caller must ensure `value <= MAX_INDEX`.
#[inline(always)]
$v const fn from_usize_unchecked(value: usize) -> Self {
Self(unsafe { $crate::nonmax::NonMaxU32::new_unchecked(value as u32) })
}
/// Create an index from a raw `u32` without bounds checking.
///
/// # Safety
/// The caller must ensure the value is not `u32::MAX`.
#[inline(always)]
$v const fn from_raw_unchecked(raw: u32) -> Self {
Self(unsafe { $crate::nonmax::NonMaxU32::new_unchecked(raw) })
}
/// Create an index from a `usize` with bounds checking.
///
/// # Panics
/// Panics if `value > MAX_INDEX`.
#[inline]
$v const fn from_usize(value: usize) -> Self {
Self::check_index(value);
match $crate::nonmax::NonMaxU32::new(value as u32) {
Some(raw) => Self(raw),
None => panic!("index_vec index overflow"),
}
}
/// Get the index value as a `usize`.
#[inline(always)]
$v const fn index(self) -> usize {
self.0.get() as usize
}
/// Get the raw `NonMaxU32` value.
#[inline(always)]
$v const fn raw(self) -> $crate::nonmax::NonMaxU32 {
self.0
}
#[doc(hidden)]
#[inline]
$v const fn check_index(v: usize) {
if Self::CHECKS_MAX_INDEX && (v > Self::MAX_INDEX) {
panic!("index_vec index overflow");
}
}
}
impl core::fmt::Debug for $type {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}({})", stringify!($type), self.index())
}
}
impl core::cmp::PartialOrd<usize> for $type {
#[inline]
fn partial_cmp(&self, other: &usize) -> Option<core::cmp::Ordering> {
self.index().partial_cmp(other)
}
}
impl core::cmp::PartialOrd<$type> for usize {
#[inline]
fn partial_cmp(&self, other: &$type) -> Option<core::cmp::Ordering> {
self.partial_cmp(&other.index())
}
}
impl PartialEq<usize> 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<usize> for $type {
type Output = Self;
#[inline]
fn add(self, other: usize) -> Self {
Self::new(self.index().wrapping_add(other))
}
}
impl core::ops::Sub<usize> for $type {
type Output = Self;
#[inline]
fn sub(self, other: usize) -> Self {
Self::new(self.index().wrapping_sub(other))
}
}
impl core::ops::AddAssign<usize> for $type {
#[inline]
fn add_assign(&mut self, other: usize) {
*self = *self + other
}
}
impl core::ops::SubAssign<usize> for $type {
#[inline]
fn sub_assign(&mut self, other: usize) {
*self = *self - other;
}
}
impl core::ops::Rem<usize> 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<usize> 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)]
macro_rules! __internal_maybe_index_impl_serde {
($type:ident) => {
impl $crate::serde::ser::Serialize for $type {
fn serialize<S: $crate::serde::ser::Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
self.index().serialize(serializer)
}
}
impl<'de> $crate::serde::de::Deserialize<'de> for $type {
fn deserialize<D: $crate::serde::de::Deserializer<'de>>(
deserializer: D,
) -> Result<Self, D::Error> {
usize::deserialize(deserializer).map(Self::from_usize)
}
}
};
}
#[cfg(not(feature = "serde"))]
#[macro_export]
#[doc(hidden)]
macro_rules! __internal_maybe_index_impl_serde {
($type:ident) => {};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __define_index_type_inner {
// DISABLE_MAX_INDEX_CHECK
(
@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:ty)]
@debug_fmt [$dbg:expr_2021]
@max [$max:expr_2021]
@no_check_max [$_old_no_check_max:expr_2021]
) => {
$crate::__define_index_type_inner!{
@configs [$(($CONFIG_NAME; $value))*]
@attrs [$(#[$attrs])*]
@derives [$(#[$derive])*]
@decl [$v struct $type ($raw)]
@debug_fmt [$dbg]
@max [$max]
@no_check_max [$no_check_max]
}
};
// MAX_INDEX
(
@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:ty)]
@debug_fmt [$dbg:expr_2021]
@max [$max:expr_2021]
@no_check_max [$cm:expr_2021]
) => {
$crate::__define_index_type_inner!{
@configs [$(($CONFIG_NAME; $value))*]
@attrs [$(#[$attrs])*]
@derives [$(#[$derive])*]
@decl [$v struct $type ($raw)]
@debug_fmt [$dbg]
@max [$new_max]
@no_check_max [$cm]
}
};
// DEFAULT
(
@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:ty)]
@debug_fmt [$dbg:expr_2021]
@max [$max:expr_2021]
@no_check_max [$no_check_max:expr_2021]
) => {
$crate::__define_index_type_inner!{
@configs [$(($CONFIG_NAME; $value))*]
@attrs [$(#[$attrs])*]
@derives [$(#[$derive])*]
@decl [$v struct $type ($raw)]
@debug_fmt [$dbg]
@max [$max]
@no_check_max [$no_check_max]
}
impl Default for $type {
#[inline]
fn default() -> Self {
$default_expr
}
}
};
// DEBUG_FORMAT
(
@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:ty)]
@debug_fmt [$old_dbg:expr_2021]
@max [$max:expr_2021]
@no_check_max [$no_check_max:expr_2021]
) => {
$crate::__define_index_type_inner!{
@configs [$(($CONFIG_NAME; $value))*]
@attrs [$(#[$attrs])*]
@derives [$(#[$derive])*]
@decl [$v struct $type ($raw)]
@debug_fmt [$dbg]
@max [$max]
@no_check_max [$no_check_max]
}
};
// DISPLAY_FORMAT
(
@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:ty)]
@debug_fmt [$dbg:expr_2021]
@max [$max:expr_2021]
@no_check_max [$no_check_max:expr_2021]
) => {
$crate::__define_index_type_inner!{
@configs [$(($CONFIG_NAME; $value))*]
@attrs [$(#[$attrs])*]
@derives [$(#[$derive])*]
@decl [$v struct $type ($raw)]
@debug_fmt [$dbg]
@max [$max]
@no_check_max [$no_check_max]
}
impl core::fmt::Display for $type {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, $format, self.index())
}
}
};
// IMPL_RAW_CONVERSIONS
(
@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:ty)]
@debug_fmt [$dbg:expr_2021]
@max [$max:expr_2021]
@no_check_max [$no_check_max:expr_2021]
) => {
$crate::__define_index_type_inner!{
@configs [$(($CONFIG_NAME; $value))*]
@attrs [$(#[$attrs])*]
@derives [$(#[$derive])*]
@decl [$v struct $type ($raw)]
@debug_fmt [$dbg]
@max [$max]
@no_check_max [$no_check_max]
}
// Ensure they passed in true. This is... cludgey.
const _: [(); 1] = [(); $val as usize];
impl From<$type> for $raw {
#[inline]
fn from(v: $type) -> $raw {
v.raw()
}
}
impl From<$raw> for $type {
#[inline]
fn from(value: $raw) -> Self {
Self::from_raw(value)
}
}
};
// Try to make rust emit a decent error message...
(
@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:ty)]
@debug_fmt [$dbg:expr_2021]
@max [$max:expr_2021]
@no_check_max [$no_check_max:expr_2021]
) => {
$crate::unknown_define_index_type_option!($other);
};
// finish
(
@configs []
@attrs [$(#[$attrs:meta])*]
@derives [$(#[$derive:meta])*]
@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]
) => {
$(#[$derive])*
$(#[$attrs])*
$v struct $type($raw);
impl $type {
/// If `Self::CHECKS_MAX_INDEX` is true, we'll assert if trying to
/// produce a value larger than this in any of the ctors that don't
/// have `unchecked` in their name.
$v const MAX_INDEX: usize = $max;
/// Does this index type assert if asked to construct an index
/// larger than MAX_INDEX?
$v const CHECKS_MAX_INDEX: bool = !$no_check_max;
/// Construct this index type from a usize. Alias for `from_usize`.
#[inline(always)]
$v const fn new(value: usize) -> Self {
Self::from_usize(value)
}
/// Construct this index type from the wrapped integer type.
#[inline(always)]
$v const fn from_raw(value: $raw) -> Self {
Self::from_usize(value as usize)
}
/// Construct this index type from one in a different domain
#[inline(always)]
$v fn from_foreign<F: $crate::Idx>(value: F) -> Self {
Self::from_usize(value.index())
}
/// Construct from a usize without any checks.
#[expect(clippy::cast_possible_truncation)]
#[inline(always)]
$v const fn from_usize_unchecked(value: usize) -> Self {
Self(value as $raw)
}
/// Construct from the underlying type without any checks.
#[inline(always)]
$v const fn from_raw_unchecked(raw: $raw) -> Self {
Self(raw)
}
/// Construct this index type from a usize.
#[expect(clippy::cast_possible_truncation)]
#[inline]
$v const fn from_usize(value: usize) -> Self {
Self::check_index(value as usize);
Self(value as $raw)
}
/// Get the wrapped index as a usize.
#[inline(always)]
$v const fn index(self) -> usize {
self.0 as usize
}
/// Get the wrapped index.
#[inline(always)]
$v const fn raw(self) -> $raw {
self.0
}
#[doc(hidden)]
#[inline]
$v const fn check_index(v: usize) {
if Self::CHECKS_MAX_INDEX && (v > Self::MAX_INDEX) {
panic!("index_vec index overflow");
}
}
}
impl core::fmt::Debug for $type {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, $dbg, self.index())
}
}
impl core::cmp::PartialOrd<usize> for $type {
#[inline]
fn partial_cmp(&self, other: &usize) -> Option<core::cmp::Ordering> {
self.index().partial_cmp(other)
}
}
impl core::cmp::PartialOrd<$type> for usize {
#[inline]
fn partial_cmp(&self, other: &$type) -> Option<core::cmp::Ordering> {
self.partial_cmp(&other.index())
}
}
impl PartialEq<usize> 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<usize> for $type {
type Output = Self;
#[inline]
fn add(self, other: usize) -> Self {
// use wrapping add so that it's up to the index type whether or
// not to check -- e.g. if checks are disabled, they're disabled
// on both debug and release.
Self::new(self.index().wrapping_add(other))
}
}
impl core::ops::Sub<usize> for $type {
type Output = Self;
#[inline]
fn sub(self, other: usize) -> Self {
// use wrapping sub so that it's up to the index type whether or
// not to check -- e.g. if checks are disabled, they're disabled
// on both debug and release.
Self::new(self.index().wrapping_sub(other))
}
}
impl core::ops::AddAssign<usize> for $type {
#[inline]
fn add_assign(&mut self, other: usize) {
*self = *self + other
}
}
impl core::ops::SubAssign<usize> for $type {
#[inline]
fn sub_assign(&mut self, other: usize) {
*self = *self - other;
}
}
impl core::ops::Rem<usize> 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<usize> for $type {
#[inline]
fn from(value: usize) -> Self {
$type::from_usize(value)
}
}
$crate::__internal_maybe_index_impl_serde!($type);
};
}