From 26c74b328866b554e32d67fd98b0ecd274e8fded Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Mon, 6 May 2024 12:25:56 +0200 Subject: [PATCH] Add `ids` and `derive` crates --- Cargo.lock | 118 +++++ Cargo.toml | 3 +- derive/Cargo.toml | 14 + derive/src/id.rs | 280 ++++++++++++ derive/src/lib.rs | 41 ++ ids/Cargo.toml | 8 + ids/src/id.rs | 170 +++++++ ids/src/id/id_types.rs | 839 ++++++++++++++++++++++++++++++++++ ids/src/id/primitive_impls.rs | 129 ++++++ ids/src/id/u8_range_types.rs | 640 ++++++++++++++++++++++++++ ids/src/lib.rs | 21 + ids/tests/test_id.rs | 399 ++++++++++++++++ rustfmt.toml | 1 + 13 files changed, 2662 insertions(+), 1 deletion(-) create mode 100644 Cargo.lock create mode 100644 derive/Cargo.toml create mode 100644 derive/src/id.rs create mode 100644 derive/src/lib.rs create mode 100644 ids/Cargo.toml create mode 100644 ids/src/id.rs create mode 100644 ids/src/id/id_types.rs create mode 100644 ids/src/id/primitive_impls.rs create mode 100644 ids/src/id/u8_range_types.rs create mode 100644 ids/src/lib.rs create mode 100644 ids/tests/test_id.rs create mode 100644 rustfmt.toml diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..aab554b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,118 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "imctk-derive" +version = "0.1.0" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "imctk-ids" +version = "0.1.0" +dependencies = [ + "imctk-derive", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index da06ed1..87cad11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "2" +members = ["ids", "derive"] [profile.release] -debug = true +debug = "limited" diff --git a/derive/Cargo.toml b/derive/Cargo.toml new file mode 100644 index 0000000..93ba046 --- /dev/null +++ b/derive/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "imctk-derive" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +proc-macro = true + +[dependencies] +proc-macro-crate = "3.1.0" +proc-macro2 = "1.0.81" +quote = { version = "1.0.36" } +syn = { version = "2.0.60" } diff --git a/derive/src/id.rs b/derive/src/id.rs new file mode 100644 index 0000000..435c334 --- /dev/null +++ b/derive/src/id.rs @@ -0,0 +1,280 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::DeriveInput; + +use crate::resolve_crate; + +pub fn real_derive_id(input: DeriveInput) -> syn::Result { + #![allow(non_snake_case)] + + let DeriveInput { + attrs, + ident, + generics, + data, + .. + } = input; + + let mut repr_transparent_found = false; + + for attr in attrs { + if attr.meta.path().is_ident("repr") { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("transparent") { + repr_transparent_found = true; + } else { + bail!( + &attr, + "deriving `Id` is only supported for `#[repr(transparent)]` structs" + ); + } + Ok(()) + })?; + } + } + + if !repr_transparent_found { + bail!(&ident, "`derive(Id)` requries `#[repr(transparent)]`"); + } + + let data_struct = match data { + syn::Data::Struct(data_struct) => data_struct, + syn::Data::Enum(data_enum) => { + bail!( + data_enum.enum_token, + "can only derive `Id` for `struct` types" + ); + } + syn::Data::Union(data_union) => { + bail!( + data_union.union_token, + "can only derive `Id` for `struct` types" + ); + } + }; + + // We're extra careful about shadowing of prelude items, as we're emitting code using `unsafe` + let imctk_ids = resolve_crate("imctk-ids"); + let usize = quote![::core::primitive::usize]; + let bool = quote![::core::primitive::bool]; + + let Id = quote![#imctk_ids::Id]; + + let Clone = quote![::core::prelude::rust_2021::Clone]; + let Copy = quote![::core::prelude::rust_2021::Copy]; + let Eq = quote![::core::prelude::rust_2021::Eq]; + let Hash = quote![::core::hash::Hash]; + let Hasher = quote![::core::hash::Hasher]; + let Option = quote![::core::prelude::rust_2021::Option]; + let Sized = quote![::core::prelude::rust_2021::Sized]; + let Ord = quote![::core::prelude::rust_2021::Ord]; + let PartialEq = quote![::core::prelude::rust_2021::PartialEq]; + let PartialOrd = quote![::core::prelude::rust_2021::PartialOrd]; + let Ordering = quote![::core::cmp::Ordering]; + let PhantomData = quote![::core::marker::PhantomData]; + let Send = quote![::core::marker::Send]; + let Sync = quote![::core::marker::Sync]; + + let mut fields = data_struct.fields.iter(); + + let Some(field_def) = fields.next() else { + bail!( + data_struct.fields, + "can only derive `Id` for structs where the first field's type implements `Id` and any following fields are `PhantomData`" + ); + }; + + let mut extra_init = quote![]; + + for field_def in fields { + if let Some(ident) = &field_def.ident { + extra_init.extend(quote![, #ident: #PhantomData]); + } else { + extra_init.extend(quote![, #PhantomData]); + } + } + + let field = if let Some(ident) = &field_def.ident { + quote![#ident] + } else { + quote![0] + }; + + let construct = |inner: TokenStream| { + if let Some(ident) = &field_def.ident { + quote![Self { #ident: #inner #extra_init}] + } else { + quote![Self(#inner #extra_init)] + } + }; + + let target_ident = ident; + let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); + + let target_type = quote![#target_ident #type_generics]; + + let inner = &field_def.ty; + + let min_def = construct(quote![<#inner as #Id>::MIN]); + let max_def = construct(quote![<#inner as #Id>::MAX]); + + let from_index_unchecked_body = construct(quote! { + // SAFETY: forwarding to an existing implementation + unsafe { <#inner as #Id>::from_index_unchecked(index) } + }); + + let from_index_body = construct(quote! { + <#inner as #Id>::from_index(index) + }); + + let try_from_index_body = construct(quote! { + <#inner as #Id>::try_from_index(index)? + }); + + let max_body = construct(quote! { + #Ord::max(self.#field, other.#field) + }); + + let min_body = construct(quote! { + #Ord::min(self.#field, other.#field) + }); + + let clamp_body = construct(quote! { + #Ord::clamp(self.#field, min.#field, max.#field) + }); + + Ok(quote! { + // SAFETY: forwarding to an existing implementation + unsafe impl #impl_generics #Id for #target_type #where_clause { + type Base = <#inner as #Id>::Base; + type Generic = <#inner as #Id>::Generic; + + const MAX_INDEX: #usize = <#inner as #Id>::MAX_INDEX; + const MIN: Self = #min_def; + const MAX: Self = #max_def; + + #[inline(always)] + unsafe fn from_index_unchecked(index: #usize) -> Self { + #from_index_unchecked_body + } + + #[inline(always)] + fn from_index(index: #usize) -> Self { + #from_index_body + } + + #[inline(always)] + fn try_from_index(index: #usize) -> #Option { + Some(#try_from_index_body) + } + + #[inline(always)] + fn index(self) -> #usize { + self.#field.index() + } + } + + // SAFETY: we ensure that the newtype wrapped type is `Id` and thus `Send` + unsafe impl #impl_generics #Send for #target_type #where_clause { + } + + // SAFETY: we ensure that the newtype wrapped type is `Id` and thus `Sync` + unsafe impl #impl_generics #Sync for #target_type #where_clause { + } + + impl #impl_generics #Copy for #target_type #where_clause { + } + + impl #impl_generics #Clone for #target_type #where_clause { + #[inline(always)] + fn clone(&self) -> Self { + *self + } + } + + impl #impl_generics #PartialEq for #target_type #where_clause { + #[inline(always)] + fn eq(&self, other: &Self) -> #bool { + #PartialEq::eq(&self.#field, &other.#field) + } + } + + impl #impl_generics #Eq for #target_type #where_clause {} + + impl #impl_generics #Hash for #target_type #where_clause { + #[inline(always)] + fn hash(&self, state: &mut H) { + #Hash::hash(&self.#field, state) + } + + #[inline(always)] + fn hash_slice(data: &[Self], state: &mut H) + where + Self: #Sized, + { + // SAFETY: repr(transparent) cast + let data = unsafe {&*(data as *const [Self] as *const [#inner])}; + <#inner as #Hash>::hash_slice(data, state) + } + } + + impl #impl_generics #PartialOrd for #target_type #where_clause { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> #Option<#Ordering> { + Some(#Ord::cmp(self, other)) + } + + #[inline(always)] + fn lt(&self, other: &Self) -> bool { + #PartialOrd::lt(&self.#field, &other.#field) + } + + #[inline(always)] + fn le(&self, other: &Self) -> bool { + #PartialOrd::le(&self.#field, &other.#field) + } + + #[inline(always)] + fn gt(&self, other: &Self) -> bool { + #PartialOrd::gt(&self.#field, &other.#field) + } + + #[inline(always)] + fn ge(&self, other: &Self) -> bool { + #PartialOrd::ge(&self.#field, &other.#field) + } + } + + impl #impl_generics #Ord for #target_type #where_clause { + #[inline(always)] + fn cmp(&self, other: &Self) -> #Ordering { + #Ord::cmp(&self.#field, &other.#field) + } + + #[inline(always)] + fn max(self, other: Self) -> Self + where + Self: #Sized, + { + #max_body + } + + #[inline(always)] + fn min(self, other: Self) -> Self + where + Self: #Sized, + { + #min_body + } + + #[inline(always)] + fn clamp(self, min: Self, max: Self) -> Self + where + Self: #Sized, + Self: #PartialOrd, + { + #clamp_body + } + } + }) +} diff --git a/derive/src/lib.rs b/derive/src/lib.rs new file mode 100644 index 0000000..30d407c --- /dev/null +++ b/derive/src/lib.rs @@ -0,0 +1,41 @@ +use proc_macro::TokenStream as MacroTokenStream; +use proc_macro2::{Span, TokenStream}; +use proc_macro_crate::FoundCrate; +use quote::quote; +use syn::{parse_macro_input, DeriveInput, Ident}; + +macro_rules! bail { + ($span:expr, $($tokens:tt)*) => { + { + return Err(::syn::Error::new_spanned( + $span, + format!($($tokens)*) + )); + } + }; +} + +fn resolve_crate(name: &str) -> TokenStream { + match proc_macro_crate::crate_name(name) { + Ok(FoundCrate::Name(name)) => { + let ident = Ident::new_raw(&name, Span::call_site()); + quote!(:: #ident) + } + Ok(FoundCrate::Itself) => { + quote!(crate) + } + _ => { + let ident = Ident::new_raw(name, Span::call_site()); + quote!(:: #ident) + } + } +} + +mod id; + +#[proc_macro_derive(Id)] +pub fn derive_id(input: MacroTokenStream) -> MacroTokenStream { + id::real_derive_id(parse_macro_input!(input as DeriveInput)) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} diff --git a/ids/Cargo.toml b/ids/Cargo.toml new file mode 100644 index 0000000..04cedba --- /dev/null +++ b/ids/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "imctk-ids" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +imctk-derive = { path = "../derive" } diff --git a/ids/src/id.rs b/ids/src/id.rs new file mode 100644 index 0000000..9bc84b7 --- /dev/null +++ b/ids/src/id.rs @@ -0,0 +1,170 @@ +use core::{fmt::Debug, hash::Hash}; + +mod id_types; +mod primitive_impls; +mod u8_range_types; + +/// Types that represent integer ids +/// +/// A type of this trait represents an `usize` index value in the range `0..=Self::MAX_INDEX`, with +/// the specific representation used is up to the implementing type. Implementing types can have a +/// smaller size or alignment compared to `usize`, and can leave space for rustc's niche-value +/// optimization. +/// +/// # Safety +/// This trait comes with several safety critical requirements for implementing types: +/// +/// * The [`Clone`] implementation must return a [`Copy`]. +/// * The [`PartialEq`], [`Eq`], [`PartialOrd`] and [`Ord`] implementation must behave as if this +/// type was a `struct` containing [`index: usize`][`Self::index()`] as only field and a +/// `#[derive(PartialEq, Eq, PartialOrd, Ord)]` attribute. +/// * Each valid index must have a unique representation , i.e. it must be possible to check two ids +/// for equality by comparing their raw bytes. This only applies to equality, and does not extend +/// to [`PartialOrd`] or [`Ord`]. +/// * The [`Hash`] implementation must be deterministic and only depend on the represented index. +/// * The associated [`Self::Base`] type must be an [`Id`] with the same representation and index range (it can be `Self`). +/// * The associated [`Self::Generic`] type must be [`GenericId<{Self::MAX_INDEX}>`][`GenericId`]. +/// * All implemented trait items must confirm to the their documented behavior. +/// +/// Users of this trait may depend on implementing types following these requirements for upholding +/// their own safety invariants. +pub unsafe trait Id: Copy + Ord + Hash + Send + Sync + Debug { + /// An [`Id`] type that has the same representation and index range as this type. + /// + /// This is provided to enable writing generic code that during monomorphization is only + /// instantiated once per base type instead of once per id type. + /// + /// For types that are not newtype wrappers around an existing id type, this is usually + /// [`Self`]. + type Base: Id; + + /// The [`GenericId`] type, parametrized to have the same index range as this type. + /// + /// This is used for type checked conversions between ids that do not have to share the same + /// representation. + type Generic: Id; + + /// Returns the id with a given index, panicking when the index is invalid. + /// + /// This panics if and only if `index > Self::MAX_INDEX`. + #[inline(always)] + #[track_caller] + fn from_index(index: usize) -> Self { + assert!(index <= Self::MAX_INDEX); + // SAFETY: index asserted to be in range + unsafe { Self::from_index_unchecked(index) } + } + + /// Returns the index represented by this id. + fn index(self) -> usize; + + /// The largest index representable by this id type. + const MAX_INDEX: usize; + /// The id with index zero. + const MIN: Self; + /// The id with the largest representable index. + const MAX: Self; + + /// Returns the id with a given index, assuming a valid index. + /// + /// # Safety + /// This is only safe to call when `index <= Self::MAX_INDEX`, which is not checked by this + /// method. + /// + /// Implementations are encouraged to include a debug-only assertion for this requirement. + unsafe fn from_index_unchecked(index: usize) -> Self; + + /// Returns the id with a given index, if it is valid. + /// + /// This returns `None` if and only if `index > Self::MAX_INDEX`. + /// + /// Never panics. + #[inline(always)] + fn try_from_index(index: usize) -> Option { + if index <= Self::MAX_INDEX { + // SAFETY: index checked to be in range + Some(unsafe { Self::from_index_unchecked(index) }) + } else { + None + } + } + + /// Returns a [`Self::Base`] id of the same index. + /// + /// This cannot fail and will never panic. + #[inline(always)] + fn into_base_id(self) -> Self::Base { + // SAFETY: we require identical representations of Self and Self::Base + unsafe { std::mem::transmute_copy(&self) } + } + + /// Returns an id with the same index as a [`Self::Base`] id. + /// + /// This cannot fail and will never panic. + #[inline(always)] + fn from_base_id(base: Self::Base) -> Self { + // SAFETY: we require identical representations of Self and Self::Base + unsafe { std::mem::transmute_copy(&base) } + } + + /// Returns a value of a compatible [`Id`] type having the same index. + /// + /// Two id types are considered compatible when the have the same [`Self::Base`] type. + /// + /// As compatible types can represent the same range of indices, this cannot fail and will never + /// panic. + #[inline(always)] + fn cast_into_id>(self) -> T { + // SAFETY: safe due to the documented requirement on `Self::Generic` + unsafe { T::from_index_unchecked(self.index()) } + } + + /// Creates a value from a compatible [`Id`] type using the same index. + /// + /// Two id types are considered compatible when the have the same [`Self::Base`] type. + /// + /// As compatible types can represent the same range of indices, this cannot fail and will never + /// panic. + #[inline(always)] + fn cast_from_id>(from: T) -> Self { + // SAFETY: safe due to the documented requirement on `Self::Generic` + unsafe { Self::from_index_unchecked(from.index()) } + } +} + +/// A newtype wrapper around `usize` that limits the contained value to the `MAX_INDEX` generic +/// parameter. +/// +/// Used to implement conversion between different [`Id`] types that have the same index range. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct GenericId(usize); + +impl Debug for GenericId { + #[inline(always)] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self.0, f) + } +} + +/// SAFETY: The most direct implementation that upholds Id's safety requirements +unsafe impl Id for GenericId { + type Base = Self; + type Generic = Self; + + fn index(self) -> usize { + self.0 + } + + const MAX_INDEX: usize = MAX_INDEX; + + const MIN: Self = Self(0); + + const MAX: Self = Self(MAX_INDEX); + + unsafe fn from_index_unchecked(index: usize) -> Self { + debug_assert!(index <= Self::MAX_INDEX); + Self(index) + } +} + +pub use id_types::{Id16, Id32, Id64, Id8, IdSize}; diff --git a/ids/src/id/id_types.rs b/ids/src/id/id_types.rs new file mode 100644 index 0000000..a8b2ee5 --- /dev/null +++ b/ids/src/id/id_types.rs @@ -0,0 +1,839 @@ +mod id8 { + use crate::id::{u8_range_types::NonMaxHighNibbleU8, GenericId, Id}; + use core::{fmt, fmt::Debug, hash::Hash}; + + /// [`Id`] type representing indices in the range `0..0xf0`. + #[allow(dead_code)] // Only constructed via transmutation and/or pointer casts + #[derive(Clone, Copy)] + #[repr(transparent)] + pub struct Id8(NonMaxHighNibbleU8); + + impl Id8 { + #[inline(always)] + const fn as_u8(self) -> u8 { + // SAFETY: transmuting fully initialized data to u8 is always safe + unsafe { std::mem::transmute::(self) } + } + + #[inline(always)] + const unsafe fn from_u8_unchecked(index: u8) -> Self { + debug_assert!(index as usize <= Self::MAX_INDEX); + // SAFETY: delegated to caller + unsafe { std::mem::transmute::(index) } + } + } + + impl PartialEq for Id8 { + #[inline(always)] + fn eq(&self, other: &Self) -> bool { + self.as_u8() == other.as_u8() + } + } + + impl Eq for Id8 {} + + impl PartialOrd for Id8 { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + + #[inline(always)] + fn lt(&self, other: &Self) -> bool { + self.as_u8() < other.as_u8() + } + + #[inline(always)] + fn le(&self, other: &Self) -> bool { + self.as_u8() <= other.as_u8() + } + + #[inline(always)] + fn gt(&self, other: &Self) -> bool { + self.as_u8() > other.as_u8() + } + + #[inline(always)] + fn ge(&self, other: &Self) -> bool { + self.as_u8() >= other.as_u8() + } + } + + impl Ord for Id8 { + #[inline(always)] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_u8().cmp(&other.as_u8()) + } + + #[inline(always)] + fn max(self, other: Self) -> Self + where + Self: Sized, + { + // SAFETY: returns either value, each known to be a valid index + unsafe { Self::from_u8_unchecked(self.as_u8().max(other.as_u8())) } + } + + #[inline(always)] + fn min(self, other: Self) -> Self + where + Self: Sized, + { + // SAFETY: returns either value, each known to be a valid index + unsafe { Self::from_u8_unchecked(self.as_u8().min(other.as_u8())) } + } + + #[inline(always)] + fn clamp(self, min: Self, max: Self) -> Self + where + Self: Sized, + Self: PartialOrd, + { + // SAFETY: returns one of the three values, all known to be a valid index + unsafe { Self::from_u8_unchecked(self.as_u8().clamp(min.as_u8(), max.as_u8())) } + } + } + + impl Debug for Id8 { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(&self.as_u8(), f) + } + } + + impl Hash for Id8 { + #[inline(always)] + fn hash(&self, state: &mut H) { + state.write_u8(self.as_u8()) + } + + #[inline(always)] + fn hash_slice(data: &[Self], state: &mut H) + where + Self: Sized, + { + // SAFETY: reading fully initialized data as &[u8] is always safe + let bytes = unsafe { data.align_to::().1 }; + state.write(bytes); + } + } + + // SAFETY: the only purpose of all code for this type is to uphold the documented Id safety + // requirements. + unsafe impl Id for Id8 { + type Generic = GenericId<{ Self::MAX_INDEX }>; + + #[inline(always)] + fn index(self) -> usize { + self.as_u8() as usize + } + + const MAX_INDEX: usize = 0xef; + + const MIN: Self = { + // SAFETY: zero is always <= MAX_INDEX + unsafe { Self::from_u8_unchecked(0) } + }; + + const MAX: Self = { + // SAFETY: MAX_INDEX is always <= MAX_INDEX + unsafe { Self::from_u8_unchecked(Self::MAX_INDEX as u8) } + }; + + #[inline(always)] + unsafe fn from_index_unchecked(index: usize) -> Self { + // SAFETY: we require index to be <= MAX_INDEX which still holds after the primitive cast + unsafe { Self::from_u8_unchecked(index as u8) } + } + + type Base = Self; + } +} + +mod id16 { + use crate::id::{u8_range_types::NonMaxU8, GenericId, Id}; + use core::{fmt, fmt::Debug, hash::Hash}; + + /// [`Id`] type representing indices in the range `0..0xff00`. + #[cfg(target_endian = "little")] + #[allow(dead_code)] // Only constructed via transmutation and/or pointer casts + #[derive(Clone, Copy)] + #[repr(C, align(2))] + pub struct Id16 { + lsb: u8, + msb: NonMaxU8, + } + + #[cfg(target_endian = "big")] + #[allow(dead_code)] // Only constructed via transmutation and/or pointer casts + #[derive(Clone, Copy)] + #[repr(C, align(2))] + pub struct Id16 { + msb: NonMaxU8, + lsb: u8, + } + + impl Id16 { + #[inline(always)] + const fn as_u16(self) -> u16 { + // SAFETY: transmuting fully initialized data to u16 is always safe + unsafe { std::mem::transmute::(self) } + } + + #[inline(always)] + const unsafe fn from_u16_unchecked(index: u16) -> Self { + debug_assert!(index as usize <= Self::MAX_INDEX); + // SAFETY: delegated to caller + unsafe { std::mem::transmute::(index) } + } + } + + impl PartialEq for Id16 { + #[inline(always)] + fn eq(&self, other: &Self) -> bool { + self.as_u16() == other.as_u16() + } + } + + impl Eq for Id16 {} + + impl PartialOrd for Id16 { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + + #[inline(always)] + fn lt(&self, other: &Self) -> bool { + self.as_u16() < other.as_u16() + } + + #[inline(always)] + fn le(&self, other: &Self) -> bool { + self.as_u16() <= other.as_u16() + } + + #[inline(always)] + fn gt(&self, other: &Self) -> bool { + self.as_u16() > other.as_u16() + } + + #[inline(always)] + fn ge(&self, other: &Self) -> bool { + self.as_u16() >= other.as_u16() + } + } + + impl Ord for Id16 { + #[inline(always)] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_u16().cmp(&other.as_u16()) + } + + #[inline(always)] + fn max(self, other: Self) -> Self + where + Self: Sized, + { + // SAFETY: returns either value, each known to be a valid index + unsafe { Self::from_u16_unchecked(self.as_u16().max(other.as_u16())) } + } + + #[inline(always)] + fn min(self, other: Self) -> Self + where + Self: Sized, + { + // SAFETY: returns either value, each known to be a valid index + unsafe { Self::from_u16_unchecked(self.as_u16().min(other.as_u16())) } + } + + #[inline(always)] + fn clamp(self, min: Self, max: Self) -> Self + where + Self: Sized, + Self: PartialOrd, + { + // SAFETY: returns one of the three values, all known to be a valid index + unsafe { Self::from_u16_unchecked(self.as_u16().clamp(min.as_u16(), max.as_u16())) } + } + } + + impl Debug for Id16 { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(&self.as_u16(), f) + } + } + + impl Hash for Id16 { + #[inline(always)] + fn hash(&self, state: &mut H) { + state.write_u16(self.as_u16()) + } + + #[inline(always)] + fn hash_slice(data: &[Self], state: &mut H) + where + Self: Sized, + { + // SAFETY: reading fully initialized data as &[u8] is always safe + let bytes = unsafe { data.align_to::().1 }; + state.write(bytes); + } + } + + // SAFETY: the only purpose of all code for this type is to uphold the documented Id safety + // requirements. + unsafe impl Id for Id16 { + type Generic = GenericId<{ Self::MAX_INDEX }>; + + #[inline(always)] + fn index(self) -> usize { + self.as_u16() as usize + } + + const MAX_INDEX: usize = 0xfeff; + + const MIN: Self = { + // SAFETY: zero is always <= MAX_INDEX + unsafe { Self::from_u16_unchecked(0) } + }; + + const MAX: Self = { + // SAFETY: MAX_INDEX is always <= MAX_INDEX + unsafe { Self::from_u16_unchecked(Self::MAX_INDEX as u16) } + }; + + #[inline(always)] + unsafe fn from_index_unchecked(index: usize) -> Self { + // SAFETY: we require index to be <= MAX_INDEX which still holds after the primitive cast + unsafe { Self::from_u16_unchecked(index as u16) } + } + + type Base = Self; + } +} + +mod id32 { + use crate::id::{u8_range_types::NonMaxU8, GenericId, Id}; + use core::{fmt, fmt::Debug, hash::Hash}; + + /// [`Id`] type representing indices in the range `0..0xff00_0000`. + #[cfg(target_endian = "little")] + #[allow(dead_code)] // Only constructed via transmutation and/or pointer casts + #[derive(Clone, Copy)] + #[repr(C, align(4))] + pub struct Id32 { + lsbs: [u8; 3], + msb: NonMaxU8, + } + + #[cfg(target_endian = "big")] + #[allow(dead_code)] // Only constructed via transmutation and/or pointer casts + #[derive(Clone, Copy)] + #[repr(C, align(4))] + pub struct Id32 { + msb: NonMaxU8, + lsbs: [u8; 3], + } + + impl Id32 { + #[inline(always)] + const fn as_u32(self) -> u32 { + // SAFETY: transmuting fully initialized data to u32 is always safe + unsafe { std::mem::transmute::(self) } + } + + #[inline(always)] + const unsafe fn from_u32_unchecked(index: u32) -> Self { + debug_assert!(index as usize <= Self::MAX_INDEX); + // SAFETY: delegated to caller + unsafe { std::mem::transmute::(index) } + } + } + + impl PartialEq for Id32 { + #[inline(always)] + fn eq(&self, other: &Self) -> bool { + self.as_u32() == other.as_u32() + } + } + + impl Eq for Id32 {} + + impl PartialOrd for Id32 { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + + #[inline(always)] + fn lt(&self, other: &Self) -> bool { + self.as_u32() < other.as_u32() + } + + #[inline(always)] + fn le(&self, other: &Self) -> bool { + self.as_u32() <= other.as_u32() + } + + #[inline(always)] + fn gt(&self, other: &Self) -> bool { + self.as_u32() > other.as_u32() + } + + #[inline(always)] + fn ge(&self, other: &Self) -> bool { + self.as_u32() >= other.as_u32() + } + } + + impl Ord for Id32 { + #[inline(always)] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_u32().cmp(&other.as_u32()) + } + + #[inline(always)] + fn max(self, other: Self) -> Self + where + Self: Sized, + { + // SAFETY: returns either value, each known to be a valid index + unsafe { Self::from_u32_unchecked(self.as_u32().max(other.as_u32())) } + } + + #[inline(always)] + fn min(self, other: Self) -> Self + where + Self: Sized, + { + // SAFETY: returns either value, each known to be a valid index + unsafe { Self::from_u32_unchecked(self.as_u32().min(other.as_u32())) } + } + + #[inline(always)] + fn clamp(self, min: Self, max: Self) -> Self + where + Self: Sized, + Self: PartialOrd, + { + // SAFETY: returns one of the three values, all known to be a valid index + unsafe { Self::from_u32_unchecked(self.as_u32().clamp(min.as_u32(), max.as_u32())) } + } + } + + impl Debug for Id32 { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(&self.as_u32(), f) + } + } + + impl Hash for Id32 { + #[inline(always)] + fn hash(&self, state: &mut H) { + state.write_u32(self.as_u32()) + } + + #[inline(always)] + fn hash_slice(data: &[Self], state: &mut H) + where + Self: Sized, + { + // SAFETY: reading fully initialized data as &[u8] is always safe + let bytes = unsafe { data.align_to::().1 }; + state.write(bytes); + } + } + + // SAFETY: the only purpose of all code for this type is to uphold the documented Id safety + // requirements. + unsafe impl Id for Id32 { + type Generic = GenericId<{ Self::MAX_INDEX }>; + + #[inline(always)] + fn index(self) -> usize { + self.as_u32() as usize + } + + const MAX_INDEX: usize = { + let max_index = 0xfeff_ffffu32; + if (max_index as usize as u32) == max_index { + max_index as usize + } else { + usize::MAX + } + }; + + const MIN: Self = { + // SAFETY: zero is always <= MAX_INDEX + unsafe { Self::from_u32_unchecked(0) } + }; + + const MAX: Self = { + // SAFETY: MAX_INDEX is always <= MAX_INDEX + unsafe { Self::from_u32_unchecked(Self::MAX_INDEX as u32) } + }; + + #[inline(always)] + unsafe fn from_index_unchecked(index: usize) -> Self { + // SAFETY: we require index to be <= MAX_INDEX which still holds after the primitive cast + unsafe { Self::from_u32_unchecked(index as u32) } + } + + type Base = Self; + } +} + +mod id64 { + use crate::id::{u8_range_types::NonMaxU8, GenericId, Id}; + use core::{fmt, fmt::Debug, hash::Hash}; + + /// [`Id`] type representing indices in the range `0..0xff00_0000_0000_0000`. + #[cfg(target_endian = "little")] + #[allow(dead_code)] // Only constructed via transmutation and/or pointer casts + #[derive(Clone, Copy)] + #[repr(C, align(8))] + pub struct Id64 { + lsbs: [u8; 7], + msb: NonMaxU8, + } + + #[cfg(target_endian = "big")] + #[allow(dead_code)] // Only constructed via transmutation and/or pointer casts + #[derive(Clone, Copy)] + #[repr(C, align(8))] + pub struct Id64 { + msb: NonMaxU8, + lsbs: [u8; 7], + } + + impl Id64 { + #[inline(always)] + const fn as_u64(self) -> u64 { + // SAFETY: transmuting fully initialized data to u64 is always safe + unsafe { std::mem::transmute::(self) } + } + + #[inline(always)] + const unsafe fn from_u64_unchecked(index: u64) -> Self { + debug_assert!(index as usize <= Self::MAX_INDEX); + // SAFETY: delegated to caller + unsafe { std::mem::transmute::(index) } + } + } + + impl PartialEq for Id64 { + #[inline(always)] + fn eq(&self, other: &Self) -> bool { + self.as_u64() == other.as_u64() + } + } + + impl Eq for Id64 {} + + impl PartialOrd for Id64 { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + + #[inline(always)] + fn lt(&self, other: &Self) -> bool { + self.as_u64() < other.as_u64() + } + + #[inline(always)] + fn le(&self, other: &Self) -> bool { + self.as_u64() <= other.as_u64() + } + + #[inline(always)] + fn gt(&self, other: &Self) -> bool { + self.as_u64() > other.as_u64() + } + + #[inline(always)] + fn ge(&self, other: &Self) -> bool { + self.as_u64() >= other.as_u64() + } + } + + impl Ord for Id64 { + #[inline(always)] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_u64().cmp(&other.as_u64()) + } + + #[inline(always)] + fn max(self, other: Self) -> Self + where + Self: Sized, + { + // SAFETY: returns either value, each known to be a valid index + unsafe { Self::from_u64_unchecked(self.as_u64().max(other.as_u64())) } + } + + #[inline(always)] + fn min(self, other: Self) -> Self + where + Self: Sized, + { + // SAFETY: returns either value, each known to be a valid index + unsafe { Self::from_u64_unchecked(self.as_u64().min(other.as_u64())) } + } + + #[inline(always)] + fn clamp(self, min: Self, max: Self) -> Self + where + Self: Sized, + Self: PartialOrd, + { + // SAFETY: returns one of the three values, all known to be a valid index + unsafe { Self::from_u64_unchecked(self.as_u64().clamp(min.as_u64(), max.as_u64())) } + } + } + + impl Debug for Id64 { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(&self.as_u64(), f) + } + } + + impl Hash for Id64 { + #[inline(always)] + fn hash(&self, state: &mut H) { + state.write_u64(self.as_u64()) + } + + #[inline(always)] + fn hash_slice(data: &[Self], state: &mut H) + where + Self: Sized, + { + // SAFETY: reading fully initialized data as &[u8] is always safe + let bytes = unsafe { data.align_to::().1 }; + state.write(bytes); + } + } + + // SAFETY: the only purpose of all code for this type is to uphold the documented Id safety + // requirements. + unsafe impl Id for Id64 { + type Generic = GenericId<{ Self::MAX_INDEX }>; + + #[inline(always)] + fn index(self) -> usize { + self.as_u64() as usize + } + + const MAX_INDEX: usize = { + let max_index = 0xfeff_ffff_ffff_ffffu64; + if (max_index as usize as u64) == max_index { + max_index as usize + } else { + usize::MAX + } + }; + + const MIN: Self = { + // SAFETY: zero is always <= MAX_INDEX + unsafe { Self::from_u64_unchecked(0) } + }; + + const MAX: Self = { + // SAFETY: MAX_INDEX is always <= MAX_INDEX + unsafe { Self::from_u64_unchecked(Self::MAX_INDEX as u64) } + }; + + #[inline(always)] + unsafe fn from_index_unchecked(index: usize) -> Self { + // SAFETY: we require index to be <= MAX_INDEX which still holds after the primitive cast + unsafe { Self::from_u64_unchecked(index as u64) } + } + + type Base = Self; + } +} + +mod id_size { + use crate::id::{u8_range_types::NonMaxMsbU8, GenericId, Id}; + use core::{fmt, fmt::Debug, hash::Hash}; + + const LSBS: usize = (usize::BITS as usize / 8) - 1; + + /// [`Id`] type representing indices in the range `0..=isize::MAX as usize`. + #[cfg(target_endian = "little")] + #[allow(dead_code)] // Only constructed via transmutation and/or pointer casts + #[derive(Clone, Copy)] + #[cfg_attr(target_pointer_width = "16", repr(C, align(2)))] + #[cfg_attr(target_pointer_width = "32", repr(C, align(4)))] + #[cfg_attr(target_pointer_width = "64", repr(C, align(8)))] + pub struct IdSize { + lsbs: [u8; LSBS], + msb: NonMaxMsbU8, + } + + #[cfg(target_endian = "big")] + #[allow(dead_code)] // Only constructed via transmutation and/or pointer casts + #[derive(Clone, Copy)] + #[cfg_attr(target_pointer_width = "16", repr(C, align(2)))] + #[cfg_attr(target_pointer_width = "32", repr(C, align(4)))] + #[cfg_attr(target_pointer_width = "64", repr(C, align(8)))] + pub struct IdSize { + msb: NonMaxMsbU8, + lsbs: [u8; LSBS], + } + + impl IdSize { + #[inline(always)] + const fn as_usize(self) -> usize { + // SAFETY: transmuting fully initialized data to u64 is always safe + unsafe { std::mem::transmute::(self) } + } + + #[inline(always)] + const unsafe fn from_usize_unchecked(index: usize) -> Self { + debug_assert!(index <= Self::MAX_INDEX); + // SAFETY: delegated to caller + unsafe { std::mem::transmute::(index) } + } + } + + impl PartialEq for IdSize { + #[inline(always)] + fn eq(&self, other: &Self) -> bool { + self.as_usize() == other.as_usize() + } + } + + impl Eq for IdSize {} + + impl PartialOrd for IdSize { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + + #[inline(always)] + fn lt(&self, other: &Self) -> bool { + self.as_usize() < other.as_usize() + } + + #[inline(always)] + fn le(&self, other: &Self) -> bool { + self.as_usize() <= other.as_usize() + } + + #[inline(always)] + fn gt(&self, other: &Self) -> bool { + self.as_usize() > other.as_usize() + } + + #[inline(always)] + fn ge(&self, other: &Self) -> bool { + self.as_usize() >= other.as_usize() + } + } + + impl Ord for IdSize { + #[inline(always)] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_usize().cmp(&other.as_usize()) + } + + #[inline(always)] + fn max(self, other: Self) -> Self + where + Self: Sized, + { + // SAFETY: returns either value, each known to be a valid index + unsafe { Self::from_usize_unchecked(self.as_usize().max(other.as_usize())) } + } + + #[inline(always)] + fn min(self, other: Self) -> Self + where + Self: Sized, + { + // SAFETY: returns either value, each known to be a valid index + unsafe { Self::from_usize_unchecked(self.as_usize().min(other.as_usize())) } + } + + #[inline(always)] + fn clamp(self, min: Self, max: Self) -> Self + where + Self: Sized, + Self: PartialOrd, + { + // SAFETY: returns one of the three values, all known to be a valid index + unsafe { + Self::from_usize_unchecked(self.as_usize().clamp(min.as_usize(), max.as_usize())) + } + } + } + + impl Debug for IdSize { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(&self.as_usize(), f) + } + } + + impl Hash for IdSize { + #[inline(always)] + fn hash(&self, state: &mut H) { + state.write_usize(self.as_usize()) + } + + #[inline(always)] + fn hash_slice(data: &[Self], state: &mut H) + where + Self: Sized, + { + // SAFETY: reading fully initialized data as &[u8] is always safe + let bytes = unsafe { data.align_to::().1 }; + state.write(bytes); + } + } + + // SAFETY: the only purpose of all code for this type is to uphold the documented Id safety + // requirements. + unsafe impl Id for IdSize { + type Generic = GenericId<{ Self::MAX_INDEX }>; + + #[inline(always)] + fn index(self) -> usize { + self.as_usize() + } + + const MAX_INDEX: usize = isize::MAX as usize; + + const MIN: Self = { + // SAFETY: zero is always <= MAX_INDEX + unsafe { Self::from_usize_unchecked(0) } + }; + + const MAX: Self = { + // SAFETY: MAX_INDEX is always <= MAX_INDEX + unsafe { Self::from_usize_unchecked(Self::MAX_INDEX) } + }; + + #[inline(always)] + unsafe fn from_index_unchecked(index: usize) -> Self { + // SAFETY: delegated to caller + unsafe { Self::from_usize_unchecked(index) } + } + + type Base = Self; + } +} + +pub use id16::Id16; +pub use id32::Id32; +pub use id64::Id64; +pub use id8::Id8; +pub use id_size::IdSize; diff --git a/ids/src/id/primitive_impls.rs b/ids/src/id/primitive_impls.rs new file mode 100644 index 0000000..cd4d6b2 --- /dev/null +++ b/ids/src/id/primitive_impls.rs @@ -0,0 +1,129 @@ +use crate::{GenericId, Id}; + +// SAFETY: the only purpose of all code for this type is to uphold the documented Id safety +// requirements. +unsafe impl Id for usize { + type Generic = GenericId<{ Self::MAX_INDEX }>; + type Base = Self; + + #[inline(always)] + fn index(self) -> usize { + self + } + + const MAX_INDEX: usize = Self::MAX; + + const MIN: Self = 0; + + const MAX: Self = Self::MAX; + + #[inline(always)] + unsafe fn from_index_unchecked(index: usize) -> Self { + index + } + + #[inline(always)] + fn from_index(index: usize) -> Self { + index + } +} + +// SAFETY: the only purpose of all code for this type is to uphold the documented Id safety +// requirements. +unsafe impl Id for u64 { + type Generic = GenericId<{ Self::MAX_INDEX }>; + type Base = Self; + + #[inline(always)] + fn index(self) -> usize { + self as usize + } + + const MAX_INDEX: usize = if (u64::MAX as usize as u64) == u64::MAX { + u64::MAX as usize + } else { + usize::MAX + }; + + const MIN: Self = 0; + + const MAX: Self = Self::MAX; + + #[inline(always)] + unsafe fn from_index_unchecked(index: usize) -> Self { + index as Self + } +} + +// SAFETY: the only purpose of all code for this type is to uphold the documented Id safety +// requirements. +unsafe impl Id for u32 { + type Generic = GenericId<{ Self::MAX_INDEX }>; + type Base = Self; + + #[inline(always)] + fn index(self) -> usize { + self as usize + } + + const MAX_INDEX: usize = if (u32::MAX as usize as u32) == u32::MAX { + u32::MAX as usize + } else { + usize::MAX + }; + + const MIN: Self = 0; + + const MAX: Self = Self::MAX; + + #[inline(always)] + unsafe fn from_index_unchecked(index: usize) -> Self { + index as Self + } +} + +// SAFETY: the only purpose of all code for this type is to uphold the documented Id safety +// requirements. +unsafe impl Id for u16 { + type Generic = GenericId<{ Self::MAX_INDEX }>; + type Base = Self; + + #[inline(always)] + fn index(self) -> usize { + self as usize + } + + const MAX_INDEX: usize = u16::MAX as usize; + + const MIN: Self = 0; + + const MAX: Self = Self::MAX; + + #[inline(always)] + unsafe fn from_index_unchecked(index: usize) -> Self { + index as Self + } +} + +// SAFETY: the only purpose of all code for this type is to uphold the documented Id safety +// requirements. +unsafe impl Id for u8 { + type Generic = GenericId<{ Self::MAX_INDEX }>; + type Base = Self; + + #[inline(always)] + fn index(self) -> usize { + self as usize + } + + const MAX_INDEX: usize = u8::MAX as usize; + + const MIN: Self = 0; + + const MAX: Self = Self::MAX; + + #[inline(always)] + unsafe fn from_index_unchecked(index: usize) -> Self { + index as Self + } +} diff --git a/ids/src/id/u8_range_types.rs b/ids/src/id/u8_range_types.rs new file mode 100644 index 0000000..e5ba744 --- /dev/null +++ b/ids/src/id/u8_range_types.rs @@ -0,0 +1,640 @@ +#[allow(dead_code)] // Only constructed via transmutation and/or pointer casts +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum NonMaxU8 { + Val00 = 0x00, + Val01 = 0x01, + Val02 = 0x02, + Val03 = 0x03, + Val04 = 0x04, + Val05 = 0x05, + Val06 = 0x06, + Val07 = 0x07, + Val08 = 0x08, + Val09 = 0x09, + Val0A = 0x0a, + Val0B = 0x0b, + Val0C = 0x0c, + Val0D = 0x0d, + Val0E = 0x0e, + Val0F = 0x0f, + Val10 = 0x10, + Val11 = 0x11, + Val12 = 0x12, + Val13 = 0x13, + Val14 = 0x14, + Val15 = 0x15, + Val16 = 0x16, + Val17 = 0x17, + Val18 = 0x18, + Val19 = 0x19, + Val1A = 0x1a, + Val1B = 0x1b, + Val1C = 0x1c, + Val1D = 0x1d, + Val1E = 0x1e, + Val1F = 0x1f, + Val20 = 0x20, + Val21 = 0x21, + Val22 = 0x22, + Val23 = 0x23, + Val24 = 0x24, + Val25 = 0x25, + Val26 = 0x26, + Val27 = 0x27, + Val28 = 0x28, + Val29 = 0x29, + Val2A = 0x2a, + Val2B = 0x2b, + Val2C = 0x2c, + Val2D = 0x2d, + Val2E = 0x2e, + Val2F = 0x2f, + Val30 = 0x30, + Val31 = 0x31, + Val32 = 0x32, + Val33 = 0x33, + Val34 = 0x34, + Val35 = 0x35, + Val36 = 0x36, + Val37 = 0x37, + Val38 = 0x38, + Val39 = 0x39, + Val3A = 0x3a, + Val3B = 0x3b, + Val3C = 0x3c, + Val3D = 0x3d, + Val3E = 0x3e, + Val3F = 0x3f, + Val40 = 0x40, + Val41 = 0x41, + Val42 = 0x42, + Val43 = 0x43, + Val44 = 0x44, + Val45 = 0x45, + Val46 = 0x46, + Val47 = 0x47, + Val48 = 0x48, + Val49 = 0x49, + Val4A = 0x4a, + Val4B = 0x4b, + Val4C = 0x4c, + Val4D = 0x4d, + Val4E = 0x4e, + Val4F = 0x4f, + Val50 = 0x50, + Val51 = 0x51, + Val52 = 0x52, + Val53 = 0x53, + Val54 = 0x54, + Val55 = 0x55, + Val56 = 0x56, + Val57 = 0x57, + Val58 = 0x58, + Val59 = 0x59, + Val5A = 0x5a, + Val5B = 0x5b, + Val5C = 0x5c, + Val5D = 0x5d, + Val5E = 0x5e, + Val5F = 0x5f, + Val60 = 0x60, + Val61 = 0x61, + Val62 = 0x62, + Val63 = 0x63, + Val64 = 0x64, + Val65 = 0x65, + Val66 = 0x66, + Val67 = 0x67, + Val68 = 0x68, + Val69 = 0x69, + Val6A = 0x6a, + Val6B = 0x6b, + Val6C = 0x6c, + Val6D = 0x6d, + Val6E = 0x6e, + Val6F = 0x6f, + Val70 = 0x70, + Val71 = 0x71, + Val72 = 0x72, + Val73 = 0x73, + Val74 = 0x74, + Val75 = 0x75, + Val76 = 0x76, + Val77 = 0x77, + Val78 = 0x78, + Val79 = 0x79, + Val7A = 0x7a, + Val7B = 0x7b, + Val7C = 0x7c, + Val7D = 0x7d, + Val7E = 0x7e, + Val7F = 0x7f, + Val80 = 0x80, + Val81 = 0x81, + Val82 = 0x82, + Val83 = 0x83, + Val84 = 0x84, + Val85 = 0x85, + Val86 = 0x86, + Val87 = 0x87, + Val88 = 0x88, + Val89 = 0x89, + Val8A = 0x8a, + Val8B = 0x8b, + Val8C = 0x8c, + Val8D = 0x8d, + Val8E = 0x8e, + Val8F = 0x8f, + Val90 = 0x90, + Val91 = 0x91, + Val92 = 0x92, + Val93 = 0x93, + Val94 = 0x94, + Val95 = 0x95, + Val96 = 0x96, + Val97 = 0x97, + Val98 = 0x98, + Val99 = 0x99, + Val9A = 0x9a, + Val9B = 0x9b, + Val9C = 0x9c, + Val9D = 0x9d, + Val9E = 0x9e, + Val9F = 0x9f, + ValA0 = 0xa0, + ValA1 = 0xa1, + ValA2 = 0xa2, + ValA3 = 0xa3, + ValA4 = 0xa4, + ValA5 = 0xa5, + ValA6 = 0xa6, + ValA7 = 0xa7, + ValA8 = 0xa8, + ValA9 = 0xa9, + ValAA = 0xaa, + ValAB = 0xab, + ValAC = 0xac, + ValAD = 0xad, + ValAE = 0xae, + ValAF = 0xaf, + ValB0 = 0xb0, + ValB1 = 0xb1, + ValB2 = 0xb2, + ValB3 = 0xb3, + ValB4 = 0xb4, + ValB5 = 0xb5, + ValB6 = 0xb6, + ValB7 = 0xb7, + ValB8 = 0xb8, + ValB9 = 0xb9, + ValBA = 0xba, + ValBB = 0xbb, + ValBC = 0xbc, + ValBD = 0xbd, + ValBE = 0xbe, + ValBF = 0xbf, + ValC0 = 0xc0, + ValC1 = 0xc1, + ValC2 = 0xc2, + ValC3 = 0xc3, + ValC4 = 0xc4, + ValC5 = 0xc5, + ValC6 = 0xc6, + ValC7 = 0xc7, + ValC8 = 0xc8, + ValC9 = 0xc9, + ValCA = 0xca, + ValCB = 0xcb, + ValCC = 0xcc, + ValCD = 0xcd, + ValCE = 0xce, + ValCF = 0xcf, + ValD0 = 0xd0, + ValD1 = 0xd1, + ValD2 = 0xd2, + ValD3 = 0xd3, + ValD4 = 0xd4, + ValD5 = 0xd5, + ValD6 = 0xd6, + ValD7 = 0xd7, + ValD8 = 0xd8, + ValD9 = 0xd9, + ValDA = 0xda, + ValDB = 0xdb, + ValDC = 0xdc, + ValDD = 0xdd, + ValDE = 0xde, + ValDF = 0xdf, + ValE0 = 0xe0, + ValE1 = 0xe1, + ValE2 = 0xe2, + ValE3 = 0xe3, + ValE4 = 0xe4, + ValE5 = 0xe5, + ValE6 = 0xe6, + ValE7 = 0xe7, + ValE8 = 0xe8, + ValE9 = 0xe9, + ValEA = 0xea, + ValEB = 0xeb, + ValEC = 0xec, + ValED = 0xed, + ValEE = 0xee, + ValEF = 0xef, + ValF0 = 0xf0, + ValF1 = 0xf1, + ValF2 = 0xf2, + ValF3 = 0xf3, + ValF4 = 0xf4, + ValF5 = 0xf5, + ValF6 = 0xf6, + ValF7 = 0xf7, + ValF8 = 0xf8, + ValF9 = 0xf9, + ValFA = 0xfa, + ValFB = 0xfb, + ValFC = 0xfc, + ValFD = 0xfd, + ValFE = 0xfe, +} + +#[allow(dead_code)] // Only constructed via transmutation and/or pointer casts +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum NonMaxHighNibbleU8 { + Val00 = 0x00, + Val01 = 0x01, + Val02 = 0x02, + Val03 = 0x03, + Val04 = 0x04, + Val05 = 0x05, + Val06 = 0x06, + Val07 = 0x07, + Val08 = 0x08, + Val09 = 0x09, + Val0A = 0x0a, + Val0B = 0x0b, + Val0C = 0x0c, + Val0D = 0x0d, + Val0E = 0x0e, + Val0F = 0x0f, + Val10 = 0x10, + Val11 = 0x11, + Val12 = 0x12, + Val13 = 0x13, + Val14 = 0x14, + Val15 = 0x15, + Val16 = 0x16, + Val17 = 0x17, + Val18 = 0x18, + Val19 = 0x19, + Val1A = 0x1a, + Val1B = 0x1b, + Val1C = 0x1c, + Val1D = 0x1d, + Val1E = 0x1e, + Val1F = 0x1f, + Val20 = 0x20, + Val21 = 0x21, + Val22 = 0x22, + Val23 = 0x23, + Val24 = 0x24, + Val25 = 0x25, + Val26 = 0x26, + Val27 = 0x27, + Val28 = 0x28, + Val29 = 0x29, + Val2A = 0x2a, + Val2B = 0x2b, + Val2C = 0x2c, + Val2D = 0x2d, + Val2E = 0x2e, + Val2F = 0x2f, + Val30 = 0x30, + Val31 = 0x31, + Val32 = 0x32, + Val33 = 0x33, + Val34 = 0x34, + Val35 = 0x35, + Val36 = 0x36, + Val37 = 0x37, + Val38 = 0x38, + Val39 = 0x39, + Val3A = 0x3a, + Val3B = 0x3b, + Val3C = 0x3c, + Val3D = 0x3d, + Val3E = 0x3e, + Val3F = 0x3f, + Val40 = 0x40, + Val41 = 0x41, + Val42 = 0x42, + Val43 = 0x43, + Val44 = 0x44, + Val45 = 0x45, + Val46 = 0x46, + Val47 = 0x47, + Val48 = 0x48, + Val49 = 0x49, + Val4A = 0x4a, + Val4B = 0x4b, + Val4C = 0x4c, + Val4D = 0x4d, + Val4E = 0x4e, + Val4F = 0x4f, + Val50 = 0x50, + Val51 = 0x51, + Val52 = 0x52, + Val53 = 0x53, + Val54 = 0x54, + Val55 = 0x55, + Val56 = 0x56, + Val57 = 0x57, + Val58 = 0x58, + Val59 = 0x59, + Val5A = 0x5a, + Val5B = 0x5b, + Val5C = 0x5c, + Val5D = 0x5d, + Val5E = 0x5e, + Val5F = 0x5f, + Val60 = 0x60, + Val61 = 0x61, + Val62 = 0x62, + Val63 = 0x63, + Val64 = 0x64, + Val65 = 0x65, + Val66 = 0x66, + Val67 = 0x67, + Val68 = 0x68, + Val69 = 0x69, + Val6A = 0x6a, + Val6B = 0x6b, + Val6C = 0x6c, + Val6D = 0x6d, + Val6E = 0x6e, + Val6F = 0x6f, + Val70 = 0x70, + Val71 = 0x71, + Val72 = 0x72, + Val73 = 0x73, + Val74 = 0x74, + Val75 = 0x75, + Val76 = 0x76, + Val77 = 0x77, + Val78 = 0x78, + Val79 = 0x79, + Val7A = 0x7a, + Val7B = 0x7b, + Val7C = 0x7c, + Val7D = 0x7d, + Val7E = 0x7e, + Val7F = 0x7f, + Val80 = 0x80, + Val81 = 0x81, + Val82 = 0x82, + Val83 = 0x83, + Val84 = 0x84, + Val85 = 0x85, + Val86 = 0x86, + Val87 = 0x87, + Val88 = 0x88, + Val89 = 0x89, + Val8A = 0x8a, + Val8B = 0x8b, + Val8C = 0x8c, + Val8D = 0x8d, + Val8E = 0x8e, + Val8F = 0x8f, + Val90 = 0x90, + Val91 = 0x91, + Val92 = 0x92, + Val93 = 0x93, + Val94 = 0x94, + Val95 = 0x95, + Val96 = 0x96, + Val97 = 0x97, + Val98 = 0x98, + Val99 = 0x99, + Val9A = 0x9a, + Val9B = 0x9b, + Val9C = 0x9c, + Val9D = 0x9d, + Val9E = 0x9e, + Val9F = 0x9f, + ValA0 = 0xa0, + ValA1 = 0xa1, + ValA2 = 0xa2, + ValA3 = 0xa3, + ValA4 = 0xa4, + ValA5 = 0xa5, + ValA6 = 0xa6, + ValA7 = 0xa7, + ValA8 = 0xa8, + ValA9 = 0xa9, + ValAA = 0xaa, + ValAB = 0xab, + ValAC = 0xac, + ValAD = 0xad, + ValAE = 0xae, + ValAF = 0xaf, + ValB0 = 0xb0, + ValB1 = 0xb1, + ValB2 = 0xb2, + ValB3 = 0xb3, + ValB4 = 0xb4, + ValB5 = 0xb5, + ValB6 = 0xb6, + ValB7 = 0xb7, + ValB8 = 0xb8, + ValB9 = 0xb9, + ValBA = 0xba, + ValBB = 0xbb, + ValBC = 0xbc, + ValBD = 0xbd, + ValBE = 0xbe, + ValBF = 0xbf, + ValC0 = 0xc0, + ValC1 = 0xc1, + ValC2 = 0xc2, + ValC3 = 0xc3, + ValC4 = 0xc4, + ValC5 = 0xc5, + ValC6 = 0xc6, + ValC7 = 0xc7, + ValC8 = 0xc8, + ValC9 = 0xc9, + ValCA = 0xca, + ValCB = 0xcb, + ValCC = 0xcc, + ValCD = 0xcd, + ValCE = 0xce, + ValCF = 0xcf, + ValD0 = 0xd0, + ValD1 = 0xd1, + ValD2 = 0xd2, + ValD3 = 0xd3, + ValD4 = 0xd4, + ValD5 = 0xd5, + ValD6 = 0xd6, + ValD7 = 0xd7, + ValD8 = 0xd8, + ValD9 = 0xd9, + ValDA = 0xda, + ValDB = 0xdb, + ValDC = 0xdc, + ValDD = 0xdd, + ValDE = 0xde, + ValDF = 0xdf, + ValE0 = 0xe0, + ValE1 = 0xe1, + ValE2 = 0xe2, + ValE3 = 0xe3, + ValE4 = 0xe4, + ValE5 = 0xe5, + ValE6 = 0xe6, + ValE7 = 0xe7, + ValE8 = 0xe8, + ValE9 = 0xe9, + ValEA = 0xea, + ValEB = 0xeb, + ValEC = 0xec, + ValED = 0xed, + ValEE = 0xee, + ValEF = 0xef, +} + +#[allow(dead_code)] // Only constructed via transmutation and/or pointer casts +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum NonMaxMsbU8 { + Val00 = 0x00, + Val01 = 0x01, + Val02 = 0x02, + Val03 = 0x03, + Val04 = 0x04, + Val05 = 0x05, + Val06 = 0x06, + Val07 = 0x07, + Val08 = 0x08, + Val09 = 0x09, + Val0A = 0x0a, + Val0B = 0x0b, + Val0C = 0x0c, + Val0D = 0x0d, + Val0E = 0x0e, + Val0F = 0x0f, + Val10 = 0x10, + Val11 = 0x11, + Val12 = 0x12, + Val13 = 0x13, + Val14 = 0x14, + Val15 = 0x15, + Val16 = 0x16, + Val17 = 0x17, + Val18 = 0x18, + Val19 = 0x19, + Val1A = 0x1a, + Val1B = 0x1b, + Val1C = 0x1c, + Val1D = 0x1d, + Val1E = 0x1e, + Val1F = 0x1f, + Val20 = 0x20, + Val21 = 0x21, + Val22 = 0x22, + Val23 = 0x23, + Val24 = 0x24, + Val25 = 0x25, + Val26 = 0x26, + Val27 = 0x27, + Val28 = 0x28, + Val29 = 0x29, + Val2A = 0x2a, + Val2B = 0x2b, + Val2C = 0x2c, + Val2D = 0x2d, + Val2E = 0x2e, + Val2F = 0x2f, + Val30 = 0x30, + Val31 = 0x31, + Val32 = 0x32, + Val33 = 0x33, + Val34 = 0x34, + Val35 = 0x35, + Val36 = 0x36, + Val37 = 0x37, + Val38 = 0x38, + Val39 = 0x39, + Val3A = 0x3a, + Val3B = 0x3b, + Val3C = 0x3c, + Val3D = 0x3d, + Val3E = 0x3e, + Val3F = 0x3f, + Val40 = 0x40, + Val41 = 0x41, + Val42 = 0x42, + Val43 = 0x43, + Val44 = 0x44, + Val45 = 0x45, + Val46 = 0x46, + Val47 = 0x47, + Val48 = 0x48, + Val49 = 0x49, + Val4A = 0x4a, + Val4B = 0x4b, + Val4C = 0x4c, + Val4D = 0x4d, + Val4E = 0x4e, + Val4F = 0x4f, + Val50 = 0x50, + Val51 = 0x51, + Val52 = 0x52, + Val53 = 0x53, + Val54 = 0x54, + Val55 = 0x55, + Val56 = 0x56, + Val57 = 0x57, + Val58 = 0x58, + Val59 = 0x59, + Val5A = 0x5a, + Val5B = 0x5b, + Val5C = 0x5c, + Val5D = 0x5d, + Val5E = 0x5e, + Val5F = 0x5f, + Val60 = 0x60, + Val61 = 0x61, + Val62 = 0x62, + Val63 = 0x63, + Val64 = 0x64, + Val65 = 0x65, + Val66 = 0x66, + Val67 = 0x67, + Val68 = 0x68, + Val69 = 0x69, + Val6A = 0x6a, + Val6B = 0x6b, + Val6C = 0x6c, + Val6D = 0x6d, + Val6E = 0x6e, + Val6F = 0x6f, + Val70 = 0x70, + Val71 = 0x71, + Val72 = 0x72, + Val73 = 0x73, + Val74 = 0x74, + Val75 = 0x75, + Val76 = 0x76, + Val77 = 0x77, + Val78 = 0x78, + Val79 = 0x79, + Val7A = 0x7a, + Val7B = 0x7b, + Val7C = 0x7c, + Val7D = 0x7d, + Val7E = 0x7e, + Val7F = 0x7f, +} diff --git a/ids/src/lib.rs b/ids/src/lib.rs new file mode 100644 index 0000000..3ec31b3 --- /dev/null +++ b/ids/src/lib.rs @@ -0,0 +1,21 @@ +#![deny(unsafe_op_in_unsafe_fn)] +#![warn(missing_docs)] +#![warn(clippy::undocumented_unsafe_blocks)] +#![cfg_attr(coverage_nightly, feature(coverage_attribute))] +//! Type checked and niche compatible integer ids. + +#[cfg(doc)] +use core::hash::Hash; + +mod id; + +/// Derives an [`Id`] instance for a newtype wrapper around an existing [`Id`] type. +/// +/// Deriving an [`Id`] instance requires the `#[repr(transparent)]` attribute on the target struct. +/// +/// This automatically derives all of [`Id`]'s supertraits ([`Clone`], [`Copy`], [`PartialEq`], +/// [`Eq`], [`PartialOrd`], [`Ord`] , [`Hash`], [`Send`] and [`Sync`]) to ensure those traits are +/// implemented according to `Id`'s safety requirements. +pub use imctk_derive::Id; + +pub use id::{GenericId, Id, Id16, Id32, Id64, Id8, IdSize}; diff --git a/ids/tests/test_id.rs b/ids/tests/test_id.rs new file mode 100644 index 0000000..706f18f --- /dev/null +++ b/ids/tests/test_id.rs @@ -0,0 +1,399 @@ +use imctk_ids::*; + +use std::{hash::BuildHasher, marker::PhantomData}; + +#[cfg(miri)] +const N: usize = 32; +#[cfg(miri)] +const M: usize = 4; + +#[cfg(not(miri))] +const N: usize = 1024; +#[cfg(not(miri))] +const M: usize = 64; + +fn scale(i: usize, n: usize) -> usize { + (((i as u128) * (T::MAX_INDEX as u128)) / (n as u128)) as usize +} + +fn basic_tests(forward_debug: bool) { + let build_hasher = std::hash::RandomState::new(); + + for i in 0..=N { + let index = scale::(i, N); + let id = T::try_from_index(index); + assert_eq!(id.is_some(), index <= T::MAX_INDEX); + if let Some(value) = id { + assert_eq!(value.index(), index); + } + } + + for index in T::MAX_INDEX.saturating_sub(N)..=T::MAX_INDEX.saturating_add(N) { + let id = T::try_from_index(index); + assert_eq!(id.is_some(), index <= T::MAX_INDEX); + if let Some(value) = id { + assert_eq!(value.index(), index); + } + } + + let scale = scale::; + + for index in 0..=T::MAX_INDEX.min(N) { + let id = T::from_index(index); + assert_eq!(id.index(), index); + if forward_debug { + assert_eq!(format!("{:?}", id), format!("{:?}", index)); + } else { + assert!(format!("{:?}", id).contains(&format!("{:?}", index))); + } + } + for index in T::MAX_INDEX.saturating_sub(N)..=T::MAX_INDEX { + let id = T::from_index(index); + assert_eq!(id.index(), index); + if forward_debug { + assert_eq!(format!("{:?}", id), format!("{:?}", index)); + } else { + assert!(format!("{:?}", id).contains(&format!("{:?}", index))); + } + } + for i in 0..=N { + let index = scale(i, N); + let id = T::from_index(index); + assert_eq!(id.index(), index); + if forward_debug { + assert_eq!(format!("{:?}", id), format!("{:?}", index)); + } else { + assert!(format!("{:?}", id).contains(&format!("{:?}", index))); + } + } + for i in 0..=M { + let index_i = scale(i, M); + let id_i = T::from_index(index_i); + for j in 0..=M { + let index_j = scale(j, M); + let id_j = T::from_index(index_j); + assert_eq!(index_i < index_j, id_i < id_j); + assert_eq!(index_i <= index_j, id_i <= id_j); + assert_eq!(index_i > index_j, id_i > id_j); + assert_eq!(index_i >= index_j, id_i >= id_j); + assert_eq!(index_i == index_j, id_i == id_j); + assert_eq!(index_i.partial_cmp(&index_j), id_i.partial_cmp(&id_j)); + assert_eq!(index_i.max(index_j), id_i.max(id_j).index()); + assert_eq!(index_i.min(index_j), id_i.min(id_j).index()); + assert!( + index_i != index_j + || ({ build_hasher.hash_one(id_i) }) == { build_hasher.hash_one(id_j) } + ); + assert!( + index_i != index_j + || ({ build_hasher.hash_one([id_i, id_j]) }) == { + build_hasher.hash_one([id_j, id_i]) + } + ); + + if index_i <= index_j { + for k in 0..=M { + let index_k = scale(k, M); + let id_k = T::from_index(index_k); + assert_eq!( + index_k.clamp(index_i, index_j), + id_k.clamp(id_i, id_j).index() + ); + } + } + } + } +} + +fn base_conversion_tests>() { + for index in 0..=T::MAX_INDEX.min(N) { + let id = T::from_index(index); + let id = U::from_base_id(id.into_base_id()); + assert_eq!(id.index(), index); + } + for index in T::MAX_INDEX.saturating_sub(N)..=T::MAX_INDEX { + let id = T::from_index(index); + let id = U::from_base_id(id.into_base_id()); + assert_eq!(id.index(), index); + } + for i in 0..=N { + let index = scale::(i, N); + let id = T::from_index(index); + let id = U::from_base_id(id.into_base_id()); + assert_eq!(id.index(), index); + } +} + +fn generic_conversion_tests>() { + for index in 0..=T::MAX_INDEX.min(N) { + let id = U::cast_from_id(T::from_index(index)); + let id2 = T::from_index(index).cast_into_id(); + assert_eq!(id, id2); + assert_eq!(id.index(), index); + } + for index in T::MAX_INDEX.saturating_sub(N)..=T::MAX_INDEX { + let id = U::cast_from_id(T::from_index(index)); + let id2 = T::from_index(index).cast_into_id(); + assert_eq!(id, id2); + assert_eq!(id.index(), index); + } + for i in 0..=N { + let index = scale::(i, N); + let id = U::cast_from_id(T::from_index(index)); + let id2 = T::from_index(index).cast_into_id(); + assert_eq!(id, id2); + assert_eq!(id.index(), index); + } +} + +#[test] +fn basic_id8() { + basic_tests::(true) +} + +#[test] +fn basic_id16() { + basic_tests::(true) +} + +#[test] +fn basic_id32() { + basic_tests::(true) +} + +#[test] +fn basic_id64() { + basic_tests::(true) +} + +#[test] +fn basic_id_size() { + basic_tests::(true) +} + +#[test] +fn basic_id_generic() { + basic_tests::>(true) +} + +#[derive(Id)] +#[repr(transparent)] +pub struct CustomDebug(Id32); + +impl core::fmt::Debug for CustomDebug { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.index()) + } +} + +#[derive(Id, Debug)] +#[repr(transparent)] +pub struct GenericWrapper(T); + +#[derive(Id, Debug)] +#[repr(transparent)] +pub struct NewtypeId8(Id8); + +#[test] +fn basic_newtype_id8() { + basic_tests::(false); +} + +#[test] +fn base_convert_id8() { + base_conversion_tests::(); + base_conversion_tests::(); +} + +#[derive(Id, Debug)] +#[repr(transparent)] +pub struct NewtypeId16(Id16); + +#[test] +fn basic_newtype_id16() { + basic_tests::(false); +} + +#[test] +fn base_convert_id16() { + base_conversion_tests::(); + base_conversion_tests::(); +} + +#[derive(Id, Debug)] +#[repr(transparent)] +pub struct NewtypeId32(Id32); + +#[test] +fn basic_newtype_id32() { + basic_tests::(false); +} + +#[test] +fn base_convert_id32() { + base_conversion_tests::(); + base_conversion_tests::(); +} + +#[derive(Id, Debug)] +#[repr(transparent)] +pub struct NewtypeId64(Id64); + +#[test] +fn basic_newtype_id64() { + basic_tests::(false); +} + +#[test] +fn base_convert_id64() { + base_conversion_tests::(); + base_conversion_tests::(); +} + +#[derive(Id, Debug)] +#[repr(transparent)] +pub struct NewtypeIdSize(IdSize); + +#[test] +fn basic_newtype_id_size() { + basic_tests::(false); +} + +#[test] +fn base_convert_id_size() { + base_conversion_tests::(); + base_conversion_tests::(); +} + +#[test] +fn basic_id_custom_debug() { + basic_tests::(true); +} + +#[test] +fn basic_id_generic_wrapper() { + basic_tests::>(false); +} + +#[test] +fn basic_u8() { + basic_tests::(true) +} + +#[test] +fn basic_u16() { + basic_tests::(true) +} + +#[test] +fn basic_u32() { + basic_tests::(true) +} + +#[test] +fn basic_u64() { + basic_tests::(true) +} + +#[test] +fn basic_usize() { + basic_tests::(true) +} + +#[derive(Id, Debug)] +#[repr(transparent)] +pub struct CustomU8(GenericId<{ u8::MAX as usize }>); + +#[derive(Id, Debug)] +#[repr(transparent)] +pub struct NewtypeU8(u8); + +#[test] +fn conversion_u8() { + generic_conversion_tests::(); + generic_conversion_tests::(); + base_conversion_tests::(); + base_conversion_tests::(); +} + +#[derive(Id, Debug)] +#[repr(transparent)] +pub struct CustomU16(GenericId<{ u16::MAX as usize }>); + +#[derive(Id, Debug)] +#[repr(transparent)] +pub struct NewtypeU16(u16); + +#[test] +fn conversion_u16() { + generic_conversion_tests::(); + generic_conversion_tests::(); + base_conversion_tests::(); + base_conversion_tests::(); +} + +#[derive(Id, Debug)] +#[repr(transparent)] +pub struct CustomU32(GenericId<{ u32::MAX as usize }>); + +#[derive(Id, Debug)] +#[repr(transparent)] +pub struct NewtypeU32(u32); + +#[test] +fn conversion_u32() { + generic_conversion_tests::(); + generic_conversion_tests::(); + base_conversion_tests::(); + base_conversion_tests::(); +} + +#[derive(Id, Debug)] +#[repr(transparent)] +pub struct CustomU64(GenericId<{ u64::MAX as usize }>); + +#[derive(Id, Debug)] +#[repr(transparent)] +pub struct NewtypeU64(u64); + +#[test] +fn conversion_u64() { + generic_conversion_tests::(); + generic_conversion_tests::(); + base_conversion_tests::(); + base_conversion_tests::(); +} + +#[derive(Id, Debug)] +#[repr(transparent)] +pub struct CustomUsize(GenericId<{ usize::MAX }>); + +#[derive(Id, Debug)] +#[repr(transparent)] +pub struct NewtypeUsize(usize); + +#[test] +fn conversion_usize() { + generic_conversion_tests::(); + generic_conversion_tests::(); + base_conversion_tests::(); + base_conversion_tests::(); +} + +#[derive(Id)] +#[repr(transparent)] +pub struct NewtypePhantom(usize, PhantomData); + +impl std::fmt::Debug for NewtypePhantom { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("NewtypePhantom") + .field(&self.0) + .field(&self.1) + .finish() + } +} + +#[test] +fn basic_phantom() { + basic_tests::>(false); +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..c3c8c37 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +imports_granularity = "Crate"