makepad/platform/script/tests/hook_scope.rs
Kevin Boos 6d71eda34f
Splash: host->script hook calls find fns defined after a shadowing let (#1210)
`call_script_fn` looked names up in the module body scope, but a
`let`/`fn` that shadows a name already in scope opens a child scope,
and everything the script defines after it lands there, invisible from
the module scope. The Splash prefix's own `let fs` / `let host` can be
that shadow, so app hooks never resolved.

* The VM records the scope a root frame ended in (`ScriptBody::end_scope`)
* Splash looks hooks up there, falling back to the module scope
2026-09-03 21:58:22 +02:00

53 lines
2.3 KiB
Rust

//! Host->script hook lookups against a Splash-shaped body: the script is
//! wrapped in an auto-closed object literal, and a `let`/`fn` that shadows
//! an existing name opens a child scope the module scope cannot see into.
//! The body remembers the scope it ended in for exactly that.
use makepad_script::*;
fn test_vm() -> ScriptVm<'static> {
let host = Box::leak(Box::new(0i32));
let std = Box::leak(Box::new(0i32));
ScriptVm { host, std, bx: Box::new(ScriptVmBase::new()) }
}
fn scopes(vm: &mut ScriptVm, file: &str) -> (ScriptObject, Option<ScriptObject>) {
let bodies = vm.bx.code.bodies.borrow();
bodies.iter().find_map(|body| match &body.source {
ScriptSource::Mod(m) if m.file == file => {
Some((body.scope.as_object(), body.end_scope.as_ref().map(|s| s.as_object())))
}
_ => None,
}).expect("body")
}
#[test]
fn hooks_resolve_in_the_scope_the_body_ended_in() {
let mut vm = test_vm();
vm.bx.captured_errors = Some(Vec::new());
// `fs` is defined twice: the second `let` shadows and opens a child scope.
let code = "let fs = 1\n{height: 1, let fs = 2\nfn before(){ 7 }\nfn on_x(){ 42 }\n{}\n";
vm.with_instruction_limit(500_000, |vm| {
vm.eval(ScriptMod {
cargo_manifest_path: String::new(),
module_path: String::new(),
file: "hook_scope".to_string(),
line: 0, column: 0,
code: code.to_string(),
values: vec![],
})
});
let errors = vm.take_errors();
assert!(errors.is_empty(), "eval errored: {errors:?}");
let (module, end) = scopes(&mut vm, "hook_scope");
let end = end.expect("the body recorded the scope it ended in");
let from_module = vm.bx.heap.scope_value(module, id!(on_x), NoTrap);
assert!(from_module.is_nil() || from_module.is_err(), "the module scope should not see past the shadowing let");
let on_x = vm.bx.heap.scope_value(end, id!(on_x), NoTrap);
assert!(!on_x.is_nil() && !on_x.is_err(), "on_x not found in the end scope");
let result = vm.with_instruction_limit(500_000, |vm| vm.call(on_x, &[]));
assert_eq!(format!("{result:?}"), "42", "on_x returned {result:?}");
let before = vm.bx.heap.scope_value(end, id!(before), NoTrap);
assert!(!before.is_nil() && !before.is_err(), "before not found in the end scope");
}