fix: 修复编译阶段缺失的预注册顶层 var 名步骤
This commit is contained in:
@@ -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)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) fn compile_function_body_with_field_hooks<'a, E>(
|
pub(crate) fn compile_function_body_with_field_hooks<'a, E>(
|
||||||
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
|
&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.
|
// register, so builtin slots never collide with reused temporaries.
|
||||||
self.pre_register_builtin_references(body_stmts, &mut ctx);
|
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 let Some(emit) = emit_fields.as_mut() {
|
||||||
if fields_after_super {
|
if fields_after_super {
|
||||||
let mut parent_bytecode = Vec::new();
|
let mut parent_bytecode = Vec::new();
|
||||||
@@ -1569,6 +1640,10 @@ impl Compiler {
|
|||||||
// is emitted, keeping builtin slots clear of the temporary register pool.
|
// is emitted, keeping builtin slots clear of the temporary register pool.
|
||||||
self.pre_register_builtin_references(&program.body, &mut ctx);
|
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)
|
// First sub-pass: emit FunctionDeclarations (hoisting)
|
||||||
// This ensures function objects are available before any code runs.
|
// This ensures function objects are available before any code runs.
|
||||||
for stmt in &program.body {
|
for stmt in &program.body {
|
||||||
|
|||||||
@@ -27,13 +27,18 @@ impl Compiler {
|
|||||||
fn emit_bind_target(
|
fn emit_bind_target(
|
||||||
&self, name: &str, src_reg: u8, kind: VariableDeclarationKind, is_const: bool, ctx: &mut CompileCtx,
|
&self, name: &str, src_reg: u8, kind: VariableDeclarationKind, is_const: bool, ctx: &mut CompileCtx,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let var_reg = ctx.alloc_reg();
|
|
||||||
let target_reg = if matches!(kind, VariableDeclarationKind::Var) {
|
let target_reg = if matches!(kind, VariableDeclarationKind::Var) {
|
||||||
match ctx.declare(name, var_reg, kind, is_const) {
|
// `var` names are pre-declared (hoisting); reuse the pre-registered slot
|
||||||
Ok(()) => var_reg,
|
// instead of allocating a new one so n_registers matches let/const.
|
||||||
Err(_) => ctx.lookup(name).unwrap_or(var_reg),
|
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 {
|
} else {
|
||||||
|
let var_reg = ctx.alloc_reg();
|
||||||
ctx.declare(name, var_reg, kind, is_const)?;
|
ctx.declare(name, var_reg, kind, is_const)?;
|
||||||
var_reg
|
var_reg
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,14 +37,22 @@ impl Compiler {
|
|||||||
let tmp = ctx.alloc_reg();
|
let tmp = ctx.alloc_reg();
|
||||||
ctx.emit_load_const(tmp, idx);
|
ctx.emit_load_const(tmp, idx);
|
||||||
let var_reg = ctx.alloc_reg();
|
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());
|
let is_captured = ctx.scopes.symbols.lookup_is_captured(bi.name.as_str());
|
||||||
if is_captured {
|
if is_captured {
|
||||||
let cell_idx = ctx.scopes.cell_registry.len() as u8;
|
let cell_idx = ctx.scopes.cell_registry.len() as u8;
|
||||||
ctx.scopes.cell_registry.push((bi.name.to_string(), cell_idx));
|
ctx.scopes.cell_registry.push((bi.name.to_string(), cell_idx));
|
||||||
ctx.emit(opcode::encode(OpCode::MAKE_CELL, tmp, cell_idx, 0));
|
ctx.emit(opcode::encode(OpCode::MAKE_CELL, tmp, cell_idx, 0));
|
||||||
} else {
|
} 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());
|
ctx.init_var(bi.name.as_str());
|
||||||
r = Some(var_reg);
|
r = Some(var_reg);
|
||||||
|
|||||||
@@ -30,8 +30,16 @@ impl Compiler {
|
|||||||
let tmp = ctx.alloc_reg();
|
let tmp = ctx.alloc_reg();
|
||||||
ctx.emit_load_const(tmp, idx);
|
ctx.emit_load_const(tmp, idx);
|
||||||
let var_reg = ctx.alloc_reg();
|
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) {
|
||||||
ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, tmp, 0));
|
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());
|
ctx.init_var(bi.name.as_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user