fix: 修复编译阶段缺失的预注册顶层 var 名步骤

This commit is contained in:
2026-08-05 15:21:00 +08:00
parent dce335e766
commit c250f5cb2b
4 changed files with 104 additions and 8 deletions
+75
View File
@@ -1283,6 +1283,73 @@ impl Compiler {
}
}
/// Pre-declare all `var` bindings in this statement list so hoisted function
/// declarations emitted earlier can resolve them. Mirrors the removed count
/// pass behavior (see fe8bd86): top-level `var` names must be visible while
/// compiling function bodies that close over them.
fn predeclare_var_declarations(&self, statements: &[Statement], ctx: &mut CompileCtx) {
for statement in statements {
match statement {
Statement::VariableDeclaration(decl) => {
if !matches!(decl.kind, VariableDeclarationKind::Var) {
continue;
}
for d in &decl.declarations {
if let oxide_parser::BindingPattern::BindingIdentifier(bi) = &d.id {
let reg = ctx.alloc_reg();
let _ = ctx.declare_initialized(bi.name.as_str(), reg, VariableDeclarationKind::Var, false);
}
}
}
Statement::BlockStatement(bs) => self.predeclare_var_declarations(&bs.body, ctx),
Statement::IfStatement(is) => {
self.predeclare_var_declarations(std::slice::from_ref(&is.consequent), ctx);
if let Some(alt) = &is.alternate {
self.predeclare_var_declarations(std::slice::from_ref(alt), ctx);
}
}
Statement::WhileStatement(wh) => {
self.predeclare_var_declarations(std::slice::from_ref(&wh.body), ctx);
}
Statement::DoWhileStatement(dw) => {
self.predeclare_var_declarations(std::slice::from_ref(&dw.body), ctx);
}
Statement::ForStatement(fs) => {
if let Some(oxide_parser::ForStatementInit::VariableDeclaration(decl)) = &fs.init {
if matches!(decl.kind, VariableDeclarationKind::Var) {
for d in &decl.declarations {
if let oxide_parser::BindingPattern::BindingIdentifier(bi) = &d.id {
let reg = ctx.alloc_reg();
let _ =
ctx.declare_initialized(bi.name.as_str(), reg, VariableDeclarationKind::Var, false);
}
}
}
}
self.predeclare_var_declarations(std::slice::from_ref(&fs.body), ctx);
}
Statement::SwitchStatement(sw) => {
for case in &sw.cases {
self.predeclare_var_declarations(&case.consequent, ctx);
}
}
Statement::TryStatement(ts) => {
self.predeclare_var_declarations(&ts.block.body, ctx);
if let Some(handler) = &ts.handler {
self.predeclare_var_declarations(&handler.body.body, ctx);
}
if let Some(finalizer) = &ts.finalizer {
self.predeclare_var_declarations(&finalizer.body, ctx);
}
}
Statement::LabeledStatement(ls) => {
self.predeclare_var_declarations(std::slice::from_ref(&ls.body), ctx);
}
_ => {}
}
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn compile_function_body_with_field_hooks<'a, E>(
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
@@ -1391,6 +1458,10 @@ impl Compiler {
// register, so builtin slots never collide with reused temporaries.
self.pre_register_builtin_references(body_stmts, &mut ctx);
// Pre-declare `var` names so hoisted function declarations (emitted in the
// first sub-pass below) can resolve the outer vars they close over.
self.predeclare_var_declarations(body_stmts, &mut ctx);
if let Some(emit) = emit_fields.as_mut() {
if fields_after_super {
let mut parent_bytecode = Vec::new();
@@ -1569,6 +1640,10 @@ impl Compiler {
// is emitted, keeping builtin slots clear of the temporary register pool.
self.pre_register_builtin_references(&program.body, &mut ctx);
// Pre-declare top-level `var` names so hoisted function declarations
// (emitted in the first sub-pass below) can resolve the outer vars.
self.predeclare_var_declarations(&program.body, &mut ctx);
// First sub-pass: emit FunctionDeclarations (hoisting)
// This ensures function objects are available before any code runs.
for stmt in &program.body {
+9 -4
View File
@@ -27,13 +27,18 @@ impl Compiler {
fn emit_bind_target(
&self, name: &str, src_reg: u8, kind: VariableDeclarationKind, is_const: bool, ctx: &mut CompileCtx,
) -> Result<(), String> {
let var_reg = ctx.alloc_reg();
let target_reg = if matches!(kind, VariableDeclarationKind::Var) {
match ctx.declare(name, var_reg, kind, is_const) {
Ok(()) => var_reg,
Err(_) => ctx.lookup(name).unwrap_or(var_reg),
// `var` names are pre-declared (hoisting); reuse the pre-registered slot
// instead of allocating a new one so n_registers matches let/const.
if let Some(reg) = ctx.scopes.symbols.lookup_any(name) {
reg
} else {
let var_reg = ctx.alloc_reg();
ctx.declare(name, var_reg, kind, is_const)?;
var_reg
}
} else {
let var_reg = ctx.alloc_reg();
ctx.declare(name, var_reg, kind, is_const)?;
var_reg
};
@@ -37,14 +37,22 @@ impl Compiler {
let tmp = ctx.alloc_reg();
ctx.emit_load_const(tmp, idx);
let var_reg = ctx.alloc_reg();
ctx.declare(bi.name.as_str(), var_reg, decl.kind, is_const)?;
let target_reg = if matches!(decl.kind, VariableDeclarationKind::Var) {
match ctx.declare(bi.name.as_str(), var_reg, decl.kind, is_const) {
Ok(()) => var_reg,
Err(_) => ctx.lookup(bi.name.as_str()).unwrap_or(var_reg),
}
} else {
ctx.declare(bi.name.as_str(), var_reg, decl.kind, is_const)?;
var_reg
};
let is_captured = ctx.scopes.symbols.lookup_is_captured(bi.name.as_str());
if is_captured {
let cell_idx = ctx.scopes.cell_registry.len() as u8;
ctx.scopes.cell_registry.push((bi.name.to_string(), cell_idx));
ctx.emit(opcode::encode(OpCode::MAKE_CELL, tmp, cell_idx, 0));
} else {
ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, tmp, 0));
ctx.emit(opcode::encode(OpCode::STORE_VAR, target_reg, tmp, 0));
}
ctx.init_var(bi.name.as_str());
r = Some(var_reg);
@@ -30,8 +30,16 @@ impl Compiler {
let tmp = ctx.alloc_reg();
ctx.emit_load_const(tmp, idx);
let var_reg = ctx.alloc_reg();
ctx.declare(bi.name.as_str(), var_reg, decl.kind, is_const)?;
ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, tmp, 0));
let target_reg = if matches!(decl.kind, VariableDeclarationKind::Var) {
match ctx.declare(bi.name.as_str(), var_reg, decl.kind, is_const) {
Ok(()) => var_reg,
Err(_) => ctx.lookup(bi.name.as_str()).unwrap_or(var_reg),
}
} else {
ctx.declare(bi.name.as_str(), var_reg, decl.kind, is_const)?;
var_reg
};
ctx.emit(opcode::encode(OpCode::STORE_VAR, target_reg, tmp, 0));
ctx.init_var(bi.name.as_str());
}
}