use std::sync::Arc;
use allocative::Allocative;
use derive_more::Display;
use pagable::PagableDeserialize;
use pagable::PagableSerialize;
use starlark_derive::NoSerialize;
use starlark_derive::ProvidesStaticType;
use starlark_derive::StarlarkPagable;
use starlark_derive::starlark_value;
use starlark_syntax::codemap::CodeMap;
use starlark_syntax::codemap::FileSpan;
use starlark_syntax::codemap::NativeCodeMap;
use strong_hash::StrongHash;
use crate as starlark;
use crate::const_frozen_string;
use crate::starlark_simple_value;
use crate::values::FrozenHeap;
use crate::values::FrozenHeapRef;
use crate::values::FrozenValue;
use crate::values::OwnedFrozenValue;
use crate::values::OwnedFrozenValueTyped;
use crate::values::StarlarkValue;
use crate::values::ValueLike;
use crate::values::layout::heap::heap_type::FrozenHeapName;
#[derive(Debug, StrongHash)]
struct TestHeapName(String);
impl TestHeapName {
fn heap_name(name: &str) -> FrozenHeapName {
FrozenHeapName::User(Box::new(Self(name.to_owned())))
}
}
#[derive(
Debug,
Display,
Allocative,
ProvidesStaticType,
NoSerialize,
StarlarkPagable
)]
#[display("SimpleData({}, {})", self.flag, self.count)]
struct SimpleData {
flag: bool,
count: usize,
}
starlark_simple_value!(SimpleData);
#[starlark_value(type = "SimpleData", skip_pagable)]
impl<'v> StarlarkValue<'v> for SimpleData {
type Canonical = Self;
}
fn round_trip_heap_ref(heap_ref: &FrozenHeapRef) -> crate::Result<FrozenHeapRef> {
let mut ser = pagable::testing::TestingSerializer::new();
heap_ref
.pagable_serialize(&mut ser)
.map_err(crate::Error::new_other)?;
let bytes = ser.finish();
let mut de = pagable::testing::TestingDeserializer::new(&bytes);
let restored = FrozenHeapRef::pagable_deserialize(&mut de).map_err(crate::Error::new_other)?;
Ok(restored)
}
#[derive(
Debug,
Display,
Allocative,
ProvidesStaticType,
NoSerialize,
StarlarkPagable
)]
#[display("HeapData({:?}, {:?}, {:?})", self.items, self.label, self.boxed)]
struct HeapData {
items: Vec<u32>,
label: String,
boxed: Box<i64>,
}
starlark_simple_value!(HeapData);
#[starlark_value(type = "HeapData", skip_pagable)]
impl<'v> StarlarkValue<'v> for HeapData {
type Canonical = Self;
}
#[test]
fn test_simple_value_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
heap.alloc_simple(SimpleData {
flag: true,
count: 42,
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_simple_value"));
let restored = round_trip_heap_ref(&heap_ref)?;
let headers = restored.collect_undrop_headers_ordered();
assert_eq!(headers.len(), 1);
let avalue = headers[0].unpack();
let data: &SimpleData = avalue.downcast_ref().unwrap();
assert_eq!(data.flag, true);
assert_eq!(data.count, 42);
Ok(())
}
#[test]
fn test_heap_allocated_value_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
heap.alloc_simple(HeapData {
items: vec![10, 20, 30],
label: "hello".to_owned(),
boxed: Box::new(-99),
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_heap_data_round_trip"));
let restored = round_trip_heap_ref(&heap_ref)?;
let headers = restored.collect_drop_headers_ordered();
assert_eq!(headers.len(), 1);
let avalue = headers[0].unpack();
let data: &HeapData = avalue.downcast_ref().unwrap();
assert_eq!(data.items, vec![10, 20, 30]);
assert_eq!(data.label, "hello");
assert_eq!(*data.boxed, -99);
Ok(())
}
#[derive(
Debug,
Display,
Allocative,
ProvidesStaticType,
NoSerialize,
StarlarkPagable
)]
#[display("RefData({})", self.label)]
struct RefData {
label: usize,
target: FrozenValue,
}
starlark_simple_value!(RefData);
#[starlark_value(type = "RefData", skip_pagable)]
impl<'v> StarlarkValue<'v> for RefData {
type Canonical = Self;
}
#[test]
fn test_frozen_value_ref_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
let target_fv = heap.alloc_simple(SimpleData {
flag: true,
count: 99,
});
heap.alloc_simple(RefData {
label: 7,
target: target_fv,
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test"));
let restored = round_trip_heap_ref(&heap_ref)?;
let headers = restored.collect_undrop_headers_ordered();
assert_eq!(headers.len(), 2);
let target_data: &SimpleData = headers[0].unpack().downcast_ref().unwrap();
assert_eq!(target_data.flag, true);
assert_eq!(target_data.count, 99);
let ref_data: &RefData = headers[1].unpack().downcast_ref().unwrap();
assert_eq!(ref_data.label, 7);
let resolved: &SimpleData = ref_data
.target
.to_value()
.downcast_ref::<SimpleData>()
.expect("FrozenValue should point to SimpleData");
assert_eq!(resolved.flag, true);
assert_eq!(resolved.count, 99);
let target_header_addr = headers[0] as *const _ as usize;
let fv_ptr = ref_data.target.ptr_value().ptr_value_untagged();
assert_eq!(fv_ptr, target_header_addr);
Ok(())
}
#[derive(
Debug,
Display,
Allocative,
ProvidesStaticType,
NoSerialize,
StarlarkPagable
)]
#[display("DropRefData({:?})", self.items)]
struct DropRefData {
items: Vec<u32>,
target: FrozenValue,
}
starlark_simple_value!(DropRefData);
#[starlark_value(type = "DropRefData", skip_pagable)]
impl<'v> StarlarkValue<'v> for DropRefData {
type Canonical = Self;
}
#[test]
fn test_frozen_value_drop_to_undrop_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
let target_fv = heap.alloc_simple(SimpleData {
flag: false,
count: 123,
});
heap.alloc_simple(DropRefData {
items: vec![1, 2, 3],
target: target_fv,
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test"));
let restored = round_trip_heap_ref(&heap_ref)?;
let drop_headers = restored.collect_drop_headers_ordered();
assert_eq!(drop_headers.len(), 1);
let drop_ref_data: &DropRefData = drop_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(drop_ref_data.items, vec![1, 2, 3]);
let undrop_headers = restored.collect_undrop_headers_ordered();
assert_eq!(undrop_headers.len(), 1);
let target_data: &SimpleData = undrop_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(target_data.flag, false);
assert_eq!(target_data.count, 123);
let resolved: &SimpleData = drop_ref_data
.target
.to_value()
.downcast_ref::<SimpleData>()
.expect("FrozenValue should point to SimpleData");
assert_eq!(resolved.flag, false);
assert_eq!(resolved.count, 123);
let target_header_addr = undrop_headers[0] as *const _ as usize;
let fv_ptr = drop_ref_data.target.ptr_value().ptr_value_untagged();
assert_eq!(fv_ptr, target_header_addr);
Ok(())
}
#[test]
fn test_frozen_value_undrop_to_drop_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
let target_fv = heap.alloc_simple(HeapData {
items: vec![10, 20],
label: "target".to_owned(),
boxed: Box::new(42),
});
heap.alloc_simple(RefData {
label: 5,
target: target_fv,
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test"));
let restored = round_trip_heap_ref(&heap_ref)?;
let drop_headers = restored.collect_drop_headers_ordered();
assert_eq!(drop_headers.len(), 1);
let target_data: &HeapData = drop_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(target_data.items, vec![10, 20]);
assert_eq!(target_data.label, "target");
assert_eq!(*target_data.boxed, 42);
let undrop_headers = restored.collect_undrop_headers_ordered();
assert_eq!(undrop_headers.len(), 1);
let ref_data: &RefData = undrop_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(ref_data.label, 5);
let resolved: &HeapData = ref_data
.target
.to_value()
.downcast_ref::<HeapData>()
.expect("FrozenValue should point to HeapData");
assert_eq!(resolved.items, vec![10, 20]);
assert_eq!(resolved.label, "target");
assert_eq!(*resolved.boxed, 42);
let target_header_addr = drop_headers[0] as *const _ as usize;
let fv_ptr = ref_data.target.ptr_value().ptr_value_untagged();
assert_eq!(fv_ptr, target_header_addr);
Ok(())
}
#[test]
fn test_frozen_list_round_trip() -> crate::Result<()> {
use crate::values::list::value::ListGen;
use crate::values::types::list::value::FrozenListData;
let heap = FrozenHeap::new();
let a = heap.alloc_simple(SimpleData {
flag: true,
count: 10,
});
let b = heap.alloc_simple(SimpleData {
flag: false,
count: 20,
});
heap.alloc_list(&[a, b]);
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test"));
let restored = round_trip_heap_ref(&heap_ref)?;
let undrop_headers = restored.collect_undrop_headers_ordered();
assert_eq!(undrop_headers.len(), 3);
let a_data: &SimpleData = undrop_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(a_data.flag, true);
assert_eq!(a_data.count, 10);
let b_data: &SimpleData = undrop_headers[1].unpack().downcast_ref().unwrap();
assert_eq!(b_data.flag, false);
assert_eq!(b_data.count, 20);
let list_value: &ListGen<FrozenListData> = undrop_headers[2].unpack().downcast_ref().unwrap();
let content = list_value.0.content();
assert_eq!(content.len(), 2);
let elem_a: &SimpleData = content[0]
.to_value()
.downcast_ref::<SimpleData>()
.expect("element 0 should be SimpleData");
assert_eq!(elem_a.flag, true);
assert_eq!(elem_a.count, 10);
let elem_b: &SimpleData = content[1]
.to_value()
.downcast_ref::<SimpleData>()
.expect("element 1 should be SimpleData");
assert_eq!(elem_b.flag, false);
assert_eq!(elem_b.count, 20);
assert_eq!(
content[0].ptr_value().ptr_value_untagged(),
undrop_headers[0] as *const _ as usize
);
assert_eq!(
content[1].ptr_value().ptr_value_untagged(),
undrop_headers[1] as *const _ as usize
);
Ok(())
}
#[test]
fn test_frozen_tuple_round_trip() -> crate::Result<()> {
use crate::values::types::tuple::value::FrozenTuple;
let heap = FrozenHeap::new();
let a = heap.alloc_simple(SimpleData {
flag: true,
count: 100,
});
let b = heap.alloc_simple(SimpleData {
flag: false,
count: 200,
});
heap.alloc_tuple(&[a, b]);
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test"));
let restored = round_trip_heap_ref(&heap_ref)?;
let undrop_headers = restored.collect_undrop_headers_ordered();
assert_eq!(undrop_headers.len(), 3);
let a_data: &SimpleData = undrop_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(a_data.flag, true);
assert_eq!(a_data.count, 100);
let b_data: &SimpleData = undrop_headers[1].unpack().downcast_ref().unwrap();
assert_eq!(b_data.flag, false);
assert_eq!(b_data.count, 200);
let tuple_value: &FrozenTuple = undrop_headers[2].unpack().downcast_ref().unwrap();
let content = tuple_value.content();
assert_eq!(content.len(), 2);
let elem_a: &SimpleData = content[0]
.to_value()
.downcast_ref::<SimpleData>()
.expect("element 0 should be SimpleData");
assert_eq!(elem_a.flag, true);
assert_eq!(elem_a.count, 100);
let elem_b: &SimpleData = content[1]
.to_value()
.downcast_ref::<SimpleData>()
.expect("element 1 should be SimpleData");
assert_eq!(elem_b.flag, false);
assert_eq!(elem_b.count, 200);
assert_eq!(
content[0].ptr_value().ptr_value_untagged(),
undrop_headers[0] as *const _ as usize
);
assert_eq!(
content[1].ptr_value().ptr_value_untagged(),
undrop_headers[1] as *const _ as usize
);
Ok(())
}
#[test]
fn test_frozen_str_value_round_trip() -> crate::Result<()> {
use crate::values::string::str_type::StarlarkStr;
let heap = FrozenHeap::new();
let str_fv = heap.alloc_str("hello world").to_frozen_value();
heap.alloc_simple(RefData {
label: 42,
target: str_fv,
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test"));
let restored = round_trip_heap_ref(&heap_ref)?;
let undrop_headers = restored.collect_undrop_headers_ordered();
assert_eq!(undrop_headers.len(), 2);
let restored_str = undrop_headers[0]
.unpack()
.downcast_ref::<StarlarkStr>()
.unwrap();
assert_eq!(restored_str.as_str(), "hello world");
let ref_data: &RefData = undrop_headers[1].unpack().downcast_ref().unwrap();
assert_eq!(ref_data.label, 42);
assert!(ref_data.target.is_str());
assert_eq!(ref_data.target.unpack_str().unwrap(), "hello world");
assert_eq!(
ref_data.target.ptr_value().ptr_value_untagged(),
undrop_headers[0] as *const _ as usize
);
Ok(())
}
#[test]
fn test_frozen_value_inline_int_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
let int_fv = FrozenValue::testing_new_int(42);
heap.alloc_simple(RefData {
label: 1,
target: int_fv,
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test"));
let restored = round_trip_heap_ref(&heap_ref)?;
let undrop_headers = restored.collect_undrop_headers_ordered();
assert_eq!(undrop_headers.len(), 1);
let ref_data: &RefData = undrop_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(ref_data.label, 1);
assert_eq!(ref_data.target.unpack_i32(), Some(42));
Ok(())
}
#[test]
fn test_cross_heap_frozen_value_round_trip() -> crate::Result<()> {
let dep_heap = FrozenHeap::new();
let dep_fv = dep_heap.alloc_simple(SimpleData {
flag: true,
count: 77,
});
let dep_heap_ref = dep_heap.into_ref_named(TestHeapName::heap_name("dep"));
let main_heap = FrozenHeap::new();
main_heap.add_reference(&dep_heap_ref);
main_heap.alloc_simple(RefData {
label: 99,
target: dep_fv,
});
let main_heap_ref = main_heap.into_ref_named(TestHeapName::heap_name("main"));
let restored = round_trip_heap_ref(&main_heap_ref)?;
let undrop_headers = restored.collect_undrop_headers_ordered();
assert_eq!(undrop_headers.len(), 1);
let ref_data: &RefData = undrop_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(ref_data.label, 99);
let resolved: &SimpleData = ref_data
.target
.to_value()
.downcast_ref::<SimpleData>()
.expect("FrozenValue should point to SimpleData in dep heap");
assert_eq!(resolved.flag, true);
assert_eq!(resolved.count, 77);
let refs: Vec<_> = restored.refs().collect();
assert_eq!(refs.len(), 1);
let restored_dep_headers = refs[0].collect_undrop_headers_ordered();
assert_eq!(restored_dep_headers.len(), 1);
assert_eq!(
ref_data.target.ptr_value().ptr_value_untagged(),
restored_dep_headers[0] as *const _ as usize
);
Ok(())
}
#[test]
fn test_heap_ref_dedup_round_trip() -> crate::Result<()> {
let heap_d = FrozenHeap::new();
let d_fv = heap_d.alloc_simple(SimpleData {
flag: true,
count: 1,
});
let heap_d_ref = heap_d.into_ref_named(TestHeapName::heap_name("d"));
let heap_b = FrozenHeap::new();
heap_b.add_reference(&heap_d_ref);
heap_b.alloc_simple(RefData {
label: 2,
target: d_fv,
});
let heap_b_ref = heap_b.into_ref_named(TestHeapName::heap_name("b"));
let heap_c = FrozenHeap::new();
heap_c.add_reference(&heap_d_ref);
heap_c.alloc_simple(RefData {
label: 3,
target: d_fv,
});
let heap_c_ref = heap_c.into_ref_named(TestHeapName::heap_name("c"));
let heap_a = FrozenHeap::new();
heap_a.add_reference(&heap_b_ref);
heap_a.add_reference(&heap_c_ref);
heap_a.alloc_simple(SimpleData {
flag: false,
count: 4,
});
let heap_a_ref = heap_a.into_ref_named(TestHeapName::heap_name("a"));
let restored = round_trip_heap_ref(&heap_a_ref)?;
let a_refs: Vec<_> = restored.refs().collect();
assert_eq!(a_refs.len(), 2);
let restored_b = &a_refs[0];
let restored_c = &a_refs[1];
let b_refs: Vec<_> = restored_b.refs().collect();
let c_refs: Vec<_> = restored_c.refs().collect();
assert_eq!(b_refs.len(), 1);
assert_eq!(c_refs.len(), 1);
assert_eq!(b_refs[0], c_refs[0]);
let d_headers = b_refs[0].collect_undrop_headers_ordered();
assert_eq!(d_headers.len(), 1);
let d_data: &SimpleData = d_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(d_data.flag, true);
assert_eq!(d_data.count, 1);
let b_headers = restored_b.collect_undrop_headers_ordered();
assert_eq!(b_headers.len(), 1);
let b_data: &RefData = b_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(b_data.label, 2);
assert_eq!(
b_data.target.ptr_value().ptr_value_untagged(),
d_headers[0] as *const _ as usize
);
let c_headers = restored_c.collect_undrop_headers_ordered();
assert_eq!(c_headers.len(), 1);
let c_data: &RefData = c_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(c_data.label, 3);
assert_eq!(
c_data.target.ptr_value().ptr_value_untagged(),
d_headers[0] as *const _ as usize
);
Ok(())
}
#[test]
fn test_frozen_value_typed_round_trip() -> crate::Result<()> {
use crate::values::FrozenValueTyped;
let heap = FrozenHeap::new();
let fv = heap.alloc_simple(SimpleData {
flag: true,
count: 77,
});
let typed = FrozenValueTyped::<SimpleData>::new(fv).unwrap();
heap.alloc_simple(RefData {
label: 3,
target: typed.to_frozen_value(),
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_fvt"));
let restored = round_trip_heap_ref(&heap_ref)?;
let undrop_headers = restored.collect_undrop_headers_ordered();
assert_eq!(undrop_headers.len(), 2);
let ref_data: &RefData = undrop_headers[1].unpack().downcast_ref().unwrap();
let restored_typed = FrozenValueTyped::<SimpleData>::new(ref_data.target).unwrap();
assert_eq!(restored_typed.flag, true);
assert_eq!(restored_typed.count, 77);
Ok(())
}
#[derive(
Debug,
Display,
Allocative,
ProvidesStaticType,
NoSerialize,
StarlarkPagable
)]
#[display("SmallMapData")]
struct SmallMapData {
entries: starlark_map::small_map::SmallMap<String, FrozenValue>,
}
starlark_simple_value!(SmallMapData);
#[starlark_value(type = "SmallMapData", skip_pagable)]
impl<'v> StarlarkValue<'v> for SmallMapData {
type Canonical = Self;
}
#[derive(
Debug,
Display,
Allocative,
ProvidesStaticType,
NoSerialize,
StarlarkPagable
)]
#[display("SmallMapFvData")]
struct SmallMapFvData {
entries: starlark_map::small_map::SmallMap<FrozenValue, FrozenValue>,
}
starlark_simple_value!(SmallMapFvData);
#[starlark_value(type = "SmallMapFvData", skip_pagable)]
impl<'v> StarlarkValue<'v> for SmallMapFvData {
type Canonical = Self;
}
#[test]
fn test_small_map_string_key_round_trip() -> crate::Result<()> {
use starlark_map::small_map::SmallMap;
let heap = FrozenHeap::new();
let v1 = heap.alloc_simple(SimpleData {
flag: true,
count: 10,
});
let v2 = heap.alloc_simple(SimpleData {
flag: false,
count: 20,
});
let mut entries = SmallMap::new();
entries.insert("beta".to_owned(), v2);
entries.insert("alpha".to_owned(), v1);
heap.alloc_simple(SmallMapData { entries });
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_sm_string"));
let restored = round_trip_heap_ref(&heap_ref)?;
let drop_headers = restored.collect_drop_headers_ordered();
assert_eq!(drop_headers.len(), 1);
let map_data: &SmallMapData = drop_headers[0].unpack().downcast_ref().unwrap();
let keys: Vec<&str> = map_data.entries.iter().map(|(k, _)| k.as_str()).collect();
assert_eq!(keys, vec!["beta", "alpha"]);
let v1_restored: &SimpleData = map_data
.entries
.get("alpha")
.unwrap()
.downcast_ref::<SimpleData>()
.unwrap();
assert_eq!(v1_restored.count, 10);
let v2_restored: &SimpleData = map_data
.entries
.get("beta")
.unwrap()
.downcast_ref::<SimpleData>()
.unwrap();
assert_eq!(v2_restored.count, 20);
Ok(())
}
#[test]
fn test_small_map_frozen_value_key_backward_ref() -> crate::Result<()> {
use starlark_map::small_map::SmallMap;
use crate::values::ValueLike;
let heap = FrozenHeap::new();
let k1 = heap.alloc_str("key_one").to_frozen_value();
let k2 = heap.alloc_str("key_two").to_frozen_value();
let v1 = heap.alloc_simple(HeapData {
items: vec![100],
label: "val_one".to_owned(),
boxed: Box::new(1),
});
let v2 = heap.alloc_simple(HeapData {
items: vec![200],
label: "val_two".to_owned(),
boxed: Box::new(2),
});
let mut entries = SmallMap::new();
entries.insert_hashed(k1.get_hashed()?, v1);
entries.insert_hashed(k2.get_hashed()?, v2);
heap.alloc_simple(SmallMapFvData { entries });
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_sm_fv_key"));
let restored = round_trip_heap_ref(&heap_ref)?;
let drop_headers = restored.collect_drop_headers_ordered();
assert_eq!(drop_headers.len(), 3);
let map_data: &SmallMapFvData = drop_headers[2].unpack().downcast_ref().unwrap();
assert_eq!(map_data.entries.len(), 2);
let key_strs: Vec<&str> = map_data
.entries
.iter()
.map(|(k, _)| k.unpack_str().unwrap())
.collect();
assert_eq!(key_strs, vec!["key_one", "key_two"]);
let val_labels: Vec<&str> = map_data
.entries
.iter()
.map(|(_, v)| v.downcast_ref::<HeapData>().unwrap().label.as_str())
.collect();
assert_eq!(val_labels, vec!["val_one", "val_two"]);
for (k, _) in map_data.entries.iter() {
let hashed = k.get_hashed()?;
assert!(
map_data.entries.get_hashed(hashed.as_ref()).is_some(),
"lookup by key {:?} should succeed",
k.unpack_str()
);
}
Ok(())
}
#[test]
fn test_small_map_frozen_value_key_forward_ref() -> crate::Result<()> {
use starlark_map::small_map::SmallMap;
use crate::values::ValueLike;
let heap = FrozenHeap::new();
let k1 = heap.alloc_str("hello").to_frozen_value();
let k2 = heap.alloc_str("world").to_frozen_value();
let v1 = FrozenValue::testing_new_int(111);
let v2 = FrozenValue::testing_new_int(222);
let mut entries = SmallMap::new();
entries.insert_hashed(k1.get_hashed()?, v1);
entries.insert_hashed(k2.get_hashed()?, v2);
heap.alloc_simple(SmallMapFvData { entries });
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_forward_ref"));
let restored = round_trip_heap_ref(&heap_ref)?;
let drop_headers = restored.collect_drop_headers_ordered();
assert_eq!(drop_headers.len(), 1);
let undrop_headers = restored.collect_undrop_headers_ordered();
assert_eq!(undrop_headers.len(), 2);
let map_data: &SmallMapFvData = drop_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(map_data.entries.len(), 2);
let key_strs: Vec<&str> = map_data
.entries
.iter()
.map(|(k, _)| k.unpack_str().unwrap())
.collect();
assert_eq!(key_strs, vec!["hello", "world"]);
let values: Vec<i32> = map_data
.entries
.iter()
.map(|(_, v)| v.unpack_i32().unwrap())
.collect();
assert_eq!(values, vec![111, 222]);
for (k, _) in map_data.entries.iter() {
let hashed = k.get_hashed()?;
assert!(
map_data.entries.get_hashed(hashed.as_ref()).is_some(),
"lookup by key {:?} should succeed",
k.unpack_str()
);
}
let key_ptrs: Vec<usize> = map_data
.entries
.iter()
.map(|(k, _)| k.ptr_value().ptr_value_untagged())
.collect();
assert_eq!(key_ptrs[0], undrop_headers[0] as *const _ as usize);
assert_eq!(key_ptrs[1], undrop_headers[1] as *const _ as usize);
Ok(())
}
#[test]
fn test_range_round_trip() -> crate::Result<()> {
use std::num::NonZeroI32;
use crate::values::types::range::Range;
let heap = FrozenHeap::new();
heap.alloc_simple(Range::new(1, 10, NonZeroI32::new(2).unwrap()));
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_range"));
let restored = round_trip_heap_ref(&heap_ref)?;
let undrop_headers = restored.collect_undrop_headers_ordered();
assert_eq!(undrop_headers.len(), 1);
let range: &Range = undrop_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(format!("{}", range), "range(1, 10, 2)");
Ok(())
}
#[test]
fn test_owned_frozen_value_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
let fv = heap.alloc_simple(SimpleData {
flag: true,
count: 42,
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_owned"));
let owned = unsafe { OwnedFrozenValue::new(heap_ref, fv) };
let mut ser = pagable::testing::TestingSerializer::new();
owned
.pagable_serialize(&mut ser)
.map_err(crate::Error::new_other)?;
let bytes = ser.finish();
let mut de = pagable::testing::TestingDeserializer::new(&bytes);
let restored =
OwnedFrozenValue::pagable_deserialize(&mut de).map_err(crate::Error::new_other)?;
let simple: &SimpleData = restored
.value()
.downcast_ref()
.expect("should be SimpleData");
assert_eq!(simple.flag, true);
assert_eq!(simple.count, 42);
Ok(())
}
#[test]
fn test_owned_frozen_value_typed_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
let fv = heap.alloc_simple(SimpleData {
flag: false,
count: 99,
});
let typed_fv =
crate::values::FrozenValueTyped::<SimpleData>::new(fv).expect("should be SimpleData");
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_owned_typed"));
let owned = unsafe { OwnedFrozenValueTyped::new(heap_ref, typed_fv) };
let mut ser = pagable::testing::TestingSerializer::new();
owned
.pagable_serialize(&mut ser)
.map_err(crate::Error::new_other)?;
let bytes = ser.finish();
let mut de = pagable::testing::TestingDeserializer::new(&bytes);
let restored = OwnedFrozenValueTyped::<SimpleData>::pagable_deserialize(&mut de)
.map_err(crate::Error::new_other)?;
assert_eq!(restored.flag, false);
assert_eq!(restored.count, 99);
Ok(())
}
#[derive(
Debug,
Display,
Allocative,
ProvidesStaticType,
NoSerialize,
StarlarkPagable
)]
#[display("TestStackFrame({}, {:?})", self.name, self.location)]
struct TestStackFrame {
name: String,
#[starlark_pagable(pagable)]
location: Option<FileSpan>,
}
starlark_simple_value!(TestStackFrame);
#[starlark_value(type = "TestStackFrame", skip_pagable)]
impl<'v> StarlarkValue<'v> for TestStackFrame {
type Canonical = Self;
}
#[test]
fn test_stack_frame_data_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
heap.alloc_simple(TestStackFrame {
name: "native_func".to_owned(),
location: None,
});
let codemap = CodeMap::new(
"test.bzl".to_owned(),
"load('foo')\ndef bar():\n pass\n".to_owned(),
);
let span = codemap.full_span();
heap.alloc_simple(TestStackFrame {
name: "bar".to_owned(),
location: Some(FileSpan {
file: codemap,
span,
}),
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_stack_frame_data"));
let restored = round_trip_heap_ref(&heap_ref)?;
let headers = restored.collect_drop_headers_ordered();
assert_eq!(headers.len(), 2);
let data0: &TestStackFrame = headers[0].unpack().downcast_ref().unwrap();
assert_eq!(data0.name, "native_func");
assert!(data0.location.is_none());
let data1: &TestStackFrame = headers[1].unpack().downcast_ref().unwrap();
assert_eq!(data1.name, "bar");
let loc = data1.location.as_ref().expect("should have location");
assert_eq!(loc.file.filename(), "test.bzl");
assert_eq!(loc.file.source(), "load('foo')\ndef bar():\n pass\n");
Ok(())
}
#[test]
fn test_native_codemap_round_trip() -> crate::Result<()> {
static NATIVE: NativeCodeMap = NativeCodeMap::new("test_native.rs", 42, 10);
pagable::static_value!(
NATIVE_STATIC: NativeCodeMap = &NATIVE,
starlark_syntax::codemap::NativeCodeMapStaticEntry
);
let codemap = NativeCodeMap::to_codemap(NATIVE_STATIC);
let file_span = FileSpan {
file: codemap,
span: NativeCodeMap::FULL_SPAN,
};
let heap = FrozenHeap::new();
heap.alloc_simple(TestStackFrame {
name: "native_call".to_owned(),
location: Some(file_span),
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_native_codemap"));
let restored = round_trip_heap_ref(&heap_ref)?;
let headers = restored.collect_drop_headers_ordered();
assert_eq!(headers.len(), 1);
let data: &TestStackFrame = headers[0].unpack().downcast_ref().unwrap();
assert_eq!(data.name, "native_call");
let loc = data.location.as_ref().expect("should have location");
assert_eq!(loc.file.filename(), "test_native.rs");
assert_eq!(loc.file.source(), "<native>");
assert_eq!(loc.file.id(), NativeCodeMap::to_codemap(NATIVE_STATIC).id());
Ok(())
}
#[test]
fn test_frozen_dict_round_trip() -> crate::Result<()> {
use crate::values::dict::AllocDict;
use crate::values::types::dict::value::FrozenDict;
let heap = FrozenHeap::new();
heap.alloc(AllocDict([("hello", 1), ("world", 2)]));
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_frozen_dict"));
let restored = round_trip_heap_ref(&heap_ref)?;
let headers = restored.collect_drop_headers_ordered();
assert_eq!(headers.len(), 1);
let dict: &FrozenDict = headers[0].unpack().downcast_ref().unwrap();
assert_eq!(dict.0.content.len(), 2);
let keys: Vec<&str> = dict
.0
.content
.keys()
.map(|k| k.to_value().unpack_str().unwrap())
.collect();
assert!(keys.contains(&"hello"));
assert!(keys.contains(&"world"));
Ok(())
}
#[test]
fn test_frozen_struct_round_trip() -> crate::Result<()> {
use crate::values::structs::AllocStruct;
let heap = FrozenHeap::new();
heap.alloc(AllocStruct([("name", "alice"), ("age", "30")]));
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_frozen_struct"));
let restored = round_trip_heap_ref(&heap_ref)?;
let headers = restored.collect_drop_headers_ordered();
assert_eq!(headers.len(), 1);
let attrs = headers[0].unpack().dir_attr();
assert_eq!(attrs.len(), 2);
assert!(attrs.contains(&"name".to_owned()));
assert!(attrs.contains(&"age".to_owned()));
Ok(())
}
#[test]
fn test_frozen_set_round_trip() -> crate::Result<()> {
use starlark_map::small_set::SmallSet;
use crate::values::types::set::value::FrozenSet;
use crate::values::types::set::value::FrozenSetData;
use crate::values::types::set::value::SetGen;
let heap = FrozenHeap::new();
let mut content: SmallSet<FrozenValue> = SmallSet::new();
content.insert_hashed(FrozenValue::testing_new_int(1).get_hashed()?);
content.insert_hashed(FrozenValue::testing_new_int(2).get_hashed()?);
content.insert_hashed(FrozenValue::testing_new_int(3).get_hashed()?);
heap.alloc_simple(SetGen(FrozenSetData::new(content)));
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_frozen_set"));
let restored = round_trip_heap_ref(&heap_ref)?;
let headers = restored.collect_drop_headers_ordered();
assert_eq!(headers.len(), 1);
let set: &FrozenSet = headers[0].unpack().downcast_ref().unwrap();
assert_eq!(set.0.len(), 3);
let values: Vec<i32> = set.0.iter().map(|v| v.unpack_i32().unwrap()).collect();
assert!(values.contains(&1));
assert!(values.contains(&2));
assert!(values.contains(&3));
Ok(())
}
#[test]
fn test_frozen_record_type_round_trip() -> crate::Result<()> {
use std::sync::Arc;
use starlark_map::small_map::SmallMap;
use crate::eval::ParametersSpec;
use crate::eval::ParametersSpecParam;
use crate::typing::Ty;
use crate::values::record::field::FieldGen;
use crate::values::record::record_type::FrozenRecordType;
use crate::values::record::ty_record_type::TyRecordData;
use crate::values::types::type_instance_id::TypeInstanceId;
use crate::values::typing::type_compiled::compiled::TypeCompiled;
let heap = FrozenHeap::new();
let shared = Arc::new(TyRecordData {
name: "MyRec".to_owned(),
ty_record: Ty::any(),
ty_record_type: Ty::any(),
parameter_spec: ParametersSpec::<FrozenValue>::new_named_only(
"MyRec",
[
("x", ParametersSpecParam::Required),
("y", ParametersSpecParam::Required),
],
),
});
let make_fields = || {
let mut fields: SmallMap<String, FieldGen<FrozenValue>> = SmallMap::new();
fields.insert(
"x".to_owned(),
FieldGen {
typ: TypeCompiled::any(),
default: None,
},
);
fields.insert(
"y".to_owned(),
FieldGen {
typ: TypeCompiled::any(),
default: None,
},
);
fields
};
let id_a = TypeInstanceId::r#gen();
let id_b = TypeInstanceId::r#gen();
heap.alloc_simple(FrozenRecordType {
id: id_a,
ty_record_data: Some(shared.clone()),
fields: make_fields(),
});
heap.alloc_simple(FrozenRecordType {
id: id_b,
ty_record_data: Some(shared),
fields: make_fields(),
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_frozen_record_type"));
let restored = round_trip_heap_ref(&heap_ref)?;
let headers = restored.collect_drop_headers_ordered();
assert_eq!(headers.len(), 2);
let rt_a: &FrozenRecordType = headers[0].unpack().downcast_ref().unwrap();
let rt_b: &FrozenRecordType = headers[1].unpack().downcast_ref().unwrap();
assert_eq!(rt_a.id, id_a);
assert_eq!(rt_b.id, id_b);
for rt in [rt_a, rt_b] {
let field_names: Vec<&str> = rt.fields.keys().map(String::as_str).collect();
assert_eq!(field_names, vec!["x", "y"]);
for (_, field) in rt.fields.iter() {
assert!(field.default.is_none());
}
}
let data_a = rt_a
.ty_record_data
.as_ref()
.expect("ty_record_data restored");
let data_b = rt_b
.ty_record_data
.as_ref()
.expect("ty_record_data restored");
assert_eq!(data_a.name, "MyRec");
assert_eq!(data_a.ty_record, Ty::any());
assert_eq!(data_a.ty_record_type, Ty::any());
assert_eq!(data_a.parameter_spec.len(), 2);
assert!(
Arc::ptr_eq(data_a, data_b),
"pagable Arc dedup should round-trip the shared Arc<TyRecordData> as a single allocation",
);
Ok(())
}
#[test]
fn test_static_frozen_value_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
let none_fv = FrozenValue::new_none();
let true_fv = FrozenValue::new_bool(true);
let false_fv = FrozenValue::new_bool(false);
let empty_tuple_fv = FrozenValue::new_empty_tuple();
let static_str_fv = const_frozen_string!("static_test_str").to_frozen_value();
heap.alloc_simple(RefData {
label: 1,
target: none_fv,
});
heap.alloc_simple(RefData {
label: 2,
target: true_fv,
});
heap.alloc_simple(RefData {
label: 3,
target: false_fv,
});
heap.alloc_simple(RefData {
label: 4,
target: empty_tuple_fv,
});
heap.alloc_simple(RefData {
label: 5,
target: static_str_fv,
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_static"));
let restored = round_trip_heap_ref(&heap_ref)?;
let undrop_headers = restored.collect_undrop_headers_ordered();
assert_eq!(undrop_headers.len(), 5);
let ref0: &RefData = undrop_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(ref0.label, 1);
assert!(ref0.target.is_none());
assert_eq!(
ref0.target.ptr_value().ptr_value_untagged(),
none_fv.ptr_value().ptr_value_untagged(),
"None should point to the same static address"
);
let ref1: &RefData = undrop_headers[1].unpack().downcast_ref().unwrap();
assert_eq!(ref1.label, 2);
assert_eq!(ref1.target.unpack_bool(), Some(true));
assert_eq!(
ref1.target.ptr_value().ptr_value_untagged(),
true_fv.ptr_value().ptr_value_untagged(),
"True should point to the same static address"
);
let ref2: &RefData = undrop_headers[2].unpack().downcast_ref().unwrap();
assert_eq!(ref2.label, 3);
assert_eq!(ref2.target.unpack_bool(), Some(false));
assert_eq!(
ref2.target.ptr_value().ptr_value_untagged(),
false_fv.ptr_value().ptr_value_untagged(),
"False should point to the same static address"
);
let ref3: &RefData = undrop_headers[3].unpack().downcast_ref().unwrap();
assert_eq!(ref3.label, 4);
assert_eq!(
ref3.target.ptr_value().ptr_value_untagged(),
empty_tuple_fv.ptr_value().ptr_value_untagged(),
"Empty tuple should point to the same static address"
);
let ref4: &RefData = undrop_headers[4].unpack().downcast_ref().unwrap();
assert_eq!(ref4.label, 5);
assert_eq!(ref4.target.unpack_str(), Some("static_test_str"));
assert_eq!(
ref4.target.ptr_value().ptr_value_untagged(),
static_str_fv.ptr_value().ptr_value_untagged(),
"Static string should point to the same static address"
);
Ok(())
}
#[derive(Debug, Display, Allocative, ProvidesStaticType, NoSerialize)]
#[display("AsTypeRoundTripTestType")]
struct AsTypeRoundTripTestType;
#[starlark_value(type = "AsTypeRoundTripTestType")]
impl<'v> StarlarkValue<'v> for AsTypeRoundTripTestType {}
crate::declare_starlark_value_as_type!(AS_TYPE_RT_STATIC, AsTypeRoundTripTestType);
#[test]
fn test_starlark_value_as_type_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
let static_fv = AS_TYPE_RT_STATIC.to_frozen_value();
heap.alloc_simple(RefData {
label: 7,
target: static_fv,
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name(
"test_starlark_value_as_type_round_trip",
));
let restored = round_trip_heap_ref(&heap_ref)?;
let headers = restored.collect_undrop_headers_ordered();
assert_eq!(headers.len(), 1, "only RefData lives on the heap");
let ref_data: &RefData = headers[0].unpack().downcast_ref().unwrap();
assert_eq!(ref_data.label, 7);
assert_eq!(
ref_data.target.ptr_value().ptr_value_untagged(),
static_fv.ptr_value().ptr_value_untagged(),
"StarlarkValueAsType static should round-trip to the same static address"
);
Ok(())
}
#[derive(Debug, Allocative, ProvidesStaticType, StarlarkPagable)]
struct InnerArcData {
target: FrozenValue,
label: u32,
}
impl pagable::PagableSerialize for InnerArcData {
fn pagable_serialize(
&self,
serializer: &mut dyn pagable::PagableSerializer,
) -> pagable::Result<()> {
let mut ctx = crate::pagable::StarlarkSerializerImpl::recover_from_pagable(serializer)
.map_err(|e: crate::Error| e.into_anyhow())?;
<Self as crate::pagable::StarlarkSerialize>::starlark_serialize(self, &mut ctx)
.map_err(|e: crate::Error| e.into_anyhow())
}
}
impl<'de> pagable::PagableDeserialize<'de> for InnerArcData {
fn pagable_deserialize<D: pagable::PagableDeserializer<'de> + ?Sized>(
deserializer: &mut D,
) -> pagable::Result<Self> {
let mut ctx =
crate::pagable::StarlarkDeserializerImpl::recover_from_pagable(deserializer.as_dyn())
.map_err(|e: crate::Error| e.into_anyhow())?;
<Self as crate::pagable::StarlarkDeserialize>::starlark_deserialize(&mut ctx)
.map_err(|e: crate::Error| e.into_anyhow())
}
}
#[derive(
Debug,
Display,
Allocative,
ProvidesStaticType,
NoSerialize,
StarlarkPagable
)]
#[display("OuterArcValue({})", self.outer_label)]
struct OuterArcValue {
#[starlark_pagable(pagable)]
inner: Arc<InnerArcData>,
outer_label: u32,
items: Vec<u32>,
}
starlark_simple_value!(OuterArcValue);
#[starlark_value(type = "OuterArcValue", skip_pagable)]
impl<'v> StarlarkValue<'v> for OuterArcValue {
type Canonical = Self;
}
#[test]
fn test_with_starlark_context_arc_dedup_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
let target_fv = heap.alloc_simple(SimpleData {
flag: true,
count: 314,
});
let shared = Arc::new(InnerArcData {
target: target_fv,
label: 99,
});
heap.alloc_simple(OuterArcValue {
inner: shared.clone(),
outer_label: 1,
items: vec![10, 20, 30],
});
heap.alloc_simple(OuterArcValue {
inner: shared,
outer_label: 2,
items: vec![40, 50],
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_with_starlark_context_arc"));
let restored = round_trip_heap_ref(&heap_ref)?;
let undrop = restored.collect_undrop_headers_ordered();
assert_eq!(undrop.len(), 1);
let target_data: &SimpleData = undrop[0].unpack().downcast_ref().unwrap();
assert_eq!(target_data.flag, true);
assert_eq!(target_data.count, 314);
let drop_headers = restored.collect_drop_headers_ordered();
assert_eq!(drop_headers.len(), 2);
let outer_a: &OuterArcValue = drop_headers[0].unpack().downcast_ref().unwrap();
let outer_b: &OuterArcValue = drop_headers[1].unpack().downcast_ref().unwrap();
assert_eq!(outer_a.outer_label, 1);
assert_eq!(outer_b.outer_label, 2);
assert_eq!(outer_a.items, vec![10, 20, 30]);
assert_eq!(outer_b.items, vec![40, 50]);
assert_eq!(outer_a.inner.label, 99);
assert_eq!(outer_b.inner.label, 99);
let target_addr = undrop[0] as *const _ as usize;
assert_eq!(
outer_a.inner.target.ptr_value().ptr_value_untagged(),
target_addr,
);
assert_eq!(
outer_b.inner.target.ptr_value().ptr_value_untagged(),
target_addr,
);
assert!(
Arc::ptr_eq(&outer_a.inner, &outer_b.inner),
"pagable Arc dedup should round-trip the shared Arc as a single allocation",
);
Ok(())
}
#[derive(Debug, Allocative, ProvidesStaticType, StarlarkPagable)]
struct ArcBlanketInner {
target: FrozenValue,
label: u32,
}
#[derive(
Debug,
Display,
Allocative,
ProvidesStaticType,
NoSerialize,
StarlarkPagable
)]
#[display("ArcBlanketOuter({})", self.outer_label)]
struct ArcBlanketOuter {
inner: Arc<ArcBlanketInner>,
outer_label: u32,
items: Vec<u32>,
}
starlark_simple_value!(ArcBlanketOuter);
#[starlark_value(type = "ArcBlanketOuter", skip_pagable)]
impl<'v> StarlarkValue<'v> for ArcBlanketOuter {
type Canonical = Self;
}
#[test]
fn test_arc_blanket_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
let target_fv = heap.alloc_simple(SimpleData {
flag: true,
count: 271,
});
let shared = Arc::new(ArcBlanketInner {
target: target_fv,
label: 7,
});
heap.alloc_simple(ArcBlanketOuter {
inner: shared.clone(),
outer_label: 1,
items: vec![1, 2, 3],
});
heap.alloc_simple(ArcBlanketOuter {
inner: shared,
outer_label: 2,
items: vec![4, 5],
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_arc_blanket"));
let restored = round_trip_heap_ref(&heap_ref)?;
let undrop = restored.collect_undrop_headers_ordered();
assert_eq!(undrop.len(), 1);
let target_data: &SimpleData = undrop[0].unpack().downcast_ref().unwrap();
assert_eq!(target_data.flag, true);
assert_eq!(target_data.count, 271);
let drop_headers = restored.collect_drop_headers_ordered();
assert_eq!(drop_headers.len(), 2);
let outer_a: &ArcBlanketOuter = drop_headers[0].unpack().downcast_ref().unwrap();
let outer_b: &ArcBlanketOuter = drop_headers[1].unpack().downcast_ref().unwrap();
assert_eq!(outer_a.outer_label, 1);
assert_eq!(outer_b.outer_label, 2);
assert_eq!(outer_a.items, vec![1, 2, 3]);
assert_eq!(outer_b.items, vec![4, 5]);
assert_eq!(outer_a.inner.label, 7);
assert_eq!(outer_b.inner.label, 7);
let target_addr = undrop[0] as *const _ as usize;
assert_eq!(
outer_a.inner.target.ptr_value().ptr_value_untagged(),
target_addr,
);
assert_eq!(
outer_b.inner.target.ptr_value().ptr_value_untagged(),
target_addr,
);
assert!(
Arc::ptr_eq(&outer_a.inner, &outer_b.inner),
"Arc<T> blanket should preserve Arc identity across round-trip",
);
Ok(())
}
#[derive(Debug, Clone, PartialEq, Eq, StarlarkPagable)]
struct AnyPayload {
name: String,
count: u32,
}
crate::register_starlark_any!(AnyPayload);
crate::register_any_array!(AnyPayload);
#[test]
fn test_starlark_any_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
let payload = AnyPayload {
name: "hello".to_owned(),
count: 7,
};
heap.alloc_any_value(payload.clone());
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_starlark_any"));
let restored = round_trip_heap_ref(&heap_ref)?;
let headers = restored.collect_drop_headers_ordered();
assert_eq!(headers.len(), 1);
let avalue = headers[0].unpack();
let any_payload: &crate::values::any::StarlarkAny<AnyPayload> = avalue.downcast_ref().unwrap();
assert_eq!(any_payload.0, payload);
Ok(())
}
#[test]
fn test_starlark_any_multiple_values_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
let a = AnyPayload {
name: "first".to_owned(),
count: 1,
};
let b = AnyPayload {
name: "second".to_owned(),
count: 2,
};
heap.alloc_any_value(a.clone());
heap.alloc_any_value(b.clone());
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_starlark_any_multi"));
let restored = round_trip_heap_ref(&heap_ref)?;
let headers = restored.collect_drop_headers_ordered();
assert_eq!(headers.len(), 2);
let got_a: &crate::values::any::StarlarkAny<AnyPayload> =
headers[0].unpack().downcast_ref().unwrap();
let got_b: &crate::values::any::StarlarkAny<AnyPayload> =
headers[1].unpack().downcast_ref().unwrap();
assert_eq!(got_a.0, a);
assert_eq!(got_b.0, b);
Ok(())
}
#[test]
fn test_frozen_any_array_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
let items = vec![
AnyPayload {
name: "alpha".to_owned(),
count: 10,
},
AnyPayload {
name: "beta".to_owned(),
count: 20,
},
AnyPayload {
name: "gamma".to_owned(),
count: 30,
},
];
heap.alloc_any_array_value(&items);
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_frozen_any_array"));
let restored = round_trip_heap_ref(&heap_ref)?;
let headers = restored.collect_drop_headers_ordered();
assert_eq!(headers.len(), 1);
let avalue = headers[0].unpack();
let any_array: &crate::values::types::any_array::AnyArray<AnyPayload> =
avalue.downcast_ref().unwrap();
assert_eq!(any_array.as_slice().len(), 3);
assert_eq!(any_array.as_slice()[0], items[0]);
assert_eq!(any_array.as_slice()[1], items[1]);
assert_eq!(any_array.as_slice()[2], items[2]);
Ok(())
}
#[test]
fn test_frozen_any_array_empty_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
heap.alloc_any_array_value::<AnyPayload>(&[]);
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_frozen_any_array_empty"));
let restored = round_trip_heap_ref(&heap_ref)?;
let headers = restored.collect_drop_headers_ordered();
assert_eq!(headers.len(), 1);
let avalue = headers[0].unpack();
let any_array: &crate::values::types::any_array::AnyArray<AnyPayload> =
avalue.downcast_ref().unwrap();
assert_eq!(any_array.as_slice().len(), 0);
Ok(())
}
#[derive(Display, Allocative, ProvidesStaticType, NoSerialize, StarlarkPagable)]
#[display("AtomicHost({})", self.label)]
struct AtomicHost {
label: String,
#[allocative(skip)]
option: crate::values::any::AtomicFrozenAnyValueOption<AnyPayload>,
}
impl std::fmt::Debug for AtomicHost {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AtomicHost")
.field("label", &self.label)
.field("option_is_some", &self.option.load_relaxed().is_some())
.finish()
}
}
starlark_simple_value!(AtomicHost);
#[starlark_value(type = "AtomicHost", skip_pagable)]
impl<'v> StarlarkValue<'v> for AtomicHost {
type Canonical = Self;
}
#[test]
fn test_atomic_frozen_any_value_option_some_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
let payload_fv = heap.alloc_any_value(AnyPayload {
name: "target".to_owned(),
count: 42,
});
heap.alloc_simple(AtomicHost {
label: "host_with_some".to_owned(),
option: crate::values::any::AtomicFrozenAnyValueOption::new(Some(payload_fv)),
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name(
"test_atomic_frozen_any_value_option_some",
));
let restored = round_trip_heap_ref(&heap_ref)?;
let drop_headers = restored.collect_drop_headers_ordered();
assert_eq!(drop_headers.len(), 2);
let host: &AtomicHost = drop_headers[1].unpack().downcast_ref().unwrap();
assert_eq!(host.label, "host_with_some");
let loaded = host.option.load_relaxed();
let fv = loaded.expect("option should be Some after round-trip");
assert_eq!(
*fv.as_ref(),
AnyPayload {
name: "target".to_owned(),
count: 42,
}
);
Ok(())
}
#[test]
fn test_atomic_frozen_any_value_option_none_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
heap.alloc_simple(AtomicHost {
label: "host_with_none".to_owned(),
option: crate::values::any::AtomicFrozenAnyValueOption::new(None),
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name(
"test_atomic_frozen_any_value_option_none",
));
let restored = round_trip_heap_ref(&heap_ref)?;
let drop_headers = restored.collect_drop_headers_ordered();
assert_eq!(drop_headers.len(), 1);
let host: &AtomicHost = drop_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(host.label, "host_with_none");
assert!(
host.option.load_relaxed().is_none(),
"option should be None after round-trip"
);
Ok(())
}
#[derive(Debug, Allocative, ProvidesStaticType, StarlarkPagable)]
struct FrozenComplexPayload {
label: String,
numbers: Vec<u32>,
}
crate::register_starlark_any_complex!(frozen FrozenComplexPayload);
#[test]
fn test_starlark_any_complex_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
heap.alloc_simple(crate::values::any_complex::StarlarkAnyComplex::new(
FrozenComplexPayload {
label: "complex".to_owned(),
numbers: vec![1, 2, 3],
},
));
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_starlark_any_complex"));
let restored = round_trip_heap_ref(&heap_ref)?;
let drop_headers = restored.collect_drop_headers_ordered();
assert_eq!(drop_headers.len(), 1);
let got: &crate::values::any_complex::StarlarkAnyComplex<FrozenComplexPayload> =
drop_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(got.value.label, "complex");
assert_eq!(got.value.numbers, vec![1, 2, 3]);
Ok(())
}
#[crate::starlark_pagable_typetag]
trait TypetagPayload:
pagable::PagableTagged + std::fmt::Debug + Allocative + Send + Sync + 'static
{
fn label(&self) -> u32;
fn target(&self) -> FrozenValue;
}
#[derive(Debug, Allocative, ProvidesStaticType, StarlarkPagable)]
struct TypetagImpl {
target: FrozenValue,
label: u32,
}
#[crate::starlark_pagable_typetag]
impl TypetagPayload for TypetagImpl {
fn label(&self) -> u32 {
self.label
}
fn target(&self) -> FrozenValue {
self.target
}
}
#[derive(
Debug,
Display,
Allocative,
ProvidesStaticType,
NoSerialize,
StarlarkPagable
)]
#[display("TypetagOuter({})", self.outer)]
struct TypetagOuter {
#[starlark_pagable(pagable)]
inner: Box<dyn TypetagPayload>,
outer: u32,
}
starlark_simple_value!(TypetagOuter);
#[starlark_value(type = "TypetagOuter", skip_pagable)]
impl<'v> StarlarkValue<'v> for TypetagOuter {
type Canonical = Self;
}
#[test]
fn test_starlark_pagable_typetag_round_trip() -> crate::Result<()> {
let heap = FrozenHeap::new();
let target_fv = heap.alloc_simple(SimpleData {
flag: true,
count: 161,
});
heap.alloc_simple(TypetagOuter {
inner: Box::new(TypetagImpl {
target: target_fv,
label: 11,
}),
outer: 1,
});
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_typetag"));
let restored = round_trip_heap_ref(&heap_ref)?;
let undrop = restored.collect_undrop_headers_ordered();
assert_eq!(undrop.len(), 1);
let target_data: &SimpleData = undrop[0].unpack().downcast_ref().unwrap();
assert_eq!(target_data.count, 161);
let drop_headers = restored.collect_drop_headers_ordered();
assert_eq!(drop_headers.len(), 1);
let outer: &TypetagOuter = drop_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(outer.outer, 1);
assert_eq!(outer.inner.label(), 11);
let target_addr = undrop[0] as *const _ as usize;
assert_eq!(
outer.inner.target().ptr_value().ptr_value_untagged(),
target_addr,
);
Ok(())
}
#[test]
fn test_type_compiled_impl_as_starlark_value_round_trip() -> crate::Result<()> {
use crate::typing::Ty;
use crate::values::typing::type_compiled::compiled::DummyTypeMatcher;
use crate::values::typing::type_compiled::compiled::TypeCompiledImplAsStarlarkValue;
let heap = FrozenHeap::new();
let original_ty = Ty::any();
heap.alloc_simple(
TypeCompiledImplAsStarlarkValue::<DummyTypeMatcher>::new_for_test(
DummyTypeMatcher,
original_ty.clone(),
),
);
let heap_ref = heap.into_ref_named(TestHeapName::heap_name(
"test_type_compiled_impl_round_trip",
));
let restored = round_trip_heap_ref(&heap_ref)?;
let drop_headers = restored.collect_drop_headers_ordered();
assert_eq!(drop_headers.len(), 1);
let got: &TypeCompiledImplAsStarlarkValue<DummyTypeMatcher> =
drop_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(got.ty_for_test(), &original_ty);
let _: &DummyTypeMatcher = got.impl_for_test();
Ok(())
}
#[test]
fn test_type_compiled_impl_with_is_str_round_trip() -> crate::Result<()> {
use crate::typing::Ty;
use crate::values::typing::type_compiled::compiled::TypeCompiledImplAsStarlarkValue;
use crate::values::typing::type_compiled::matcher::TypeMatcher;
use crate::values::typing::type_compiled::matchers::IsStr;
let heap = FrozenHeap::new();
let original_ty = Ty::string();
heap.alloc_simple(TypeCompiledImplAsStarlarkValue::<IsStr>::new_for_test(
IsStr,
original_ty.clone(),
));
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_type_compiled_is_str"));
let restored = round_trip_heap_ref(&heap_ref)?;
let drop_headers = restored.collect_drop_headers_ordered();
assert_eq!(drop_headers.len(), 1);
let got: &TypeCompiledImplAsStarlarkValue<IsStr> =
drop_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(got.ty_for_test(), &original_ty);
assert!(
got.impl_for_test()
.matches(const_frozen_string!("hi").to_value())
);
assert!(
!got.impl_for_test()
.matches(FrozenValue::new_bool(true).to_value())
);
Ok(())
}
#[test]
fn test_type_compiled_impl_with_is_int_round_trip() -> crate::Result<()> {
use crate::typing::Ty;
use crate::values::typing::type_compiled::compiled::TypeCompiledImplAsStarlarkValue;
use crate::values::typing::type_compiled::matcher::TypeMatcher;
use crate::values::typing::type_compiled::matchers::IsInt;
let heap = FrozenHeap::new();
let original_ty = Ty::int();
heap.alloc_simple(TypeCompiledImplAsStarlarkValue::<IsInt>::new_for_test(
IsInt,
original_ty.clone(),
));
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_type_compiled_is_int"));
let restored = round_trip_heap_ref(&heap_ref)?;
let drop_headers = restored.collect_drop_headers_ordered();
assert_eq!(drop_headers.len(), 1);
let got: &TypeCompiledImplAsStarlarkValue<IsInt> =
drop_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(got.ty_for_test(), &original_ty);
assert!(
got.impl_for_test()
.matches(FrozenValue::testing_new_int(42).to_value())
);
assert!(
!got.impl_for_test()
.matches(const_frozen_string!("x").to_value())
);
Ok(())
}
#[test]
fn test_type_compiled_impl_with_is_any_of_round_trip() -> crate::Result<()> {
use crate::typing::Ty;
use crate::values::typing::type_compiled::compiled::TypeCompiledImplAsStarlarkValue;
use crate::values::typing::type_compiled::matcher::TypeMatcher;
use crate::values::typing::type_compiled::matcher::TypeMatcherBox;
use crate::values::typing::type_compiled::matchers::IsAnyOf;
use crate::values::typing::type_compiled::matchers::IsInt;
use crate::values::typing::type_compiled::matchers::IsStr;
let inners = vec![TypeMatcherBox::new(IsStr), TypeMatcherBox::new(IsInt)];
let heap = FrozenHeap::new();
let original_ty = Ty::union2(Ty::string(), Ty::int());
heap.alloc_simple(TypeCompiledImplAsStarlarkValue::<IsAnyOf>::new_for_test(
IsAnyOf(inners),
original_ty.clone(),
));
let heap_ref = heap.into_ref_named(TestHeapName::heap_name("test_type_compiled_is_any_of"));
let restored = round_trip_heap_ref(&heap_ref)?;
let drop_headers = restored.collect_drop_headers_ordered();
assert_eq!(drop_headers.len(), 1);
let got: &TypeCompiledImplAsStarlarkValue<IsAnyOf> =
drop_headers[0].unpack().downcast_ref().unwrap();
assert_eq!(got.ty_for_test(), &original_ty);
assert_eq!(got.impl_for_test().0.len(), 2);
assert!(
got.impl_for_test()
.matches(const_frozen_string!("s").to_value())
);
assert!(
got.impl_for_test()
.matches(FrozenValue::testing_new_int(1).to_value())
);
assert!(
!got.impl_for_test()
.matches(FrozenValue::new_bool(true).to_value())
);
Ok(())
}