makepad/platform/script/tests/handle_refs.rs
Admin 62d4f31b1e script: Splash VM heaps, isolates and lazy system modules
Every heap ref carries its heap tag, generation checks always on, rooted
async values. Splash isolates get a domain table and curated minimal
preludes; system modules under `mod` expand lazily on first touch. No
wall-clock script caps: isolation and force-stop instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 12:26:21 +02:00

109 lines
4.4 KiB
Rust

//! Host references are ordinary script handles: a miss in property position is an
//! evaluation error with the object's own keys, and a minted handle survives let,
//! object literals, arrays, call arguments and an object's property.
use makepad_script::*;
struct FieldRef(u32);
impl ScriptHandleGc for FieldRef {
fn gc(&mut self) {}
fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "field derby.{}", self.0)
}
}
fn test_vm() -> ScriptVm<'static> {
let host = Box::leak(Box::new(ScriptVmHost::new((), ())));
let mut vm = ScriptVm { host: host.host_context(), bx: Box::new(ScriptVmBase::new()) };
vm.bx.captured_errors = Some(Vec::new());
vm
}
/// A frozen api object `race` with fields lap, laps, rank (handles), and a plain
/// object `plain` with the same keys.
fn install(vm: &mut ScriptVm) {
let ht = vm.new_handle_type(id!(field));
for (name, frozen) in [(id_lut!(race), true), (id_lut!(plain), false)] {
let m = vm.new_module(name);
for (i, key) in [id_lut!(lap), id_lut!(laps), id_lut!(rank)].into_iter().enumerate() {
let h = vm.bx.heap.new_handle(ht, Box::new(FieldRef(i as u32)));
vm.bx.heap.set_value_def(m, key.into(), h.into());
}
if frozen {
vm.bx.heap.freeze_api(m);
}
}
}
fn run(vm: &mut ScriptVm, name: &str, code: &str) -> (ScriptValue, Vec<String>) {
vm.capture_errors();
let v = vm.eval(ScriptMod::new("", "", &format!("{name}.splash"), 0, 0, code, &[]));
(v, vm.take_errors())
}
fn errors_of(vm: &mut ScriptVm, name: &str, code: &str) -> Vec<String> {
run(vm, name, code).1
}
#[test]
fn a_miss_in_property_position_is_an_evaluation_error_with_its_place() {
let mut vm = test_vm();
install(&mut vm);
// Bare, in an object literal property value, in a call argument.
for (name, code, line_col) in [
("direct", "mod.race.lpa", "direct.splash:0:9:"),
("lit", "let w = {bind: mod.race.lpa}\nw", "lit.splash:0:24:"),
("multi", "let a = 1\nlet w = {\n a: a\n bind: mod.race.lpa\n}\nw", "multi.splash:3:17:"),
("arg", "fn f(x) { x }\nf(mod.race.lpa)", "arg.splash:1:11:"),
] {
let errors = errors_of(&mut vm, name, code);
assert_eq!(errors.len(), 1, "{name}: {errors:?}");
assert!(errors[0].starts_with(line_col), "{name}: {errors:?}");
// A frozen API object lists its own keys, not a guess.
assert!(errors[0].contains("no field `lpa`; it has lap, laps, rank"), "{name}: {errors:?}");
}
// A hit raises nothing.
assert!(errors_of(&mut vm, "hit", "let w = {bind: mod.race.lap}\nw").is_empty());
}
#[test]
fn an_ordinary_object_keeps_its_did_you_mean_message() {
let mut vm = test_vm();
install(&mut vm);
let errors = errors_of(&mut vm, "plain", "let w = {bind: mod.plain.lpa}\nw");
assert_eq!(errors.len(), 1, "{errors:?}");
assert!(errors[0].contains("property lpa not found in prototype chain. Did you mean: lap"), "{errors:?}");
assert!(!errors[0].contains("no field"), "{errors:?}");
}
#[test]
fn a_host_handle_passes_through_every_position_and_prints() {
let mut vm = test_vm();
install(&mut vm);
let lap = {
let m = vm.modules();
let race = vm.value(m, id_lut!(race)).as_object().unwrap();
vm.value(race, id_lut!(lap))
};
assert!(lap.is_handle(), "{lap:?}");
let code = "let h = mod.race.lap
fn pass(x) { x }
let w = {bind: h, list: [h, mod.race.lap]}
let again = pass(w.bind)
let same = again == mod.race.lap && w.list[0] == h && w.list[1] == h && h != mod.race.laps && w.bind == h
{same: same, h: h, w: w, via_fn: again}";
let (v, errors) = run(&mut vm, "round", code);
assert!(errors.is_empty(), "{errors:?}");
let out = v.as_object().unwrap();
let get = |vm: &ScriptVm, k: LiveId| vm.value(out, k);
assert_eq!(get(&vm, id_lut!(same)).as_bool(), Some(true));
assert_eq!(get(&vm, id_lut!(h)), lap);
assert_eq!(get(&vm, id_lut!(via_fn)), lap);
// The host reads its payload back, and `log` prints the host's text.
let h = get(&vm, id_lut!(h)).as_handle().unwrap();
assert_eq!(vm.bx.heap.handle_ref::<FieldRef>(h).map(|f| f.0), Some(0));
let mut text = String::new();
let mut seen = Vec::new();
vm.bx.heap.to_debug_string(get(&vm, id_lut!(w)), &mut seen, &mut text, true, 0);
assert!(text.contains("field derby.0"), "{text}");
}