use tishlang_bytecode::{compile, Chunk, Opcode};
fn count_opcode(chunk: &Chunk, target: Opcode) -> usize {
let code = &chunk.code;
let mut ip = 0usize;
let mut n = 0usize;
while ip < code.len() {
let op = Opcode::from_u8(code[ip]).expect("valid opcode");
if op == target {
n += 1;
}
ip += op.instruction_size(code, ip).expect("sized instruction");
}
n
}
fn compile_src(src: &str) -> Chunk {
let program = tishlang_parser::parse(src).expect("parse");
compile(&program).expect("compile")
}
fn slots_disabled() -> bool {
std::env::var("TISH_VM_SLOTS")
.map(|v| v == "0")
.unwrap_or(false)
}
#[test]
fn captured_param_chunk_stays_slot_based_with_copy_in() {
if slots_disabled() {
return;
}
let chunk = compile_src(
r#"
function handleEvent(payload) {
function fmt(x) { return x + payload.tag }
return fmt("v")
}
console.log(handleEvent({ tag: "t" }))
"#,
);
let outer = &chunk.nested[0];
assert!(
outer.slot_based,
"a captured-param chunk must no longer be disqualified from general slot mode"
);
assert_eq!(
count_opcode(outer, Opcode::DeclareVarPlain),
1,
"exactly one copy-in store for the one captured param"
);
assert_eq!(
count_opcode(outer, Opcode::DeclareVar),
0,
"the uncaptured helper name must be slot-bound (#728), not DeclareVar-bound — \
that is the cycle"
);
assert!(
count_opcode(outer, Opcode::StoreLocal) >= 1,
"the helper closure should be stored to a frame slot"
);
}
#[test]
fn only_captured_params_are_copied_in() {
if slots_disabled() {
return;
}
let chunk = compile_src(
r#"
function outer(p, q) {
function get() { return p }
let r = get() + q
return r
}
console.log(outer(1, 2))
"#,
);
let outer = &chunk.nested[0];
assert!(outer.slot_based, "mixed-param chunk should be slot-based");
assert_eq!(
count_opcode(outer, Opcode::DeclareVarPlain),
1,
"only the captured param `p` gets a copy-in store; `q` stays slotted"
);
}
#[test]
fn self_recursive_helper_keeps_scope_path_beside_copy_in() {
if slots_disabled() {
return;
}
let chunk = compile_src(
r#"
function outer(n) {
function fact(k) { if (k < 2) { return n } return k * fact(k - 1) }
return fact(4)
}
console.log(outer(1))
"#,
);
let outer = &chunk.nested[0];
assert!(outer.slot_based, "chunk should stay slot-based");
assert_eq!(
count_opcode(outer, Opcode::DeclareVarPlain),
1,
"the captured param `n` gets its copy-in"
);
assert_eq!(
count_opcode(outer, Opcode::DeclareVar),
1,
"the self-recursive helper name is captured and stays DeclareVar-bound"
);
}