use std::{
collections::{HashMap, HashSet},
fmt::Debug,
hash::Hash,
marker::PhantomData,
ops::Deref,
sync::{LazyLock, Mutex},
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub struct InterningTable<T> {
items: Vec<T>,
ids: HashMap<T, Interned<T>>,
}
impl<T> Default for InterningTable<T> {
fn default() -> Self {
Self {
items: Default::default(),
ids: Default::default(),
}
}
}
#[derive(Hash, Eq, PartialEq)]
pub struct Interned<T> {
phantom: PhantomData<T>,
index: u32,
}
impl<T: Eq> PartialOrd for Interned<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T: Eq> Ord for Interned<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.index.cmp(&other.index)
}
}
impl<T: Serialize + Internable> Serialize for Interned<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.get().serialize(serializer)
}
}
impl<T: Internable> AsRef<T> for Interned<T> {
fn as_ref(&self) -> &T {
(*self).get()
}
}
impl<'a, T: Deserialize<'a> + Internable> Deserialize<'a> for Interned<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'a>,
{
Ok(Interned::intern(&T::deserialize(deserializer)?))
}
}
impl<T: JsonSchema> JsonSchema for Interned<T> {
fn schema_name() -> String {
T::schema_name()
}
fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
T::json_schema(generator)
}
}
impl<T: Internable + Debug> Debug for Interned<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Interned")
.field("index", &self.index)
.field("value", self.get())
.finish()
}
}
impl<T> Clone for Interned<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for Interned<T> {}
pub struct ExplicitClosure<T, R>(T, fn(T) -> R);
impl<T, R> FnOnce<()> for ExplicitClosure<T, R> {
type Output = R;
extern "rust-call" fn call_once(self, _: ()) -> Self::Output {
let Self(input, function) = self;
function(input)
}
}
impl<T: Hash + Eq + Clone + Send> InterningTable<T> {
fn try_intern(&mut self, value: &T) -> Option<Interned<T>> {
Some(if let Some(interned) = self.ids.get(value) {
*interned
} else {
let index = self.items.len();
self.items.push(value.clone());
let handle = Interned {
phantom: PhantomData,
index: index.try_into().ok()?,
};
self.ids.insert(value.clone(), handle);
handle
})
}
fn get(&self, interned: Interned<T>) -> &T {
&self.items[interned.index as usize]
}
pub const fn new_with_values<const N: usize>(
values: fn() -> [T; N],
) -> (LazyLockNewWithValue<T, N>, [Interned<T>; N]) {
assert!(N < u32::MAX as usize);
let mut i = 0;
let mut interned_values: [Interned<T>; N] = [Interned {
phantom: PhantomData,
index: 0,
}; N];
while i < N {
interned_values[i].index = i as u32;
i += 1;
}
let lazy_lock = LazyLock::new(ExplicitClosure(values, |values| {
let values = values();
{
let set: HashSet<_> = values.iter().collect();
if set.len() != values.len() {
panic!("new_with_values: the input has duplicates");
}
}
let mut table = InterningTable::default();
for value in values {
if table.try_intern(&value).is_none() {
unreachable!(
"we asserted `N < u32::MAX`, the length of the internal vector `table` should be less than `u32::MAX`"
)
}
}
Mutex::new(table)
}));
(lazy_lock, interned_values)
}
}
pub type LazyLockNewWithValue<T, const N: usize> =
LazyLock<Mutex<InterningTable<T>>, ExplicitClosure<fn() -> [T; N], Mutex<InterningTable<T>>>>;
pub trait Internable: Sized + Hash + Eq + Clone + Send + 'static {
fn interning_table() -> &'static Mutex<InterningTable<Self>>;
fn intern(&self) -> Interned<Self> {
Interned::intern(self)
}
}
impl<T: Internable> Interned<T> {
pub fn intern(value: &T) -> Self {
{
let mut table = T::interning_table()
.lock()
.expect("interning table mutex poisoned");
table.try_intern(value)
}
.unwrap_or_else(|| {
panic!(
"more than `u32::MAX` values have been interned for type `{}`",
std::any::type_name::<T>()
)
})
}
pub fn get(self) -> &'static T {
let table = T::interning_table().lock().unwrap();
let local_reference = table.get(self);
let static_reference: &'static T = unsafe { std::mem::transmute(local_reference) };
static_reference
}
}
impl<T: Internable> Deref for Interned<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.get()
}
}