From 7055ef1fbb116073273b5334ce9b6043c95ee37b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Mon, 10 Aug 2026 21:07:39 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=9D=97=E7=BA=A7=20let/const/class=20?= =?UTF-8?q?=E9=A2=84=E5=A3=B0=E6=98=8E=20+=20TDZ=20=E8=AF=BB=E5=8F=96?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=E6=97=B6=E6=8A=9B=20ReferenceError?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_emit/src/emit.rs | 43 ++++++++ crates/oxide_emit/src/expr/identifier.rs | 9 +- crates/oxide_emit/src/prepass.rs | 103 +++++++++++++++++- crates/oxide_emit/src/shared/binding.rs | 12 +- crates/oxide_emit/src/stmt/block.rs | 1 + .../src/stmt/declaration/class_decl.rs | 10 +- crates/oxide_emit/src/stmt/declaration/var.rs | 11 +- .../src/stmt/exception/try_catch.rs | 10 ++ crates/oxide_emit/src/symbol_table.rs | 46 ++++++++ 9 files changed, 237 insertions(+), 8 deletions(-) diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index 604a545..79fc310 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -373,6 +373,12 @@ impl CompileCtx { self.scopes.symbols.declare_initialized(name, reg, kind, is_const) } + pub(crate) fn declare_predeclared( + &mut self, name: &str, reg: u32, kind: VariableDeclarationKind, is_const: bool, + ) -> Result<(), String> { + self.scopes.symbols.declare_predeclared(name, reg, kind, is_const) + } + pub(crate) fn push_scope_with_kind(&mut self, kind: ScopeKind) { self.scopes.symbols.push_scope_with_kind(kind); } @@ -650,6 +656,34 @@ impl Emitter { Self } + /// 生成运行时抛 ReferenceError 的指令序列,返回一个未定义 dummy 寄存器 + /// 保证 THROW 后不可达控制流的寄存器良定义。 + /// + /// # 步骤 + /// 1. 取全局 ReferenceError 构造器并 LOAD。 + /// 2. 加载错误消息常量,`new ReferenceError(msg)` 构造错误对象。 + /// 3. THROW 抛出;尾接 dummy 值保持后续读引用有确定寄存器。 + pub(crate) fn emit_tdz_throw(&self, msg: &str, ctx: &mut CompileCtx) -> Result { + let ctor_reg = ctx.lookup_or_builtin("ReferenceError")?; + let ctor = ctx.alloc_reg(); + ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(ctor), Operand::Reg(ctor_reg), Operand::None)); + let msg_reg = ctx.alloc_reg(); + let msg_idx = ctx.add_constant(Constant::String(msg.to_string())); + ctx.inst(Inst::load_const(Operand::Reg(msg_reg), msg_idx)); + let exc_reg = ctx.alloc_reg(); + ctx.inst(Inst::new_expression( + Operand::Reg(exc_reg), + Operand::Reg(ctor), + Operand::Reg(msg_reg), + 1, + )); + ctx.inst(Inst::new(OpCode::THROW, Operand::Reg(exc_reg), Operand::None, Operand::None)); + let dummy = ctx.alloc_reg(); + let undef_idx = ctx.add_constant(Constant::Undefined); + ctx.inst(Inst::load_const(Operand::Reg(dummy), undef_idx)); + Ok(dummy) + } + /// 遍历解构 pattern 收集内嵌默认值表达式(AssignmentPattern.right)。 fn collect_pattern_default_exprs<'a>( &self, pattern: &'a oxide_parser::BindingPattern<'a>, out: &mut Vec<&'a oxide_parser::Expression<'a>>, @@ -911,6 +945,7 @@ impl Emitter { reg: binding.reg, initialized: binding.initialized, is_const: binding.is_const, + predeclared: false, }, ); inherited_reg_start = inherited_reg_start.max(binding.reg.saturating_add(1)); @@ -922,6 +957,7 @@ impl Emitter { reg: *reg, initialized: true, is_const: true, + predeclared: false, }, ); inherited_reg_start = inherited_reg_start.max(reg.saturating_add(1)); @@ -955,6 +991,10 @@ impl Emitter { // 预声明 `var` 名,使首个 sub-pass 中提升的函数声明能解析其闭包引用的外层 var。 self.predeclare_var_declarations(body_stmts, &mut ctx); + // 预声明 body 级 `let`/`const`/`class`(未初始化 TDZ 占位), + // 使声明点前读取可编译为运行时 ReferenceError。 + self.predeclare_lexical_declarations(body_stmts, &mut ctx); + // 生成器:body 起点标记——调用时参数初始化(emit_params_prologue)结束后挂起于此, // 参数副作用/异常在 `g()` 调用时刻生效,首次 next() 从这继续执行 body。 if is_generator { @@ -1282,6 +1322,9 @@ impl Emitter { // 预声明顶层 `var` 名,使首个 sub-pass 中提升的函数声明能解析外层 var。 self.predeclare_var_declarations(&program.body, &mut ctx); + // 预声明顶层 `let`/`const`/`class`(未初始化 TDZ 占位)。 + self.predeclare_lexical_declarations(&program.body, &mut ctx); + // 闭包捕获分析(AST 级,emit 前确定) ctx.own_bindings = self.collect_own_binding_names(&[], &program.body); ctx.captured_bindings = self.collect_captured_bindings(&program.body, &[], &ctx.own_bindings); diff --git a/crates/oxide_emit/src/expr/identifier.rs b/crates/oxide_emit/src/expr/identifier.rs index 7a1d9af..faa0b4b 100644 --- a/crates/oxide_emit/src/expr/identifier.rs +++ b/crates/oxide_emit/src/expr/identifier.rs @@ -36,7 +36,14 @@ impl Emitter { return Ok(r); } - let var_reg = ctx.lookup_or_builtin(name)?; + let var_reg = match ctx.lookup_or_builtin(name) { + Ok(reg) => reg, + Err(err) if err.contains("before initialization") => { + // TDZ:块级预声明占位后,声明点前读取编译为运行时抛 ReferenceError。 + return self.emit_tdz_throw(&err, ctx); + } + Err(err) => return Err(err), + }; let r = ctx.alloc_reg(); ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(r), Operand::Reg(var_reg), Operand::None)); Ok(r) diff --git a/crates/oxide_emit/src/prepass.rs b/crates/oxide_emit/src/prepass.rs index a06aa3d..c79a384 100644 --- a/crates/oxide_emit/src/prepass.rs +++ b/crates/oxide_emit/src/prepass.rs @@ -5,7 +5,7 @@ //! 支持提升语义。 use crate::{CompileCtx, Emitter}; -use oxide_parser::{Expression, Statement, VariableDeclarationKind}; +use oxide_parser::{BindingPattern, Expression, Statement, VariableDeclarationKind}; impl Emitter { /// 在临时寄存器池之前分配 builtin 槽位。 @@ -360,4 +360,105 @@ impl Emitter { } } } + + /// 预声明当前作用域直接子语句中的 `let`/`const`/`class` 绑定(未初始化, + /// 建立 TDZ 占位)。使声明点之前的读取在编译期可分辨为 TDZ 而非隐式全局, + /// 声明点复用预登记槽位。 + /// + /// # 边界与前提 + /// - 只扫描直接子语句 + 递归 switch case(switch 不推 scope,case 内 lexical + /// 声明属于外层作用域);不递归块/if/for/while body(嵌套块自预声明, + /// 单语句 body 不含 lexical 声明)。 + /// - 跳过 for 头声明(循环作用域由 for 分支内联 declare)。 + pub(crate) fn predeclare_lexical_declarations(&self, statements: &[Statement], ctx: &mut CompileCtx) { + for statement in statements { + match statement { + Statement::VariableDeclaration(decl) => { + if matches!(decl.kind, VariableDeclarationKind::Var) { + continue; + } + let is_const = matches!(decl.kind, VariableDeclarationKind::Const); + for d in &decl.declarations { + self.predeclare_lexical_pattern(&d.id, is_const, ctx); + } + } + Statement::ClassDeclaration(cd) => { + if let Some(id) = &cd.id { + let reg = ctx.alloc_reg(); + let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Const, true); + } + } + Statement::SwitchStatement(sw) => { + for case in &sw.cases { + for s in &case.consequent { + self.predeclare_lexical_stmt(s, ctx); + } + } + } + _ => {} + } + } + } + + /// 预声明单个语句中的 lexical 声明(供 switch case 递归;`let`/`const`/`class` 分支)。 + fn predeclare_lexical_stmt(&self, stmt: &Statement, ctx: &mut CompileCtx) { + match stmt { + Statement::VariableDeclaration(decl) => { + if matches!(decl.kind, VariableDeclarationKind::Var) { + return; + } + let is_const = matches!(decl.kind, VariableDeclarationKind::Const); + for d in &decl.declarations { + self.predeclare_lexical_pattern(&d.id, is_const, ctx); + } + } + Statement::ClassDeclaration(cd) => { + if let Some(id) = &cd.id { + let reg = ctx.alloc_reg(); + let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Const, true); + } + } + _ => {} + } + } + + /// 递归预声明绑定 pattern 内的全部标识符(含数组/对象/默认值解构)。 + fn predeclare_lexical_pattern(&self, pattern: &BindingPattern, is_const: bool, ctx: &mut CompileCtx) { + match pattern { + BindingPattern::BindingIdentifier(bi) => { + let reg = ctx.alloc_reg(); + let _ = ctx.declare_predeclared( + bi.name.as_str(), + reg, + if is_const { + VariableDeclarationKind::Const + } else { + VariableDeclarationKind::Let + }, + is_const, + ); + } + BindingPattern::ArrayPattern(ap) => { + for elem in &ap.elements { + if let Some(e) = elem { + self.predeclare_lexical_pattern(e, is_const, ctx); + } + } + if let Some(rest) = &ap.rest { + self.predeclare_lexical_pattern(&rest.argument, is_const, ctx); + } + } + BindingPattern::ObjectPattern(op) => { + for prop in &op.properties { + self.predeclare_lexical_pattern(&prop.value, is_const, ctx); + } + if let Some(rest) = &op.rest { + self.predeclare_lexical_pattern(&rest.argument, is_const, ctx); + } + } + BindingPattern::AssignmentPattern(ap) => { + self.predeclare_lexical_pattern(&ap.left, is_const, ctx); + } + } + } } diff --git a/crates/oxide_emit/src/shared/binding.rs b/crates/oxide_emit/src/shared/binding.rs index b11258f..7249618 100644 --- a/crates/oxide_emit/src/shared/binding.rs +++ b/crates/oxide_emit/src/shared/binding.rs @@ -64,9 +64,15 @@ impl Emitter { var_reg } } else { - let var_reg = ctx.alloc_reg(); - ctx.declare(name, var_reg, kind, is_const)?; - var_reg + // let/const:本块预声明(TDZ 占位)则复用预登记槽位,不重复 declare; + // 否则(for 头等未预声明路径)按原逻辑声明。 + if let Some(reg) = ctx.scopes.symbols.consume_predeclared_slot(name) { + reg + } else { + let var_reg = ctx.alloc_reg(); + ctx.declare(name, var_reg, kind, is_const)?; + var_reg + } }; if let Some(&cell_idx) = ctx.captured_bindings.get(name) { let op = if fresh_cell { OpCode::MAKE_CELL_FRESH } else { OpCode::MAKE_CELL }; diff --git a/crates/oxide_emit/src/stmt/block.rs b/crates/oxide_emit/src/stmt/block.rs index 7c04d4f..a6e1e3a 100644 --- a/crates/oxide_emit/src/stmt/block.rs +++ b/crates/oxide_emit/src/stmt/block.rs @@ -9,6 +9,7 @@ impl Emitter { return Ok(None); }; ctx.push_scope(); + self.predeclare_lexical_declarations(&block.body, ctx); let mut r = None; for s in &block.body { if let Some(rr) = self.emit_statement(s, ctx)? { diff --git a/crates/oxide_emit/src/stmt/declaration/class_decl.rs b/crates/oxide_emit/src/stmt/declaration/class_decl.rs index 4e6c423..f1e9350 100644 --- a/crates/oxide_emit/src/stmt/declaration/class_decl.rs +++ b/crates/oxide_emit/src/stmt/declaration/class_decl.rs @@ -18,8 +18,14 @@ impl Emitter { .as_ref() .map(|id| id.name.to_string()) .ok_or_else(|| "ClassDeclaration without name".to_string())?; - let var_reg = ctx.alloc_reg(); - ctx.declare(&name, var_reg, VariableDeclarationKind::Let, false)?; + let var_reg = match ctx.scopes.symbols.consume_predeclared_slot(&name) { + Some(reg) => reg, + None => { + let reg = ctx.alloc_reg(); + ctx.declare(&name, reg, VariableDeclarationKind::Let, false)?; + reg + } + }; ctx.init_var(&name); let ctor_reg = self.emit_class(class, ctx)?; if let Some(&cell_idx) = ctx.captured_bindings.get(&name) { diff --git a/crates/oxide_emit/src/stmt/declaration/var.rs b/crates/oxide_emit/src/stmt/declaration/var.rs index 4744048..af8f92c 100644 --- a/crates/oxide_emit/src/stmt/declaration/var.rs +++ b/crates/oxide_emit/src/stmt/declaration/var.rs @@ -47,7 +47,16 @@ impl Emitter { Err(_) => ctx.lookup(bi.name.as_str()).unwrap_or(var_reg), } } else { - ctx.declare(bi.name.as_str(), var_reg, decl.kind, is_const)?; + // let/const 无初始化器:复用本块预声明槽位,否则新声明 + // (for 头等未预声明路径)。 + let var_reg = + if let Some(reg) = ctx.scopes.symbols.consume_predeclared_slot(bi.name.as_str()) { + reg + } else { + let r = ctx.alloc_reg(); + ctx.declare(bi.name.as_str(), r, decl.kind, is_const)?; + r + }; var_reg }; if let Some(&cell_idx) = ctx.captured_bindings.get(bi.name.as_str()) { diff --git a/crates/oxide_emit/src/stmt/exception/try_catch.rs b/crates/oxide_emit/src/stmt/exception/try_catch.rs index 840a757..96f4aa9 100644 --- a/crates/oxide_emit/src/stmt/exception/try_catch.rs +++ b/crates/oxide_emit/src/stmt/exception/try_catch.rs @@ -29,11 +29,16 @@ impl Emitter { ctx.push_open_catch_handler(); } let mut last_try_result: Option = None; + // try block 是独立块作用域:lexical 声明(let/const/class)限定于 try 内, + // 与外层同名绑定互不干扰;块级预声明使声明点前读取编译为 TDZ 抛错。 + ctx.push_scope(); + self.predeclare_lexical_declarations(&ts.block.body, ctx); for s in &ts.block.body { if let Some(r) = self.emit_statement(s, ctx)? { last_try_result = Some(r); } } + ctx.pop_scope(); ctx.inst(Inst::new( OpCode::LOAD_VAR, Operand::Reg(result_reg), @@ -58,6 +63,7 @@ impl Emitter { ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(src_reg), Operand::None, Operand::None)); self.emit_binding_pattern(¶m.pattern, src_reg, VariableDeclarationKind::Let, false, false, ctx)?; } + self.predeclare_lexical_declarations(&catch.body.body, ctx); let mut last_catch_result: Option = None; for s in &catch.body.body { if let Some(r) = self.emit_statement(s, ctx)? { @@ -75,11 +81,15 @@ impl Emitter { if has_finally { ctx.labels.set_label_pos(finally_label, ctx.insts.len()); let mut last_finally_result: Option = None; + // finally block 同为独立块作用域,lexical 声明互不泄漏。 + ctx.push_scope(); + self.predeclare_lexical_declarations(&ts.finalizer.as_ref().unwrap().body, ctx); for s in &ts.finalizer.as_ref().unwrap().body { if let Some(r) = self.emit_statement(s, ctx)? { last_finally_result = Some(r); } } + ctx.pop_scope(); ctx.inst(Inst::new( OpCode::LOAD_VAR, Operand::Reg(result_reg), diff --git a/crates/oxide_emit/src/symbol_table.rs b/crates/oxide_emit/src/symbol_table.rs index f6a3d76..cbaef8e 100644 --- a/crates/oxide_emit/src/symbol_table.rs +++ b/crates/oxide_emit/src/symbol_table.rs @@ -23,6 +23,9 @@ pub(crate) struct Binding { pub(crate) reg: u32, pub(crate) initialized: bool, pub(crate) is_const: bool, + /// 由块/函数级预声明(TDZ 占位)创建,声明点据此复用槽位。 + /// 非预声明的同名绑定(如同 scope 的参数/var)不计,避免误复用。 + pub(crate) predeclared: bool, } /// 作用域符号表:名字 → 寄存器号/初始化状态/const 标志。 @@ -97,6 +100,7 @@ impl SymbolTable { reg, initialized: false, is_const: matches!(kind, VariableDeclarationKind::Const) || is_const, + predeclared: false, }, ); Ok(()) @@ -131,6 +135,19 @@ impl SymbolTable { None } + /// 消费当前(最内层)作用域中由预声明创建的绑定槽:存在且 `predeclared` + /// 时返回其寄存器并清除标志(声明点复用后不再视作预声明);否则返回 None。 + /// 同 scope 的非预声明绑定(参数/var/未推 scope 的 try 内声明)不计,避免误复用。 + pub(crate) fn consume_predeclared_slot(&mut self, name: &str) -> Option { + let scope = self.scopes.last_mut()?; + let binding = scope.bindings.get_mut(name)?; + if !binding.predeclared { + return None; + } + binding.predeclared = false; + Some(binding.reg) + } + /// 查找或视为全局:未命中时以 `reg_for_new` 在全局作用域登记并返回(隐式全局)。 pub fn lookup_or_global(&mut self, name: &str, reg_for_new: u32) -> u32 { for scope in self.scopes.iter().rev() { @@ -144,6 +161,7 @@ impl SymbolTable { reg: reg_for_new, initialized: true, is_const: false, + predeclared: false, }, ); reg_for_new @@ -183,6 +201,7 @@ impl SymbolTable { reg, initialized: true, is_const: matches!(kind, VariableDeclarationKind::Const) || is_const, + predeclared: false, }, ); Ok(()) @@ -194,9 +213,36 @@ impl SymbolTable { reg, initialized: true, is_const: false, + predeclared: false, }); } + /// 声明未初始化绑定并标记为预声明(TDZ 占位),供声明点复用槽位。 + /// 与 `declare` 同作用域规则,仅 `predeclared` 标志不同。 + pub(crate) fn declare_predeclared( + &mut self, name: &str, reg: u32, kind: VariableDeclarationKind, is_const: bool, + ) -> Result<(), String> { + let target_idx = if matches!(kind, VariableDeclarationKind::Var) { + self.find_var_target_scope() + } else { + self.scopes.len() - 1 + }; + let target = &mut self.scopes[target_idx]; + if target.bindings.contains_key(name) { + return Err(format!("Identifier '{name}' has already been declared")); + } + target.bindings.insert( + name.to_string(), + Binding { + reg, + initialized: false, + is_const: matches!(kind, VariableDeclarationKind::Const) || is_const, + predeclared: true, + }, + ); + Ok(()) + } + /// 从内到外将同名绑定标记为已初始化(var 提升后初始化阶段使用)。 pub fn init_var(&mut self, name: &str) { for scope in self.scopes.iter_mut().rev() {