fix: 顶层 var 全局属性求值前创建(声明定时语义)+ 无初始化 var 声明不再抹槽值

This commit is contained in:
2026-09-09 22:00:29 +08:00
parent bd1427b116
commit 2457f283fe
6 changed files with 115 additions and 16 deletions
+13
View File
@@ -1555,6 +1555,8 @@ impl Emitter {
/// 随后由 `oxide_ir::lower::lower` 降为字节码。
/// `repl_persist` 为 true 时脚本顶层 let/const 也写全局对象(REPL 跨轮次持久);
/// `is_eval_script` 为 true 时顶层 var/function 声明落全局属性 configurable:true。
/// 顶层 var 的全局对象属性在求值开始前统一创建(值 undefined),声明语句
/// 保持赋值语义——声明语句出现之前的读取(typeof/反射/自引用)即见绑定。
pub fn emit_program(
&self, program: &oxide_parser::Program, repl_persist: bool, is_eval_script: bool,
) -> Result<IRFunction, String> {
@@ -1603,6 +1605,17 @@ impl Emitter {
}
}
// 全局声明实例化序言:脚本求值前为顶层 var 名创建全局对象属性(值 undefined),
// 使声明语句执行前的读取(typeof、反射、自引用)可经全局对象见绑定;同名
// 函数声明的属性由首个 sub-pass 以函数值覆盖,声明语句保持值更新语义。
let gdi_var_names = self.collect_var_binding_names(&program.body);
if !gdi_var_names.is_empty() {
let undef_reg = self.emit_undefined(&mut ctx);
for name in &gdi_var_names {
self.emit_global_prop_write(name, undef_reg, &mut ctx);
}
}
// 首个 sub-pass:发函数声明(hoisting),保证任何代码运行前函数对象已就绪。
for stmt in &program.body {
if matches!(stmt, Statement::FunctionDeclaration(_)) {
+43 -14
View File
@@ -48,37 +48,66 @@ impl Emitter {
let tmp = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(tmp), idx));
let var_reg = ctx.alloc_reg();
let target_reg = if matches!(decl.kind, VariableDeclarationKind::Var) {
// 无初始化 var 是纯声明而非赋值:绑定已预先存在(提升引用或先前写入)
// 时保留槽值,仅首次声明把 undefined 物化进槽。
let (target_reg, already_bound) = 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),
Ok(()) => (var_reg, false),
Err(_) => (ctx.lookup(bi.name.as_str()).unwrap_or(var_reg), true),
}
} else {
// let/const 无初始化器:复用本块预声明槽位,否则新声明
// (for 头等未预声明路径)。
let var_reg = if let Some(reg) = ctx.scopes.symbols.consume_predeclared_slot(bi.name.as_str()) {
let target = 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
(target, false)
};
if let Some(&cell_idx) = ctx.captured_bindings.get(bi.name.as_str()) {
ctx.inst(Inst::new(
OpCode::MAKE_CELL,
Operand::Reg(tmp),
Operand::Imm(cell_idx as u16),
Operand::None,
));
} else {
// 被捕获绑定的 cell 在函数入口已实例化(undefined);声明语句
// 不赋值,仅首次声明刷新初值,已绑定的 cell 保留现值。
if !already_bound {
ctx.inst(Inst::new(
OpCode::MAKE_CELL,
Operand::Reg(tmp),
Operand::Imm(cell_idx as u16),
Operand::None,
));
}
} else if !already_bound {
ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(target_reg), Operand::Reg(tmp), Operand::None));
}
ctx.init_var(bi.name.as_str());
// 脚本顶层 var 无初始化:仍须写全局对象属性(值为 undefined)。
// 脚本顶层 var:全局对象属性同步绑定当前值(首次声明即 undefined,
// 先前已写入则保留写入值,声明不是覆盖性赋值)。被捕获绑定的值
// 存在 cell 而非槽,从 cell 同步。
if ctx.is_global_scope && matches!(decl.kind, VariableDeclarationKind::Var) {
self.emit_global_prop_write(bi.name.as_str(), tmp, ctx);
let src = if let Some(&cell_idx) = ctx.captured_bindings.get(bi.name.as_str()) {
let r = ctx.alloc_reg();
if let Some((binding, _)) = ctx.scopes.symbols.lookup_any_binding(bi.name.as_str()) {
ctx.inst(Inst::new(
OpCode::CELL_GET,
Operand::Reg(r),
Operand::Reg(binding.reg),
Operand::Imm(cell_idx as u16),
));
} else {
ctx.inst(Inst::new(
OpCode::CELL_GET,
Operand::Reg(r),
Operand::None,
Operand::Imm(cell_idx as u16),
));
}
r
} else {
target_reg
};
self.emit_global_prop_write(bi.name.as_str(), src, ctx);
}
r = Some(var_reg);
}