diff --git a/crates/oxide_emit/src/expr/assignment.rs b/crates/oxide_emit/src/expr/assignment.rs index 6ce6018..8db3528 100644 --- a/crates/oxide_emit/src/expr/assignment.rs +++ b/crates/oxide_emit/src/expr/assignment.rs @@ -19,7 +19,10 @@ impl Emitter { &self, name: &str, op: OpCode, rhs: u32, ctx: &mut CompileCtx, ) -> Result { // const 复合赋值恒抛 TypeError(编译期拦截,值无关);检查在读旧值之前。 - self.emit_const_write_guard(name, ctx)?; + // 循环 update 段的 let/const 循环变量是 per-iteration 可变绑定,复合写同样合法,豁免。 + if !ctx.register_update_names.iter().any(|n| n == name) { + self.emit_const_write_guard(name, ctx)?; + } // 循环 update 段:被捕获绑定走寄存器 RMW(C 风格 for 每迭代 fresh, // update 写寄存器供下一迭代 fresh 拷贝,不污染本迭代闭包捕获的 cell)。 if ctx.register_update_names.iter().any(|n| n == name) { @@ -176,12 +179,7 @@ impl Emitter { // 复合赋值保规范求值序:先读属性(ic_get)再求值 RHS——RHS 副作用 // 可能改写同一属性,后求值才能读到旧值。 let val_reg = ctx.alloc_reg(); - ctx.inst(Inst::new( - OpCode::LOAD_VAR, - Operand::Reg(val_reg), - Operand::Reg(obj_reg), - Operand::None, - )); + ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(val_reg), Operand::Reg(obj_reg), Operand::None)); ctx.inst(Inst::ic_get(Operand::Reg(val_reg), Operand::Reg(key_reg))); let rhs = self.emit_expression(&assign.right, ctx)?; let op = match assign.operator { diff --git a/crates/oxide_emit/src/expr/identifier.rs b/crates/oxide_emit/src/expr/identifier.rs index fafa646..2561698 100644 --- a/crates/oxide_emit/src/expr/identifier.rs +++ b/crates/oxide_emit/src/expr/identifier.rs @@ -120,21 +120,19 @@ impl Emitter { /// 不发射写指令(THROW 后不可达)。 /// - cell 写穿共享单元,无运行时 guard;const 拦截由本入口编译期完成。 pub(crate) fn emit_identifier_store(&self, name: &str, val_reg: u32, const_flag: u16, ctx: &mut CompileCtx) { + // 循环 update 段的 let/const 循环变量是 per-iteration 可变绑定(CreateMutableBinding), + // 写寄存器而非 cell,且豁免 const 检查(register_update_names 覆盖全部循环头声明名)。 + let in_loop_update = ctx.register_update_names.iter().any(|n| n == name); // const 再赋值编译期抛 TypeError:赋值路径(含闭包捕获 const 写 cell)统一拦截。 - if const_flag != 0 { + if const_flag != 0 && !in_loop_update { let _ = self.emit_throw_error("TypeError", "Assignment to constant variable", ctx); return; } // 循环 update 段:被捕获绑定走寄存器而非 cell(C 风格 for 每迭代 fresh, // update 写寄存器供下一迭代 fresh 拷贝,不污染本迭代闭包捕获的 cell)。 - if ctx.register_update_names.iter().any(|n| n == name) { + if in_loop_update { if let Some(reg) = ctx.scopes.symbols.lookup_any(name) { - ctx.inst(Inst::new( - OpCode::STORE_VAR, - Operand::Reg(reg), - Operand::Reg(val_reg), - Operand::Imm(const_flag), - )); + ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(reg), Operand::Reg(val_reg), Operand::Imm(0))); return; } } diff --git a/crates/oxide_emit/src/expr/operator/mod.rs b/crates/oxide_emit/src/expr/operator/mod.rs index 2632165..980753a 100644 --- a/crates/oxide_emit/src/expr/operator/mod.rs +++ b/crates/oxide_emit/src/expr/operator/mod.rs @@ -467,8 +467,12 @@ impl Emitter { &self, name: &str, update: &oxide_parser::UpdateExpression, ctx: &mut CompileCtx, ) -> Result { // 自增/自减先解析赋值引用:TDZ 绑定抛 ReferenceError;const 抛 TypeError(编译期拦截)。 + // 循环 update 段的 let/const 循环变量是 per-iteration 可变绑定(CreateMutableBinding), + // 规范允许写,故豁免 const 检查(register_update_names 覆盖全部循环头声明名)。 self.emit_identifier_tdz_guard(name, ctx)?; - self.emit_const_write_guard(name, ctx)?; + if !ctx.register_update_names.iter().any(|n| n == name) { + self.emit_const_write_guard(name, ctx)?; + } let uv_idx = ctx.current_upvalue_captures.iter().position(|u| u.name == name); let captured_cell = ctx.captured_bindings.get(name).copied(); // 循环 update 段:被捕获绑定走寄存器 INC/DEC(C 风格 for 每迭代 fresh, diff --git a/crates/oxide_emit/src/stmt/iteration/for_.rs b/crates/oxide_emit/src/stmt/iteration/for_.rs index f10a653..518d8ad 100644 --- a/crates/oxide_emit/src/stmt/iteration/for_.rs +++ b/crates/oxide_emit/src/stmt/iteration/for_.rs @@ -44,6 +44,10 @@ impl Emitter { let n_labeled = ctx.take_pending_loop_labels(end_label, update_label); // 循环头声明中被嵌套函数捕获的 let/const 绑定:每迭代 fresh cell。 let mut fresh_bindings: Vec<(String, u8)> = Vec::new(); + // 循环头 let/const 声明名:update 段是 per-iteration 可变绑定 + // (规范 §14.7.4.4 CreatePerIterationEnvironment 用 CreateMutableBinding), + // 编译期 const 写检查对 update 段豁免,故记录全部声明名(含未被捕获者)。 + let mut update_names: Vec = Vec::new(); if let Some(init) = &fr.init { if let Some(expr) = init.as_expression() { self.emit_expression(expr, ctx)?; @@ -91,11 +95,13 @@ impl Emitter { )); ctx.init_var(bi.name.as_str()); } - // 记录被捕获的 let/const 声明(解构 pattern 递归收集绑定名)。 + // 记录 let/const 循环头声明名(update 段写豁免所需)与被捕获的绑定 + // (解构 pattern 递归收集绑定名,被捕获者每迭代 fresh cell)。 if !matches!(decl.kind, VariableDeclarationKind::Var) { let mut names = Vec::new(); self.collect_pattern_binding_names(&d.id, &mut names); for name in &names { + update_names.push(name.clone()); if let Some(&cell_idx) = ctx.captured_bindings.get(name) { fresh_bindings.push((name.clone(), cell_idx)); } @@ -124,11 +130,11 @@ impl Emitter { self.emit_statement(&fr.body, ctx)?; ctx.labels.set_label_pos(update_label, ctx.insts.len()); if let Some(update) = &fr.update { - // 被捕获的 let/const 循环变量在 update 段写寄存器(而非 cell), - // 供下一迭代 fresh 拷贝——否则 CELL_SET 会污染本迭代闭包捕获的 cell。 + // update 段写寄存器(而非 cell):被捕获的 let/const 循环变量每迭代 fresh, + // update 写寄存器供下一迭代 fresh 拷贝——否则 CELL_SET 会污染本迭代闭包 + // 捕获的 cell。未捕获者本就走寄存器,一并登记以豁免 update 段的 const 检查。 let prev = std::mem::take(&mut ctx.register_update_names); - let names: Vec = fresh_bindings.iter().map(|(n, _)| n.clone()).collect(); - ctx.register_update_names = names; + ctx.register_update_names = update_names; self.emit_expression(update, ctx)?; ctx.register_update_names = prev; } diff --git a/crates/oxide_vm/tests/const_guard_tests.rs b/crates/oxide_vm/tests/const_guard_tests.rs index c9b5d13..2e13e5d 100644 --- a/crates/oxide_vm/tests/const_guard_tests.rs +++ b/crates/oxide_vm/tests/const_guard_tests.rs @@ -111,3 +111,17 @@ fn const_logical_assign_write_throws() { let out = eval("const x = 0; x ||= 5"); assert!(out.contains("Assignment to constant variable"), "got: {out}"); } + +// for-const 循环 update 段写 per-iteration 可变绑定:不抛(规范 §14.7.4.4)。 +#[test] +fn for_const_update_does_not_throw() { + // 数字聚合验证 update 段正常推进:0+1+2 = 3,若误抛则返回 vm error。 + assert_eq!(eval("let s=0; for (const i = 0; i < 3; i++) s += i; s"), "3"); +} + +// for-const 循环体内(非 update 段)对 const 绑定写仍必须抛。 +#[test] +fn for_const_body_write_still_throws() { + let out = eval("for (const i = 0; i < 1; i++) { i = 5; }"); + assert!(out.contains("Assignment to constant variable"), "got: {out}"); +} diff --git a/crates/oxide_vm/tests/loop_closure_binding_tests.rs b/crates/oxide_vm/tests/loop_closure_binding_tests.rs index 9a69e75..25280bc 100644 --- a/crates/oxide_vm/tests/loop_closure_binding_tests.rs +++ b/crates/oxide_vm/tests/loop_closure_binding_tests.rs @@ -26,10 +26,36 @@ fn c_style_for_let_captures_per_iteration() { assert_eq!(to_str(&vm, result), "0,1,2"); } +#[test] +fn c_style_for_const_update_writes_per_iteration() { + // for-const 的 update 段写 per-iteration 可变绑定(CreateMutableBinding), + // 不得抛 "Assignment to constant variable"(V8 输出 0 1 2)。 + let (vm, result) = eval("let out=[]; for (const i = 0; i < 3; i++) out.push(i); out.join(',')").unwrap(); + assert_eq!(to_str(&vm, result), "0,1,2"); +} + +#[test] +fn c_style_for_const_captures_per_iteration() { + // for-const 循环变量被闭包捕获:每迭代 fresh,update 段写寄存器不污染本迭代 cell。 + let (vm, result) = + eval("let fns = []; for (const i = 0; i < 3; i++) fns.push(() => i); [fns[0](), fns[1](), fns[2]()].join(',')") + .unwrap(); + assert_eq!(to_str(&vm, result), "0,1,2"); +} + +#[test] +fn c_style_for_const_compound_update_writes_per_iteration() { + // 复合赋值形态的 update 同样合法(i += 2 是 update 段的 per-iteration 可变写)。 + let (vm, result) = eval("let out=[]; for (const i = 0; i < 6; i += 2) out.push(i); out.join(',')").unwrap(); + assert_eq!(to_str(&vm, result), "0,2,4"); +} + #[test] fn c_style_for_let_captures_after_multiple_updates() { - let (vm, result) = eval("let fns = []; for (let i = 0; i < 5; i += 2) fns.push(() => i); [fns[0](), fns[1](), fns[2]()].join(',')") - .unwrap(); + let (vm, result) = eval( + "let fns = []; for (let i = 0; i < 5; i += 2) fns.push(() => i); [fns[0](), fns[1](), fns[2]()].join(',')", + ) + .unwrap(); assert_eq!(to_str(&vm, result), "0,2,4"); } @@ -44,23 +70,26 @@ fn c_style_for_var_remains_single_binding() { #[test] fn for_of_const_captures_per_iteration() { - let (vm, result) = eval("let fns = []; for (const a of [1, 2]) fns.push(() => a); [fns[0](), fns[1]()].join(',')").unwrap(); + let (vm, result) = + eval("let fns = []; for (const a of [1, 2]) fns.push(() => a); [fns[0](), fns[1]()].join(',')").unwrap(); assert_eq!(to_str(&vm, result), "1,2"); } #[test] fn for_of_let_captures_per_iteration() { let (vm, result) = - eval("let fns = []; for (let a of [10, 20, 30]) fns.push(() => a); [fns[0](), fns[1](), fns[2]()].join(',')").unwrap(); + eval("let fns = []; for (let a of [10, 20, 30]) fns.push(() => a); [fns[0](), fns[1](), fns[2]()].join(',')") + .unwrap(); assert_eq!(to_str(&vm, result), "10,20,30"); } #[test] fn for_of_destructuring_captures_per_iteration() { // P2:解构 + 闭包捕获不再产出垃圾值。 - let (vm, result) = - eval("let fns = []; for (const [a, b] of [[1, 2], [3, 4]]) fns.push(() => a + b); [fns[0](), fns[1]()].join(',')") - .unwrap(); + let (vm, result) = eval( + "let fns = []; for (const [a, b] of [[1, 2], [3, 4]]) fns.push(() => a + b); [fns[0](), fns[1]()].join(',')", + ) + .unwrap(); assert_eq!(to_str(&vm, result), "3,7"); } @@ -74,7 +103,8 @@ fn for_of_object_destructuring_captures_per_iteration() { #[test] fn for_in_const_captures_per_iteration() { - let (vm, result) = eval("let fns = []; for (const k in {a:1,b:2}) fns.push(() => k); [fns[0](), fns[1]()].join(',')").unwrap(); + let (vm, result) = + eval("let fns = []; for (const k in {a:1,b:2}) fns.push(() => k); [fns[0](), fns[1]()].join(',')").unwrap(); assert_eq!(to_str(&vm, result), "a,b"); }