diff --git a/crates/oxide_emit/src/class/emit.rs b/crates/oxide_emit/src/class/emit.rs index cc6eda4..e294981 100644 --- a/crates/oxide_emit/src/class/emit.rs +++ b/crates/oxide_emit/src/class/emit.rs @@ -27,6 +27,10 @@ impl Emitter { &self, class: &Class, ctx: &mut CompileCtx, binding_reg: Option, ) -> Result { let ctor_name = class.id.as_ref().map(|id| id.name.to_string()); + // 类声明由调用方提供外层绑定槽(binding_reg=Some),类表达式无外部槽 + // (None,类名是类体内独立 const 绑定)。真实名 cell 仅声明路径指向 + // 本作用域绑定;表达式路径按名查 captured_bindings 会命中外层同名绑定。 + let is_class_decl = binding_reg.is_some(); let mut pushed_scope = false; let binding_reg = if let Some(name) = ctor_name.as_deref() { match binding_reg { @@ -402,13 +406,18 @@ impl Emitter { } // 类名被外层嵌套函数捕获时,真实名 cell 同样须初始化(类元素自引用走 // @@class_self_* 合成 cell,二者 cell_idx 不同则各发一次)。 - if let Some(&cell_idx) = ctx.captured_bindings.get(name) { - ctx.inst(Inst::new( - OpCode::MAKE_CELL, - Operand::Reg(binding_reg), - Operand::Imm(cell_idx as u16), - Operand::None, - )); + // 仅类声明:类名是本作用域绑定,captured_bindings 按名命中的才是 + // 它的 cell;类表达式名是类体内独立 const 绑定,按名查询只会命中 + // 外层同名绑定 cell,MAKE_CELL 会把类值误写进外层绑定。 + if is_class_decl { + if let Some(&cell_idx) = ctx.captured_bindings.get(name) { + ctx.inst(Inst::new( + OpCode::MAKE_CELL, + Operand::Reg(binding_reg), + Operand::Imm(cell_idx as u16), + Operand::None, + )); + } } } diff --git a/crates/oxide_vm/tests/class_tests.rs b/crates/oxide_vm/tests/class_tests.rs index 64293bb..950a875 100644 --- a/crates/oxide_vm/tests/class_tests.rs +++ b/crates/oxide_vm/tests/class_tests.rs @@ -810,3 +810,44 @@ fn private_field_nullish_coalesce_short_circuits_on_defined() { .unwrap(); assert_num(result, 10.0); } + +// ── 类表达式真实名 cell 边界:类名绑定不触碰外层同名绑定 ── + +// 类表达式名是类体内独立 const 绑定:真实名 cell 初始化不得按名命中外层 +// 同名绑定 cell 并覆盖之。外层 var E 被嵌套函数捕获时,读 E 应仍是原值。 +#[test] +fn class_expression_inner_name_does_not_clobber_outer_binding() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var E = 1; var h = class E { m() { return E; } }; function g() { return E; } g()", + ) + .unwrap(); + assert_eq!(result.as_int(), 1); +} + +// 类表达式名不得破坏外层同名 let 的 TDZ:提前调用的捕获函数读该名仍抛 +// ReferenceError,而非读到被类值污染的 cell。 +#[test] +fn class_expression_inner_name_preserves_outer_let_tdz() { + let mut vm = Vm::new(); + let err = eval( + &mut vm, + "function g(){ return E; } var h = class E { m(){ return E; } }; var r = g(); let E = 5; r", + ) + .unwrap_err(); + assert!(err.contains("ReferenceError"), "expected ReferenceError, got: {err}"); +} + +// 类声明路径不回归:真实名 cell 仍须初始化(嵌套函数经 cell 读类值), +// 且外层同名 var 不被类声明覆盖。 +#[test] +fn class_declaration_captured_name_cell_still_initialized() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var E = 1; function mk(){ class E { static n(){ return 5; } } return function(){ return E.n(); }; } var f = mk(); f()*10 + E", + ) + .unwrap(); + assert_num(result, 51.0); +}