From d4df1ddaf42f02002a4d979cc9aefff17b4eab22 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 19 Sep 2026 19:58:13 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=BE=AA=E7=8E=AF=E4=BD=93=E8=A2=AB?= =?UTF-8?q?=E6=8D=95=E8=8E=B7=E8=AF=8D=E6=B3=95=E5=A3=B0=E6=98=8E=E6=AF=8F?= =?UTF-8?q?=E8=BF=AD=E4=BB=A3=E6=96=B0=E5=BB=BA=20cell?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_emit/src/stmt/declaration/var.rs | 15 +- .../tests/loop_body_lex_capture_tests.rs | 222 ++++++++++++++++++ 2 files changed, 230 insertions(+), 7 deletions(-) create mode 100644 crates/oxide_vm/tests/loop_body_lex_capture_tests.rs diff --git a/crates/oxide_emit/src/stmt/declaration/var.rs b/crates/oxide_emit/src/stmt/declaration/var.rs index 5acd452e..18a38179 100644 --- a/crates/oxide_emit/src/stmt/declaration/var.rs +++ b/crates/oxide_emit/src/stmt/declaration/var.rs @@ -22,6 +22,10 @@ impl Emitter { pub(crate) fn emit_variable_declaration( &self, decl: &VariableDeclaration, ctx: &mut CompileCtx, ) -> Result, String> { + // 循环体内的块级 let/const 声明每迭代重执行:被捕获绑定每迭代实例化新 + // cell,本迭代闭包捕获新 cell、旧闭包保留旧值。var 是规范单绑定, + // 保持原位更新;函数体/嵌套块经子 ctx 进入时循环栈为空,不命中。 + let reexec = !matches!(decl.kind, VariableDeclarationKind::Var) && !ctx.labels.loop_stack.is_empty(); for d in &decl.declarations { let is_const = matches!(decl.kind, VariableDeclarationKind::Const); if is_const && d.init.is_none() { @@ -29,7 +33,7 @@ impl Emitter { } if let Some(init) = &d.init { let val_reg = self.emit_expression(init, ctx)?; - self.emit_binding_pattern(&d.id, val_reg, decl.kind, is_const, false, ctx)?; + self.emit_binding_pattern(&d.id, val_reg, decl.kind, is_const, reexec, ctx)?; if let BindingPattern::BindingIdentifier(bi) = &d.id { if crate::is_anonymous_function_definition(init) { if let Some(sub_mod) = ctx.nested.last_mut() { @@ -70,12 +74,9 @@ impl Emitter { // 被捕获绑定的 cell 在函数入口已实例化(undefined);声明语句 // 不赋值,仅首次声明刷新初值,已绑定的 cell 保留现值。 if !already_bound { - ctx.inst(Inst::new( - OpCode::MAKE_CELL, - Operand::Reg(tmp), - Operand::Imm(cell_idx as u16), - Operand::None, - )); + // 重执行迭代取新 cell,与 with-init 臂同一门控。 + let op = if reexec { OpCode::MAKE_CELL_FRESH } else { OpCode::MAKE_CELL }; + ctx.inst(Inst::new(op, Operand::Reg(tmp), Operand::Imm(cell_idx as u16), Operand::None)); } } else if !already_bound { ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(target_reg), Operand::Reg(tmp), Operand::None)); diff --git a/crates/oxide_vm/tests/loop_body_lex_capture_tests.rs b/crates/oxide_vm/tests/loop_body_lex_capture_tests.rs new file mode 100644 index 00000000..a6054f55 --- /dev/null +++ b/crates/oxide_vm/tests/loop_body_lex_capture_tests.rs @@ -0,0 +1,222 @@ +//! 循环体内块级被捕获 let/const 的每迭代绑定引擎侧形态锁:声明每迭代重执行 +//! 须新建 cell,本迭代闭包捕获新 cell、旧闭包保留旧值;循环头 fresh 机制、 +//! var 规范共享与非捕获直读三面语义不变。 +//! +//! 完成值一律收敛为布尔(JsValue 的 Display 只暴露 number/bool,不暴露字符串内容), +//! 字符串比较在引擎内完成。 + +use std::sync::Arc; + +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_vm::vm::Vm; + +fn eval(source: &str) -> String { + let allocator = Allocator::default(); + let program = match oxide_parser::parse(&allocator, source) { + Ok(p) => p, + Err(e) => return format!("parse error: {}", e[0].message), + }; + let module = match Compiler::new().compile(&program) { + Ok(m) => m, + Err(e) => return format!("compile error: {e}"), + }; + let mut vm = Vm::new(); + match vm.run(&Arc::new(module)) { + Ok(result) => format!("{result}"), + Err(e) => format!("vm error: {e}"), + } +} + +// ── for-of / for-in 体被捕获块级声明:每迭代新 cell ── + +#[test] +fn for_of_body_const_captured_by_closure_reads_per_iteration() { + assert_eq!( + eval( + "var ks = ['a', 'b']; var fns = []; \ + for (const k of ks) { const v = k; fns.push(() => v); } \ + fns[0]() + '|' + fns[1]() === 'a|b'" + ), + "true", + "for-of body const captured by closure keeps the per-iteration value" + ); +} + +#[test] +fn for_of_body_let_captured_by_closure_reads_per_iteration() { + assert_eq!( + eval( + "var ks = ['a', 'b']; var fns = []; \ + for (let k of ks) { let v = k; fns.push(() => v); } \ + fns[0]() + '|' + fns[1]() === 'a|b'" + ), + "true", + "for-of body let captured by closure keeps the per-iteration value" + ); +} + +#[test] +fn for_in_body_const_captured_by_closure_reads_per_iteration() { + assert_eq!( + eval( + "var o = { p: 1, q: 2 }; var fns = []; \ + for (const key in o) { const v = o[key]; fns.push(() => v); } \ + fns[0]() + '|' + fns[1]() === '1|2'" + ), + "true", + "for-in body const captured by closure keeps the per-iteration value" + ); +} + +// ── while / do-while / C-for / 标签循环体同形 ── + +#[test] +fn while_body_const_captured_by_closure_reads_per_iteration() { + assert_eq!( + eval( + "var i = 0; var fns = []; \ + while (i < 2) { const v = (i + 1) * 10; fns.push(() => v); i++; } \ + fns[0]() + '|' + fns[1]() === '10|20'" + ), + "true", + "while body const captured by closure keeps the per-iteration value" + ); +} + +#[test] +fn c_for_body_const_captured_by_closure_reads_per_iteration() { + assert_eq!( + eval( + "var fns = []; \ + for (var i = 0; i < 2; i++) { const v = (i + 1) * 10; fns.push(() => v); } \ + fns[0]() + '|' + fns[1]() === '10|20'" + ), + "true", + "c-style for body const captured by closure keeps the per-iteration value" + ); +} + +#[test] +fn do_while_body_const_captured_by_closure_reads_per_iteration() { + assert_eq!( + eval( + "var i = 0; var fns = []; \ + do { const v = (i + 1) * 10; fns.push(() => v); i++; } while (i < 2); \ + fns[0]() + '|' + fns[1]() === '10|20'" + ), + "true", + "do-while body const captured by closure keeps the per-iteration value" + ); +} + +#[test] +fn labeled_while_body_const_captured_by_closure_reads_per_iteration() { + assert_eq!( + eval( + "var i = 0; var fns = []; \ + outer: while (i < 2) { const v = (i + 1) * 10; fns.push(() => v); i++; } \ + fns[0]() + '|' + fns[1]() === '10|20'" + ), + "true", + "labeled while body const captured by closure keeps the per-iteration value" + ); +} + +// ── 嵌套块 / getter / 链式捕获 / 无 init 臂同形 ── + +#[test] +fn for_of_body_nested_block_const_captured_by_closure_reads_per_iteration() { + assert_eq!( + eval( + "var ks = ['a', 'b']; var fns = []; \ + for (const k of ks) { if (k) { const v = k; fns.push(() => v); } } \ + fns[0]() + '|' + fns[1]() === 'a|b'" + ), + "true", + "const in a nested block inside the for-of body keeps the per-iteration value" + ); +} + +#[test] +fn for_of_body_const_captured_by_getter_reads_per_iteration() { + assert_eq!( + eval( + "var ks = ['a', 'b']; var objs = []; \ + for (const k of ks) { const v = k; objs.push({ get x() { return v; } }); } \ + objs[0].x + '|' + objs[1].x === 'a|b'" + ), + "true", + "getter in an object literal captures the per-iteration body const" + ); +} + +#[test] +fn chained_capture_first_iteration_value_is_kept() { + assert_eq!( + eval( + "var ks = ['a', 'b']; var fns = []; \ + for (const k of ks) { const v = k; fns.push(() => () => v); } \ + fns[0]()() === 'a'" + ), + "true", + "chained arrow closures keep the first iteration value of the captured body const" + ); +} + +#[test] +fn for_of_body_let_without_init_assigned_in_body_reads_per_iteration() { + assert_eq!( + eval( + "var ks = ['k0', 'k1']; var fns = []; \ + for (const k of ks) { let v; v = k + 'x'; fns.push(() => v); } \ + fns[0]() + '|' + fns[1]() === 'k0x|k1x'" + ), + "true", + "body let without initializer assigned in the body keeps the per-iteration value" + ); +} + +// ── 守卫:头绑定形 / var 规范共享 / 非捕获直读(修后保真)── + +#[test] +fn for_of_head_binding_captured_by_closure_still_reads_head() { + // 头绑定 fresh-cell 机制在位:体内闭包直取头名,每迭代读头值。 + assert_eq!( + eval( + "var fns = []; \ + for (const x of [1]) { const v = () => x; fns.push(v); } \ + fns[0]() === 1" + ), + "true", + "closure in the body reads the per-iteration head binding (head mechanism intact)" + ); +} + +#[test] +fn for_of_body_var_captured_by_closure_stays_shared() { + // var 是规范单绑定:各迭代闭包读同一槽的末次值,不得误建 fresh cell。 + assert_eq!( + eval( + "var ks = ['a', 'b']; var fns = []; \ + for (const k of ks) { var v = k; fns.push(() => v); } \ + fns[0]() + '|' + fns[1]() === 'b|b'" + ), + "true", + "body var captured by closures stays shared (canonical single binding)" + ); +} + +#[test] +fn for_of_body_const_not_captured_direct_read_unaffected() { + // 非捕获体声明走寄存器路径,fresh 门控不得影响直读语义。 + assert_eq!( + eval( + "var ks = ['a', 'b']; var last = ''; \ + for (const k of ks) { const v = k; last = v; } \ + last === 'b'" + ), + "true", + "non-captured body const direct read is unaffected by the fresh gate" + ); +}