use std::collections::HashMap;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::sync::LazyLock;
use crate::pagable::error::PagableError;
use crate::values::layout::vtable::AValueVTable;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DeserTypeId(pub &'static str);
impl pagable::PagableSerialize for DeserTypeId {
fn pagable_serialize(
&self,
serializer: &mut dyn pagable::PagableSerializer,
) -> pagable::Result<()> {
let index = VTABLE_REGISTRY
.type_to_id
.get(self)
.copied()
.ok_or_else(|| {
anyhow::anyhow!("Type `{}` was not registered for serialization", self.0)
})?;
index.pagable_serialize(serializer)
}
}
impl<'de> pagable::PagableDeserialize<'de> for DeserTypeId {
fn pagable_deserialize<D: pagable::PagableDeserializer<'de> + ?Sized>(
deserializer: &mut D,
) -> pagable::Result<Self> {
let index = u32::pagable_deserialize(deserializer)?;
VTABLE_REGISTRY
.id_to_type
.get(index as usize)
.copied()
.ok_or_else(|| {
anyhow::anyhow!(
"Type index `{index}` is out of range ({} types registered for deserialization)",
VTABLE_REGISTRY.id_to_type.len()
)
})
}
}
impl DeserTypeId {
#[inline]
pub const fn of<T: ?Sized>() -> Self {
DeserTypeId(std::any::type_name::<T>())
}
#[inline]
pub const fn as_str(&self) -> &'static str {
self.0
}
}
impl Display for DeserTypeId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(self.0, f)
}
}
pub struct VTableRegistryEntry {
pub deser_type_id: DeserTypeId,
pub vtable: &'static AValueVTable,
}
inventory::collect!(VTableRegistryEntry);
struct VtableRegistry {
by_type: HashMap<DeserTypeId, &'static AValueVTable>,
id_to_type: Vec<DeserTypeId>,
type_to_id: HashMap<DeserTypeId, u32>,
}
static VTABLE_REGISTRY: LazyLock<VtableRegistry> = LazyLock::new(|| {
let by_type: HashMap<DeserTypeId, &'static AValueVTable> =
inventory::iter::<VTableRegistryEntry>()
.map(|e| (e.deser_type_id, e.vtable))
.collect();
let mut id_to_type: Vec<DeserTypeId> = by_type.keys().copied().collect();
id_to_type.sort_unstable_by_key(|id| id.0);
let type_to_id = id_to_type
.iter()
.enumerate()
.map(|(index, id)| (*id, index as u32))
.collect();
VtableRegistry {
by_type,
id_to_type,
type_to_id,
}
});
#[allow(dead_code)]
pub fn lookup_vtable(deser_type_id: DeserTypeId) -> crate::Result<&'static AValueVTable> {
VTABLE_REGISTRY
.by_type
.get(&deser_type_id)
.copied()
.ok_or_else(|| {
PagableError::TypeNotRegistered {
type_id: deser_type_id,
}
.into()
})
}
#[cfg(test)]
pub(crate) fn registered_type_ids() -> Vec<DeserTypeId> {
VTABLE_REGISTRY.by_type.keys().copied().collect()
}
#[cfg(test)]
mod tests {
use allocative::Allocative;
use derive_more::Display;
use starlark_derive::Freeze;
use starlark_derive::NoSerialize;
use starlark_derive::StarlarkPagable;
use starlark_derive::Trace;
use starlark_derive::starlark_value;
use super::*;
use crate as starlark;
use crate::starlark_complex_value;
use crate::starlark_simple_value;
use crate::values::Coerce;
use crate::values::ProvidesStaticType;
use crate::values::StarlarkValue;
use crate::values::ValueLifetimeless;
use crate::values::ValueLike;
#[derive(
Debug,
Display,
ProvidesStaticType,
NoSerialize,
Allocative,
StarlarkPagable
)]
#[display("TestSimpleType")]
struct TestSimpleType;
starlark_simple_value!(TestSimpleType);
#[starlark_value(type = "TestSimpleType")]
impl<'v> StarlarkValue<'v> for TestSimpleType {}
#[derive(
Debug,
Display,
ProvidesStaticType,
NoSerialize,
Allocative,
Clone,
Trace,
Freeze,
Coerce,
StarlarkPagable
)]
#[display("TestComplex")]
#[repr(C)]
struct TestComplexGen<V: ValueLifetimeless> {
_value: V,
}
starlark_complex_value!(TestComplex);
#[starlark_value(type = "TestComplex")]
impl<'v, V: ValueLike<'v>> StarlarkValue<'v> for TestComplexGen<V> where Self: ProvidesStaticType<'v>
{}
#[test]
fn test_type_id_index_round_trips_through_registry() {
for id in registered_type_ids() {
let index = VTABLE_REGISTRY.type_to_id.get(&id).copied().unwrap();
assert_eq!(VTABLE_REGISTRY.id_to_type[index as usize], id);
}
}
#[test]
fn test_type_id_indices_are_dense_and_unique() {
let n = VTABLE_REGISTRY.id_to_type.len();
assert_eq!(VTABLE_REGISTRY.type_to_id.len(), n);
let mut indices: Vec<u32> = VTABLE_REGISTRY.type_to_id.values().copied().collect();
indices.sort_unstable();
assert_eq!(indices, (0..n as u32).collect::<Vec<_>>());
}
#[test]
fn test_type_id_indices_are_sorted_by_name() {
let names: Vec<&str> = VTABLE_REGISTRY.id_to_type.iter().map(|id| id.0).collect();
let mut sorted = names.clone();
sorted.sort_unstable();
assert_eq!(
names, sorted,
"ids must be assigned in sorted type-name order"
);
}
#[test]
fn test_deser_type_id_serializes_as_compact_index() -> crate::Result<()> {
use pagable::PagableDeserialize;
use pagable::PagableSerialize;
use crate::values::types::string::str_type::StarlarkStr;
let id = DeserTypeId::of::<StarlarkStr>();
let mut ser = pagable::testing::TestingSerializer::new();
id.pagable_serialize(&mut ser)
.map_err(crate::Error::new_other)?;
let bytes = ser.finish();
assert!(
bytes.len() < id.0.len(),
"expected index encoding ({} bytes) to be smaller than type name `{}` ({} bytes)",
bytes.len(),
id.0,
id.0.len(),
);
let mut de = pagable::testing::TestingDeserializer::new(&bytes);
let restored =
DeserTypeId::pagable_deserialize(&mut de).map_err(crate::Error::new_other)?;
assert_eq!(restored, id);
Ok(())
}
#[test]
fn test_simple_type_is_registered() {
let deser_type_id = DeserTypeId::of::<TestSimpleType>();
let vtable = lookup_vtable(deser_type_id);
assert!(
vtable.is_ok(),
"Expected TestSimpleType to be registered. Available types: {:?}",
registered_type_ids()
);
let vt = vtable.unwrap();
assert_eq!(vt.type_name, "TestSimpleType");
}
#[test]
fn test_complex_type_frozen_is_registered() {
let type_id = DeserTypeId::of::<FrozenTestComplex>();
let vtable = lookup_vtable(type_id);
assert!(
vtable.is_ok(),
"Expected FrozenTestComplex to be registered. Available types: {:?}",
registered_type_ids()
);
let vt = vtable.unwrap();
assert_eq!(vt.type_name, "TestComplex");
}
#[test]
fn test_lookup_nonexistent_type() {
let result = lookup_vtable(DeserTypeId("this_type_does_not_exist_12345"));
assert!(result.is_err());
match result {
Err(err) => match err.kind() {
crate::ErrorKind::Other(e) => {
let pagable_err = e.downcast_ref::<PagableError>().unwrap();
assert!(
matches!(pagable_err, PagableError::TypeNotRegistered { .. }),
"Expected TypeNotRegistered error"
);
}
_ => panic!("Expected ErrorKind::Other"),
},
Ok(_) => panic!("Expected error, got Ok"),
}
}
#[test]
fn test_starlark_str_is_registered() {
use crate::values::types::string::str_type::StarlarkStr;
let type_id = DeserTypeId::of::<StarlarkStr>();
let vtable = lookup_vtable(type_id);
assert!(
vtable.is_ok(),
"Expected StarlarkStr to be registered. Available types: {:?}",
registered_type_ids()
);
let vt = vtable.unwrap();
assert!(vt.is_str);
}
#[test]
fn test_frozen_tuple_is_registered() {
use crate::values::types::tuple::value::FrozenTuple;
let type_id = DeserTypeId::of::<FrozenTuple>();
let vtable = lookup_vtable(type_id);
assert!(
vtable.is_ok(),
"Expected FrozenTuple to be registered. Available types: {:?}",
registered_type_ids()
);
let vt = vtable.unwrap();
assert_eq!(vt.type_name, "tuple");
}
#[test]
fn test_frozen_list_is_registered() {
use crate::values::list::value::ListGen;
use crate::values::types::list::value::FrozenListData;
let type_id = DeserTypeId::of::<ListGen<FrozenListData>>();
let vtable = lookup_vtable(type_id);
assert!(
vtable.is_ok(),
"Expected ListGen<FrozenListData> to be registered. Available types: {:?}",
registered_type_ids()
);
let vt = vtable.unwrap();
assert_eq!(vt.type_name, "list");
}
#[test]
fn test_type_compiled_non_generic_matcher_is_registered() {
use crate::values::typing::type_compiled::compiled::TypeCompiledImplAsStarlarkValue;
use crate::values::typing::type_compiled::matchers::IsAnyOf;
let type_id = DeserTypeId::of::<TypeCompiledImplAsStarlarkValue<IsAnyOf>>();
let vtable = lookup_vtable(type_id);
assert!(
vtable.is_ok(),
"Expected TypeCompiledImplAsStarlarkValue<IsAnyOf> to be registered. Available types: {:?}",
registered_type_ids()
);
}
#[test]
fn test_type_compiled_generic_matcher_is_registered() {
use crate::values::typing::type_compiled::compiled::TypeCompiledImplAsStarlarkValue;
use crate::values::typing::type_compiled::matcher::TypeMatcherBox;
use crate::values::typing::type_compiled::matchers::IsListOf;
let type_id =
DeserTypeId::of::<TypeCompiledImplAsStarlarkValue<IsListOf<TypeMatcherBox>>>();
let vtable = lookup_vtable(type_id);
assert!(
vtable.is_ok(),
"Expected TypeCompiledImplAsStarlarkValue<IsListOf<TypeMatcherBox>> to be registered. Available types: {:?}",
registered_type_ids()
);
}
}