fix: 块级 let/const/class 预声明 + TDZ 读取运行时抛 ReferenceError

This commit is contained in:
2026-08-10 21:07:39 +08:00
parent a968b89658
commit 973778f181
9 changed files with 237 additions and 8 deletions
+43
View File
@@ -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<u32, String> {
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);
+8 -1
View File
@@ -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)
+102 -1
View File
@@ -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);
}
}
}
}
+9 -3
View File
@@ -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 };
+1
View File
@@ -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)? {
@@ -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) {
+10 -1
View File
@@ -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()) {
@@ -29,11 +29,16 @@ impl Emitter {
ctx.push_open_catch_handler();
}
let mut last_try_result: Option<u32> = 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(&param.pattern, src_reg, VariableDeclarationKind::Let, false, false, ctx)?;
}
self.predeclare_lexical_declarations(&catch.body.body, ctx);
let mut last_catch_result: Option<u32> = 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<u32> = 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),
+46
View File
@@ -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<u32> {
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() {