fix: 脚本顶层 var/function 声明落 globalThis(DEFINE_GLOBAL_PROP 非可配置数据属性)

This commit is contained in:
2026-08-10 03:52:17 +08:00
parent a5a910a24e
commit e03ec0f635
11 changed files with 187 additions and 13 deletions
+22
View File
@@ -166,6 +166,9 @@ pub struct CompileCtx {
pub(crate) is_generator: bool,
/// 本函数是否为异步函数体(`async function` / async 箭头),`assemble_ir` 回写到 IR。
pub(crate) is_async: bool,
/// 是否为脚本顶层模块(emit_program 的根上下文)。顶层 var/function 声明需
/// 同步写全局对象属性(脚本环境记录的 var 可经 globalThis 反射);函数体为 false。
pub(crate) is_global_scope: bool,
pub(crate) static_block_this_reg: Option<u8>,
pub(crate) field_buffer: Option<FieldBuffer>,
/// 类构造器模块中 `@@field_keys` upvalue 下标(实例字段 computed key 数组)。
@@ -274,6 +277,7 @@ impl CompileCtx {
in_static_method: false,
is_generator: false,
is_async: false,
is_global_scope: false,
static_block_this_reg: None,
field_buffer: None,
field_keys_uv: None,
@@ -1245,12 +1249,30 @@ impl Emitter {
}
}
/// 把脚本顶层 var/function 绑定的当前值同步写入全局对象属性,使顶层声明
/// 可经 `globalThis` 反射(脚本环境记录的 var 绑定全局对象属性)。
///
/// # 边界与前提
/// - 仅顶层模块上下文调用:顶层 `this`(物理寄存器 254)恒为全局对象。
/// - let/const/class 不落全局对象,不得调用本函数。
///
/// # 副作用
/// - 定义全局对象数据属性(可写/可枚举/不可配置),属性缺失时新建。
pub(crate) fn emit_global_prop_write(&self, name: &str, val_reg: u32, ctx: &mut CompileCtx) {
let idx = ctx.add_constant(Constant::String(name.to_string()));
let key_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx));
ctx.inst(Inst::define_global_prop(Operand::This, Operand::Reg(val_reg), Operand::Reg(key_reg)));
}
/// 把完整程序编译为顶层模块体的 IRFunction。
///
/// 调用方为 `oxide_compiler::Compiler::compile`:本函数完成 emit 半程,
/// 随后由 `oxide_ir::lower::lower` 降为字节码。
pub fn emit_program(&self, program: &oxide_parser::Program) -> Result<IRFunction, String> {
let mut ctx = CompileCtx::new();
// 脚本顶层:var/function 声明需落到全局对象,let/const/class 不进全局。
ctx.is_global_scope = true;
ctx.pre_register_builtins();
self.predeclare_function_declarations(&program.body, &mut ctx);
+12 -4
View File
@@ -74,6 +74,7 @@ impl Emitter {
));
} else if !ctx.with_stack.is_empty() && !ctx.is_with_internal_binding(name) {
// with 内 var 初始化:对象有该属性则写对象,否则写提升槽(动态解析)。
// ponytail: with 内顶层 var 不写全局对象属性,with 语句本身已是稀见用例。
self.emit_with_dynamic_write(name, src_reg, 0, ctx);
} else {
// const 声明路径 STORE_VAR 恒 b=0,不查运行时 guard。
@@ -88,6 +89,10 @@ impl Emitter {
));
}
ctx.init_var(name);
// 脚本顶层 var:同步写全局对象,使 globalThis.x 反射声明值。
if ctx.is_global_scope && matches!(kind, VariableDeclarationKind::Var) {
self.emit_global_prop_write(name, src_reg, ctx);
}
Ok(())
}
@@ -240,9 +245,7 @@ impl Emitter {
}
/// 向 excluded 数组下标 `idx` 写入一个键:`key_val` 为运行时寄存器值,否则用 `key_str` 常量。
fn emit_push_excluded_key(
&self, arr: u32, key_val: Option<u32>, key_str: &str, idx: usize, ctx: &mut CompileCtx,
) {
fn emit_push_excluded_key(&self, arr: u32, key_val: Option<u32>, key_str: &str, idx: usize, ctx: &mut CompileCtx) {
let val_reg = match key_val {
Some(kv) => kv,
None => {
@@ -255,7 +258,12 @@ impl Emitter {
let elem_idx = ctx.alloc_reg();
let cidx = ctx.add_constant(Constant::Int(idx as i32));
ctx.inst(Inst::load_const(Operand::Reg(elem_idx), cidx));
ctx.inst(Inst::new(OpCode::SET_ELEM, Operand::Reg(arr), Operand::Reg(elem_idx), Operand::Reg(val_reg)));
ctx.inst(Inst::new(
OpCode::SET_ELEM,
Operand::Reg(arr),
Operand::Reg(elem_idx),
Operand::Reg(val_reg),
));
}
fn emit_assignment_maybe_default(
@@ -60,6 +60,10 @@ impl Emitter {
} else {
ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(var_reg), Operand::None));
}
// 脚本顶层函数声明:同步写全局对象,使 globalThis 可反射函数名。
if ctx.is_global_scope {
self.emit_global_prop_write(&name, var_reg, ctx);
}
Ok(None)
}
}
@@ -61,6 +61,10 @@ impl Emitter {
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)。
if ctx.is_global_scope && matches!(decl.kind, VariableDeclarationKind::Var) {
self.emit_global_prop_write(bi.name.as_str(), tmp, ctx);
}
r = Some(var_reg);
}
}