use alloc::{
collections::BTreeMap,
format,
string::{String, ToString},
vec::Vec,
};
use core::{any::TypeId, num::NonZeroU32};
use sails_idl_ast::{StructDef, Type, TypeDecl, TypeDef};
use crate::MetaType;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct TypeRef(NonZeroU32);
impl TypeRef {
pub fn new(id: u32) -> Self {
Self(NonZeroU32::new(id).expect("Type ID must not be zero"))
}
pub fn get(&self) -> u32 {
self.0.get()
}
}
pub trait TypeInfo: 'static {
type Identity: ?Sized + 'static;
const META: MetaType = MetaType::new::<Self>();
fn type_decl(registry: &mut Registry) -> TypeDecl;
fn type_def(_registry: &mut Registry) -> Option<Type> {
None
}
fn module_path() -> &'static str {
""
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct NamedKey {
pub module_path: &'static str,
pub base_name: String,
}
#[derive(Debug, Clone)]
pub struct ConcreteTypeBinding {
pub type_ref: TypeRef,
pub generics: Vec<TypeDecl>,
}
#[derive(Debug, Clone)]
pub struct NamedEntry {
pub key: NamedKey,
pub unique_name: String,
pub ty: Type,
}
struct NamedReservation {
type_ref: TypeRef,
key: NamedKey,
final_name: String,
needs_fill: bool,
}
#[derive(Debug, Default, Clone)]
pub struct Registry {
concrete_cache: BTreeMap<TypeId, ConcreteTypeBinding>,
named_interner: BTreeMap<NamedKey, TypeRef>,
used_names: BTreeMap<String, TypeRef>,
named_entries: Vec<NamedEntry>,
}
impl Registry {
pub fn new() -> Self {
Self::default()
}
pub fn register_named_type(
&mut self,
meta: MetaType,
base_name: impl Into<String>,
generics: Vec<TypeDecl>,
) -> TypeDecl {
self.register_named_type_with_dependencies(meta, base_name, generics, |_| {})
}
pub fn register_named_type_with_dependencies<F>(
&mut self,
meta: MetaType,
base_name: impl Into<String>,
generics: Vec<TypeDecl>,
register_dependencies: F,
) -> TypeDecl
where
F: FnOnce(&mut Registry),
{
let base_name = base_name.into();
let type_id = meta.type_id();
if let Some(existing) = self.concrete_cache.get(&type_id) {
return self.concrete_decl(existing);
}
let module_path = meta.module_path();
let reservation = self.reserve_named_type(module_path, &base_name);
self.cache_concrete_binding(type_id, reservation.type_ref, generics.clone());
register_dependencies(self);
if reservation.needs_fill {
self.fill_named_entry(
meta,
reservation.type_ref,
reservation.key,
&reservation.final_name,
);
} else {
#[cfg(debug_assertions)]
{
let mut candidate = meta.type_def(self).expect("named type must define itself");
candidate.name = reservation.final_name.clone();
self.assert_compatible_named_def(
reservation.type_ref,
&candidate,
module_path,
&base_name,
);
}
}
TypeDecl::named_with_generics(reservation.final_name, generics)
}
pub fn register_type<T: TypeInfo + ?Sized>(&mut self) -> Option<TypeRef> {
let _ = T::type_decl(self);
self.get_registered::<T>()
.map(|registered| registered.type_ref)
}
pub fn register_meta_type(&mut self, meta: MetaType) -> TypeRef {
let _ = meta.type_decl(self);
self.concrete_cache
.get(&meta.type_id())
.map(|registered| registered.type_ref)
.expect("register_meta_type expects a named MetaType")
}
pub fn get_meta_binding(&self, type_id: &TypeId) -> Option<&ConcreteTypeBinding> {
self.concrete_cache.get(type_id)
}
pub fn decl_for<T: TypeInfo + ?Sized>(&mut self) -> TypeDecl {
T::type_decl(self)
}
pub fn get_registered<T: TypeInfo + ?Sized>(&self) -> Option<&ConcreteTypeBinding> {
self.concrete_cache.get(&TypeId::of::<T::Identity>())
}
pub fn get_entry(&self, type_ref: TypeRef) -> Option<&NamedEntry> {
self.named_entries.get((type_ref.get() - 1) as usize)
}
pub fn get_type(&self, type_ref: TypeRef) -> Option<&Type> {
self.get_entry(type_ref).map(|entry| &entry.ty)
}
pub fn get_type_ref_by_name(&self, name: &str) -> Option<TypeRef> {
self.used_names.get(name).copied()
}
pub fn get_type_by_name(&self, name: &str) -> Option<(TypeRef, &Type)> {
let type_ref = self.get_type_ref_by_name(name)?;
self.get_type(type_ref).map(|ty| (type_ref, ty))
}
pub fn unique_name_for(&self, module_path: &'static str, base_name: &str) -> Option<String> {
let key = NamedKey {
module_path,
base_name: base_name.to_string(),
};
self.named_interner
.get(&key)
.and_then(|type_ref| self.get_entry(*type_ref))
.map(|entry| entry.unique_name.clone())
}
pub fn is_type<T: TypeInfo + ?Sized>(&self, type_ref: TypeRef) -> bool {
self.get_registered::<T>()
.is_some_and(|registered| registered.type_ref == type_ref)
}
pub fn types(&self) -> impl Iterator<Item = (TypeRef, &Type)> {
self.named_entries
.iter()
.enumerate()
.map(|(idx, entry)| (TypeRef::new((idx as u32) + 1), &entry.ty))
}
pub fn len(&self) -> usize {
self.named_entries.len()
}
pub fn is_empty(&self) -> bool {
self.named_entries.is_empty()
}
fn concrete_decl(&self, binding: &ConcreteTypeBinding) -> TypeDecl {
let name = self
.get_type(binding.type_ref)
.expect("ref valid")
.name
.clone();
TypeDecl::named_with_generics(name, binding.generics.clone())
}
fn reserve_named_type(
&mut self,
module_path: &'static str,
base_name: &str,
) -> NamedReservation {
let key = NamedKey {
module_path,
base_name: base_name.to_string(),
};
if let Some(&existing) = self.named_interner.get(&key) {
let final_name = self.get_type(existing).expect("ref valid").name.clone();
return NamedReservation {
type_ref: existing,
key,
final_name,
needs_fill: false,
};
}
let final_name = self.reserve_unique_name(base_name, module_path);
let type_ref = self.insert_placeholder_entry(key.clone(), final_name.clone());
NamedReservation {
type_ref,
key,
final_name,
needs_fill: true,
}
}
fn insert_placeholder_entry(&mut self, key: NamedKey, final_name: String) -> TypeRef {
let slot_idx = self.named_entries.len();
let type_ref = TypeRef::new((slot_idx as u32) + 1);
self.named_entries.push(NamedEntry {
key: key.clone(),
unique_name: final_name.clone(),
ty: Type {
name: final_name.clone(),
type_params: Vec::new(),
def: TypeDef::Struct(StructDef { fields: Vec::new() }),
docs: Vec::new(),
annotations: Vec::new(),
},
});
self.named_interner.insert(key, type_ref);
self.used_names.insert(final_name, type_ref);
type_ref
}
fn cache_concrete_binding(
&mut self,
type_id: TypeId,
type_ref: TypeRef,
generics: Vec<TypeDecl>,
) {
self.concrete_cache
.insert(type_id, ConcreteTypeBinding { type_ref, generics });
}
fn fill_named_entry(
&mut self,
meta: MetaType,
type_ref: TypeRef,
key: NamedKey,
final_name: &str,
) {
let mut ty = meta.type_def(self).expect("named type must define itself");
ty.name = final_name.to_string();
self.named_entries[(type_ref.get() - 1) as usize] = NamedEntry {
key,
unique_name: final_name.to_string(),
ty,
};
}
fn reserve_unique_name(&self, base: &str, module_path: &str) -> String {
let mut candidate = base.to_string();
if self.name_is_available(&candidate) {
return candidate;
}
for segment in module_path
.rsplit("::")
.filter(|segment| !segment.is_empty())
{
candidate = to_pascal_case(segment) + &candidate;
if self.name_is_available(&candidate) {
return candidate;
}
}
let numeric_base = candidate.clone();
let mut suffix = 1;
while !self.name_is_available(&candidate) {
candidate = format!("{numeric_base}{suffix}");
suffix += 1;
}
candidate
}
fn name_is_available(&self, name: &str) -> bool {
!self.used_names.contains_key(name)
}
#[cfg(debug_assertions)]
fn assert_compatible_named_def(
&self,
existing_ref: TypeRef,
candidate: &Type,
module_path: &'static str,
base_name: &str,
) {
let existing = self.get_type(existing_ref).expect("ref valid");
assert_eq!(
existing.type_params, candidate.type_params,
"conflicting type params for {module_path}::{base_name}",
);
assert_eq!(
existing.def, candidate.def,
"conflicting type definition for {module_path}::{base_name}",
);
assert_eq!(
existing.docs, candidate.docs,
"conflicting docs for {module_path}::{base_name}",
);
assert_eq!(
existing.annotations, candidate.annotations,
"conflicting annotations for {module_path}::{base_name}",
);
}
}
pub fn const_suffixed_name(base: &str, mut consts: Vec<(String, String)>) -> String {
consts.sort_by(|a, b| a.0.cmp(&b.0));
let mut suffix = String::new();
for (name, value) in &consts {
suffix.push_str(name);
suffix.push_str(value);
}
format!("{base}{suffix}")
}
fn to_pascal_case(value: &str) -> String {
let mut out = String::new();
let mut uppercase_next = true;
for ch in value.chars() {
if ch == '_' || ch == '-' {
uppercase_next = true;
continue;
}
if uppercase_next {
out.extend(ch.to_uppercase());
uppercase_next = false;
} else {
out.push(ch);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use core::mem::size_of;
#[test]
fn test_type_ref_niche_optimization() {
assert_eq!(size_of::<TypeRef>(), 4);
assert_eq!(size_of::<Option<TypeRef>>(), 4);
assert_eq!(size_of::<Option<u32>>(), 8);
}
#[test]
fn test_type_ref_behavior() {
let t = TypeRef::new(1);
assert_eq!(t.get(), 1);
}
#[test]
#[should_panic(expected = "Type ID must not be zero")]
fn test_type_ref_zero_panics() {
let _ = TypeRef::new(0);
}
#[test]
fn test_registry_initial_state() {
let registry = Registry::new();
assert!(registry.is_empty());
assert_eq!(registry.len(), 0);
}
#[test]
fn const_suffix_orders_params_by_name() {
let name = const_suffixed_name(
"Matrix",
alloc::vec![
("ROWS".to_string(), "3".to_string()),
("COLS".to_string(), "4".to_string()),
],
);
assert_eq!(name, "MatrixCOLS4ROWS3");
}
}