#![deny(missing_docs)]
#![allow(internal_features)]
#![allow(incomplete_features)]
#![feature(str_from_raw_parts)]
#![feature(generic_const_exprs)]
#![feature(nonzero_internals)]
#![cfg_attr(feature = "specialization", feature(specialization))]
#![doc = include_str!("../README.md")]
mod remote_impl;
use core::fmt;
use std::hash::Hash;
pub use fixed_type_id_macros::{
fixed_type_id, fixed_type_id_without_version_hash, random_fixed_type_id,
};
use semver::Version;
#[cfg(feature = "len128")]
pub const CONST_TYPENAME_LEN: usize = 128;
#[cfg(feature = "len64")]
pub const CONST_TYPENAME_LEN: usize = 64;
#[cfg(feature = "len256")]
pub const CONST_TYPENAME_LEN: usize = 256;
#[repr(transparent)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "rkyv", rkyv(attr(allow(missing_docs))))]
#[cfg_attr(feature = "rkyv", rkyv(compare(PartialEq), derive(Debug)))]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct FixedId(pub u64);
#[cfg(feature = "rkyv")]
impl From<&ArchivedFixedId> for FixedId {
fn from(value: &ArchivedFixedId) -> Self {
FixedId(value.0.into())
}
}
impl Hash for FixedId {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u64(self.0);
}
}
impl FixedId {
pub const fn from<Target: 'static + ?Sized + FixedTypeId>() -> Self {
Target::TYPE_ID
}
pub const fn as_u64(&self) -> u64 {
self.0
}
pub const fn from_type_name(type_name: &'static str, version: Option<FixedVersion>) -> Self {
let hash = match version {
None => rapidhash::rapidhash(type_name.as_bytes()),
Some(version) => {
let name_hash = rapidhash::rapidhash(type_name.as_bytes());
let version_hash = rapidhash::rapidhash(&version.const_to_bytes());
rapid_mix(name_hash, version_hash)
}
};
FixedId(hash)
}
}
const fn u64s_to_bytes<const N: usize>(slice: &[u64; N]) -> [u8; N * 8] {
let mut bytes = [0u8; N * 8];
let mut slice_remaining: &[u64] = slice;
let mut i = 0;
let mut slice_index = 0;
while let [current, tail @ ..] = slice_remaining {
let mut current_bytes: &[u8] = ¤t.to_le_bytes();
while let [current, tail @ ..] = current_bytes {
bytes[i] = *current;
i += 1;
current_bytes = tail;
}
slice_index += 1;
debug_assert!(i == 8 * slice_index);
slice_remaining = tail;
}
bytes
}
pub const fn usize_to_str(n: usize) -> &'static str {
match n {
0 => "0",
1 => "1",
2 => "2",
3 => "3",
4 => "4",
5 => "5",
6 => "6",
7 => "7",
8 => "8",
9 => "9",
10 => "10",
11 => "11",
12 => "12",
13 => "13",
14 => "14",
15 => "15",
16 => "16",
17 => "17",
18 => "18",
19 => "19",
20 => "20",
21 => "21",
22 => "22",
23 => "23",
24 => "24",
25 => "25",
26 => "26",
27 => "27",
28 => "28",
29 => "29",
30 => "30",
31 => "31",
32 => "32",
64 => "64",
128 => "128",
256 => "256",
512 => "512",
768 => "768",
1024 => "1024",
2048 => "2048",
4096 => "4096",
8192 => "8192",
16384 => "16384",
32768 => "32768",
65536 => "65536",
_ => "N",
}
}
#[inline(always)]
const fn rapid_mum(a: u64, b: u64) -> (u64, u64) {
let r = a as u128 * b as u128;
(r as u64, (r >> 64) as u64)
}
#[inline(always)]
const fn rapid_mix(a: u64, b: u64) -> u64 {
let (a, b) = rapid_mum(a, b);
a ^ b
}
impl fmt::Display for FixedId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
pub trait FixedTypeId {
const TYPE_NAME: &'static str;
const TYPE_ID: FixedId = FixedId::from_type_name(Self::TYPE_NAME, Some(Self::TYPE_VERSION));
const TYPE_VERSION: FixedVersion = FixedVersion::new(0, 0, 0);
#[inline(always)]
fn ty_name(&self) -> &'static str {
Self::TYPE_NAME
}
#[inline(always)]
fn ty_id(&self) -> FixedId {
Self::TYPE_ID
}
#[inline(always)]
fn ty_version(&self) -> FixedVersion {
Self::TYPE_VERSION
}
}
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(feature = "rkyv", rkyv(attr(allow(missing_docs))))]
#[cfg_attr(feature = "rkyv", rkyv(compare(PartialEq), derive(Debug)))]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct FixedVersion {
pub major: u64,
pub minor: u64,
pub patch: u64,
}
impl FixedVersion {
#[inline(always)]
pub const fn new(major: u64, minor: u64, patch: u64) -> Self {
FixedVersion {
major,
minor,
patch,
}
}
pub const fn const_to_bytes(&self) -> [u8; 24] {
u64s_to_bytes(&[self.major, self.minor, self.patch])
}
pub fn to_bytes(&self) -> [u8; 24] {
let mut bytes = [0u8; 24];
bytes[0..8].copy_from_slice(&self.major.to_le_bytes());
bytes[8..16].copy_from_slice(&self.minor.to_le_bytes());
bytes[16..24].copy_from_slice(&self.patch.to_le_bytes());
bytes
}
pub fn is_compatible(&self, expected_version: &FixedVersion) -> bool {
let compatible_cmp = semver::Comparator {
op: semver::Op::Caret,
major: expected_version.major,
minor: Some(expected_version.minor),
patch: Some(expected_version.patch),
pre: semver::Prerelease::EMPTY,
};
compatible_cmp.matches(&Version::new(self.major, self.minor, self.patch))
}
pub fn matches(&self, comparator: &semver::Comparator) -> bool {
comparator.matches(&Version::new(self.major, self.minor, self.patch))
}
}
impl From<(u64, u64, u64)> for FixedVersion {
fn from(value: (u64, u64, u64)) -> Self {
FixedVersion::new(value.0, value.1, value.2)
}
}
impl From<FixedVersion> for (u64, u64, u64) {
fn from(value: FixedVersion) -> Self {
(value.major, value.minor, value.patch)
}
}
impl From<Version> for FixedVersion {
fn from(value: Version) -> Self {
FixedVersion::new(value.major, value.minor, value.patch)
}
}
impl From<FixedVersion> for Version {
fn from(value: FixedVersion) -> Self {
Version::new(value.major, value.minor, value.patch)
}
}
pub fn name_version_to_hash(name: &str, version: &FixedVersion) -> u64 {
let name_hash = rapidhash::rapidhash(name.as_bytes());
let mut bytes = [0u8; 24];
bytes[0..8].copy_from_slice(&version.major.to_le_bytes());
bytes[8..16].copy_from_slice(&version.minor.to_le_bytes());
bytes[16..24].copy_from_slice(&version.patch.to_le_bytes());
rapid_mix(name_hash, rapidhash::rapidhash(&bytes))
}
pub trait ConstTypeName {
const RAW_SLICE: &[&str];
const TYPE_NAME_FSTR: fixedstr_ext::fstr<CONST_TYPENAME_LEN> = slice_to_fstr(Self::RAW_SLICE);
}
#[inline(always)]
pub fn type_name<T: ?Sized + FixedTypeId>() -> &'static str {
T::TYPE_NAME
}
#[inline(always)]
pub fn type_id<T: ?Sized + FixedTypeId>() -> FixedId {
T::TYPE_ID
}
#[inline(always)]
pub fn type_version<T: ?Sized + FixedTypeId>() -> FixedVersion {
T::TYPE_VERSION
}
#[macro_export]
macro_rules! implement_wrapper_fixed_type_id {
(@impl_generics $wrapper:ident, ($first:ident: $bound:path $(, $rest:ident)*), $prefix:expr) => {
impl<$first $(, $rest)*> FixedTypeId for $wrapper<$first $(, $rest)*>
where
$first: FixedTypeId + $bound,
$($rest: FixedTypeId,)*
Self: ConstTypeName,
{
const TYPE_NAME: &'static str = fstr_to_str(&Self::TYPE_NAME_FSTR);
}
impl<$first $(, $rest)*> ConstTypeName for $wrapper<$first $(, $rest)*>
where
$first: FixedTypeId + $bound,
$($rest: FixedTypeId,)*
{
const RAW_SLICE: &[&str] = &[
$prefix,
"<",
$first::TYPE_NAME,
$(
",",
$rest::TYPE_NAME,
)*
">"
];
}
};
(@impl_generics $wrapper:ident, ($first:ident $(, $rest:ident)*), $prefix:expr) => {
impl<$first $(, $rest)*> FixedTypeId for $wrapper<$first $(, $rest)*>
where
$first: FixedTypeId,
$($rest: FixedTypeId,)*
Self: ConstTypeName,
{
const TYPE_NAME: &'static str = fstr_to_str(&Self::TYPE_NAME_FSTR);
}
impl<$first: FixedTypeId $(, $rest: FixedTypeId)*> ConstTypeName for $wrapper<$first $(, $rest)*>
where
$first: FixedTypeId,
$($rest: FixedTypeId,)*
{
const RAW_SLICE: &[&str] = &[
$prefix,
"<",
$first::TYPE_NAME,
$(
",",
$rest::TYPE_NAME,
)*
">"
];
}
};
($($wrapper:ident<$first:ident $(: $bound:path)? $(, $rest:ident)*> => $prefix:expr);* $(;)?) => {
$(
implement_wrapper_fixed_type_id!(@impl_generics $wrapper, ($first $(: $bound)? $(, $rest)*), $prefix);
)*
};
}
pub const fn fstr_to_str<const N: usize>(fstr: &'static fixedstr_ext::fstr<N>) -> &'static str {
unsafe { core::str::from_raw_parts(fstr.to_ptr(), fstr.len()) }
}
pub const fn slice_to_fstr<const N: usize>(slice: &[&str]) -> fixedstr_ext::fstr<N> {
fixedstr_ext::fstr::<N>::const_create_from_str_slice(slice)
}
#[cfg(feature = "specialization")]
impl<T> FixedTypeId for T {
default const TYPE_NAME: &'static str = "NOT_IMPLEMENTED";
default const TYPE_ID: FixedId =
FixedId::from_type_name(Self::TYPE_NAME, Some(Self::TYPE_VERSION));
default const TYPE_VERSION: FixedVersion = FixedVersion::new(0, 0, 0);
default fn ty_name(&self) -> &'static str {
Self::TYPE_NAME
}
default fn ty_id(&self) -> FixedId {
Self::TYPE_ID
}
default fn ty_version(&self) -> FixedVersion {
Self::TYPE_VERSION
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unique_id_typeid_equal_to() {
pub struct A1;
pub struct A2;
fixed_type_id_without_version_hash! {
#[FixedTypeIdVersion((0,1,0))]
A1;
}
fixed_type_id_without_version_hash! {
#[FixedTypeIdVersion((0,2,0))]
#[FixedTypeIdEqualTo("A1")]
A2;
}
assert_eq!(<A1 as FixedTypeId>::TYPE_NAME, "A1");
assert_eq!(<A2 as FixedTypeId>::TYPE_NAME, "A2");
assert_eq!(<A1 as FixedTypeId>::TYPE_ID, <A2 as FixedTypeId>::TYPE_ID);
assert_eq!(
<A1 as FixedTypeId>::TYPE_VERSION,
FixedVersion::new(0, 1, 0)
);
assert_eq!(
<A2 as FixedTypeId>::TYPE_VERSION,
FixedVersion::new(0, 2, 0)
);
}
#[test]
fn unique_id_generic_ne() {
pub struct A<T> {
pub _t: T,
}
fixed_type_id! {
A<u8>;
A<u16>;
}
assert_eq!(<A<u8> as FixedTypeId>::TYPE_NAME, "A<u8>");
assert_eq!(<A<u16> as FixedTypeId>::TYPE_NAME, "A<u16>");
assert_ne!(
<A<u8> as FixedTypeId>::TYPE_ID,
<A<u16> as FixedTypeId>::TYPE_ID
);
assert_eq!(
<A<u8> as FixedTypeId>::TYPE_VERSION,
<A<u16> as FixedTypeId>::TYPE_VERSION
);
assert_eq!(
<A<u8> as FixedTypeId>::TYPE_VERSION,
FixedVersion::new(0, 0, 0)
);
assert_eq!(
<A<u16> as FixedTypeId>::TYPE_VERSION,
FixedVersion::new(0, 0, 0)
);
}
#[test]
fn macro_manual_diff() {
mod a {
use super::fixed_type_id;
use super::{FixedId, FixedTypeId, FixedVersion};
pub struct A;
fixed_type_id! {
A;
}
}
mod b {
use super::FixedTypeId;
pub struct A;
impl FixedTypeId for A {
const TYPE_NAME: &'static str = "A";
}
}
assert_eq!(<b::A as FixedTypeId>::TYPE_ID.0, {
name_version_to_hash("A", &(0, 0, 0).into())
});
assert_eq!(
<b::A as FixedTypeId>::TYPE_ID.0,
<a::A as FixedTypeId>::TYPE_ID.0
);
assert_eq!(
<a::A as FixedTypeId>::TYPE_VERSION,
FixedVersion::new(0, 0, 0)
);
assert_eq!(
<b::A as FixedTypeId>::TYPE_VERSION,
<a::A as FixedTypeId>::TYPE_VERSION
);
assert_eq!(<a::A as FixedTypeId>::TYPE_NAME, "A");
assert_eq!(
<b::A as FixedTypeId>::TYPE_NAME,
<a::A as FixedTypeId>::TYPE_NAME
);
}
#[cfg(feature = "specialization")]
#[test]
fn specialization_for_any_type() {
pub struct A {
pub _t: u8,
pub _x: i32,
};
assert_eq!(<A as FixedTypeId>::TYPE_NAME, "NOT_IMPLEMENTED");
assert_eq!(
<A as FixedTypeId>::TYPE_ID,
FixedId::from_type_name("NOT_IMPLEMENTED", Some(FixedVersion::new(0, 0, 0)))
);
assert_eq!(<A as FixedTypeId>::TYPE_VERSION, FixedVersion::new(0, 0, 0));
}
}