use std::collections::HashMap;
use std::hash::Hasher;
use std::sync::LazyLock;
use pagable::Pagable;
use super::registry::StaticValueEntry;
use super::static_string::STATIC_STRING_MAPS;
use crate::values::FrozenValue;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Pagable)]
pub(crate) struct StaticValueId(u64);
impl StaticValueId {
#[inline]
pub(crate) fn new(hash: u64) -> Self {
Self(hash)
}
}
pub(crate) fn hash_short_string(byte: u8) -> StaticValueId {
let mut hasher = starlark_map::StarlarkHasher::new();
hasher.write(b"__short_string__");
hasher.write_u8(byte);
StaticValueId::new(hasher.finish())
}
pub(crate) fn hash_empty_string() -> StaticValueId {
let mut hasher = starlark_map::StarlarkHasher::new();
hasher.write(b"__empty_string__");
StaticValueId::new(hasher.finish())
}
pub(crate) fn hash_file_line(file: &str, line: u32) -> StaticValueId {
let mut hasher = starlark_map::StarlarkHasher::new();
hasher.write(file.as_bytes());
hasher.write_u32(line);
StaticValueId::new(hasher.finish())
}
struct StaticValueMaps {
addr_to_id: HashMap<usize, StaticValueId>,
id_to_value: HashMap<StaticValueId, FrozenValue>,
}
static STATIC_VALUE_MAPS: LazyLock<StaticValueMaps> = LazyLock::new(|| {
let string_maps = &*STATIC_STRING_MAPS;
let mut addr_to_id = string_maps.addr_to_id.clone();
let mut id_to_value = string_maps.id_to_value.clone();
for e in inventory::iter::<StaticValueEntry>() {
let fv = (e.get_value)();
let id = hash_file_line(e.file, e.line);
let addr = fv.ptr_value().ptr_value_untagged();
addr_to_id.insert(addr, id);
id_to_value.insert(id, fv);
}
StaticValueMaps {
addr_to_id,
id_to_value,
}
});
pub(crate) fn get_frozen_value_by_static_id(id: StaticValueId) -> Option<FrozenValue> {
STATIC_VALUE_MAPS.id_to_value.get(&id).copied()
}
pub(crate) fn get_static_value_id(fv: FrozenValue) -> Option<StaticValueId> {
if fv.ptr_value().is_int() {
return None;
}
let addr = fv.ptr_value().ptr_value_untagged();
STATIC_VALUE_MAPS.addr_to_id.get(&addr).copied()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::const_frozen_string;
use crate::values::layout::static_string::constant_string;
#[test]
fn test_empty_string_has_id() {
let empty = constant_string("").unwrap();
let id = get_static_value_id(empty.to_frozen_value());
assert!(id.is_some(), "empty string should be registered");
}
#[test]
fn test_single_char_has_id() {
let a = constant_string("a").unwrap();
let id = get_static_value_id(a.to_frozen_value());
assert!(id.is_some(), "'a' should be registered");
}
#[test]
fn test_multi_char_string_is_registered() {
let s = const_frozen_string!("hello");
let id = get_static_value_id(s.to_frozen_value());
assert!(id.is_some(), "const_frozen_string! should be registered");
}
#[test]
fn test_all_ascii_chars_registered() {
for i in 0u8..128 {
let bytes = [i];
let s = std::str::from_utf8(&bytes).unwrap();
let fv = constant_string(s).unwrap();
let id = get_static_value_id(fv.to_frozen_value());
assert!(id.is_some(), "ASCII char {} should be registered", i);
}
}
#[test]
fn test_all_short_string_ids_unique() {
let mut ids = std::collections::HashSet::new();
let empty = constant_string("").unwrap();
ids.insert(get_static_value_id(empty.to_frozen_value()).unwrap());
for i in 0u8..128 {
let bytes = [i];
let s = std::str::from_utf8(&bytes).unwrap();
let fv = constant_string(s).unwrap();
ids.insert(get_static_value_id(fv.to_frozen_value()).unwrap());
}
assert_eq!(
ids.len(),
129,
"all 129 short strings should have unique IDs"
);
}
#[test]
fn test_determinism() {
let a1 = constant_string("a").unwrap();
let a2 = constant_string("a").unwrap();
let id1 = get_static_value_id(a1.to_frozen_value());
let id2 = get_static_value_id(a2.to_frozen_value());
assert_eq!(id1, id2, "same value should produce same ID");
}
#[test]
fn test_singleton_none_is_registered() {
let none_val = FrozenValue::new_none();
let id = get_static_value_id(none_val);
assert!(id.is_some(), "None should be registered");
}
#[test]
fn test_singleton_bools_are_registered() {
let false_val = FrozenValue::new_bool(false);
let true_val = FrozenValue::new_bool(true);
let false_id = get_static_value_id(false_val);
let true_id = get_static_value_id(true_val);
assert!(false_id.is_some(), "false should be registered");
assert!(true_id.is_some(), "true should be registered");
assert_ne!(
false_id, true_id,
"true and false should have different IDs"
);
}
#[test]
fn test_singleton_empty_tuple_is_registered() {
let empty_tuple = FrozenValue::new_empty_tuple();
let id = get_static_value_id(empty_tuple);
assert!(id.is_some(), "empty tuple should be registered");
}
#[test]
fn test_singleton_empty_array_is_registered() {
use crate::values::types::array::VALUE_EMPTY_ARRAY;
let empty_array = VALUE_EMPTY_ARRAY.unpack().to_frozen_value();
let id = get_static_value_id(empty_array);
assert!(id.is_some(), "empty array should be registered");
}
#[test]
fn test_type_compiled_any_is_registered() {
use crate::values::typing::type_compiled::compiled::TypeCompiled;
let type_any = TypeCompiled::any();
let id = get_static_value_id(type_any.to_inner());
assert!(id.is_some(), "TypeCompiled::any() should be registered");
}
}