diff --git a/crates/oxide_emit/src/prepass/declare.rs b/crates/oxide_emit/src/prepass/declare.rs index f83e349b..fd748b33 100644 --- a/crates/oxide_emit/src/prepass/declare.rs +++ b/crates/oxide_emit/src/prepass/declare.rs @@ -279,6 +279,10 @@ impl Emitter { /// 建立 TDZ 占位)。使声明点之前的读取在编译期可分辨为 TDZ 而非隐式全局, /// 声明点复用预登记槽位。 /// + /// class 声明绑定外层形态是可变的(`let` 类),`C = null` 合法;类定义期 + /// 类体(方法/构造器/访问器)对类名的引用由类自身注入的独立不可变绑定承载, + /// 不依赖此预声明标志。 + /// /// # 边界与前提 /// - 只扫描直接子语句;不递归块/if/for/while/switch case/try 区(嵌套块与 /// switch CaseBlock 各自预声明;单语句 body 不接受 lexical 声明——lexical @@ -307,7 +311,7 @@ impl Emitter { if let Some(id) = &cd.id { check_restricted_global_lexical(id.name.as_str(), global_lexical)?; let reg = ctx.alloc_reg(); - let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Const, true); + let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Let, false); } } Statement::ExportNamedDeclaration(exp) => { @@ -329,8 +333,8 @@ impl Emitter { let _ = ctx.declare_predeclared( id.name.as_str(), reg, - VariableDeclarationKind::Const, - true, + VariableDeclarationKind::Let, + false, ); } } @@ -343,8 +347,7 @@ impl Emitter { if let Some(id) = &cd.id { check_restricted_global_lexical(id.name.as_str(), global_lexical)?; let reg = ctx.alloc_reg(); - let _ = - ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Const, true); + let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Let, false); } } ExportDefaultDeclarationKind::FunctionDeclaration(fd) => { diff --git a/crates/oxide_vm/tests/class_tests.rs b/crates/oxide_vm/tests/class_tests.rs index a39b50f4..76432ec7 100644 --- a/crates/oxide_vm/tests/class_tests.rs +++ b/crates/oxide_vm/tests/class_tests.rs @@ -853,3 +853,12 @@ fn class_declaration_captured_name_cell_still_initialized() { .unwrap(); assert_num(result, 51.0); } + +// 类声明的外层绑定是可变的(let 类):顶层重赋类名合法并生效。 +// 类体内对类名的引用另由独立不可变绑定承载,见 name-binding/const.js 语料。 +#[test] +fn class_declaration_outer_binding_is_mutable() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "class A { m(){ return 1; } } A = 1; A").unwrap(); + assert_num(result, 1.0); +} diff --git a/crates/oxide_vm/tests/module_top_completion_tests.rs b/crates/oxide_vm/tests/module_top_completion_tests.rs index 017eb64b..ff9c14f5 100644 --- a/crates/oxide_vm/tests/module_top_completion_tests.rs +++ b/crates/oxide_vm/tests/module_top_completion_tests.rs @@ -96,3 +96,19 @@ fn dependent_module_namespace_contract_intact() { let r = probe(&mut vm, "globalThis.__dep === true"); assert!(r.is_bool() && r.as_bool(), "依赖命名空间值应正确流入,实际 {r:?}"); } + +#[test] +fn exported_class_declaration_binding_is_mutable() { + // 模块顶层 class 外层绑定是可变 let 类:`export class` 与具名 + // `export default class` 均允许声明后重赋(CreateMutableBinding)。 + let allocator = oxide_parser::Allocator::default(); + let source = "export class C {}\nC = null;\nexport default class D {}\nD = null;\nglobalThis.__mutable = C === null && D === null;"; + let program = oxide_parser::parse_module(&allocator, source).expect("parse module"); + let module = Compiler::new() + .compile_module(&program, "test.mjs", &mut FileLoader) + .expect("compile module"); + let mut vm = Vm::new(); + vm.run(&Arc::new(module)).expect("module run"); + let r = probe(&mut vm, "globalThis.__mutable === true"); + assert!(r.is_bool() && r.as_bool(), "模块 class 外层绑定应可重赋,实际 {r:?}"); +}