fix: tagged template 语义修正——cooked undefined/this绑定/site缓存/冻结 + groupBy getter 异常传播 + defineGetter setter 保留

This commit is contained in:
2026-08-22 11:15:55 +08:00
parent 9eb0944877
commit 0ffb5d2cf3
10 changed files with 366 additions and 83 deletions
+8
View File
@@ -230,6 +230,10 @@ pub struct CompileCtx {
pub(crate) module_self_import_specs: HashSet<String>,
/// 自导入别名:导出名 → 本地绑定槽寄存器(export 语句执行时回写绑定值)。
pub(crate) module_self_aliases: HashMap<String, u32>,
/// 标签模板 site 计数器:本编译树内全局唯一(子 ctx 继承父值继续递增)。
/// 运行时与模块 flat_id 组成模板对象缓存键,保证同一编译树同 site 恒返回
/// 同一对象、不同编译树(eval 每次编译)互不共享。
pub(crate) next_template_site: u32,
}
/// 函数体编译上下文:决定 `this`/`super` 绑定与参数前导(prologue)形态。
@@ -337,6 +341,7 @@ impl CompileCtx {
module_dep_ns_regs: HashMap::new(),
module_self_import_specs: HashSet::new(),
module_self_aliases: HashMap::new(),
next_template_site: 0,
}
}
@@ -1059,6 +1064,9 @@ impl Emitter {
ctx.scopes.private_element_kinds = parent_ctx.scopes.private_element_kinds.clone();
ctx.scopes.private_brand_id = parent_ctx.scopes.private_brand_id;
ctx.scopes.next_private_name_id = parent_ctx.scopes.next_private_name_id;
// 标签模板 site 序号继承:整棵编译树全局唯一(跨嵌套函数递增),
// 运行时以 (flat_id, site_no) 缓存模板对象,跨函数不冲突。
ctx.next_template_site = parent_ctx.next_template_site;
// 传递 enclosing_this_reg:嵌套箭头函数捕获正确的 `this`。
ctx.enclosing_this_reg = parent_ctx.enclosing_this_reg;
+134 -71
View File
@@ -1,107 +1,73 @@
//! 标签模板表达式 emit:`emit_tagged_template_expression` 构造 template object 并调用标签函数。
//! 标签模板表达式 emit:`emit_tagged_template_expression` 按 GetTemplateObject 语义
//! 经 GET_TEMPLATE_OBJECT 指令取(缓存)模板对象并调用标签函数。
use crate::{CompileCtx, Emitter};
use oxide_bytecode::module::Constant;
use oxide_bytecode::opcode::OpCode;
use oxide_ir::inst::Inst;
use oxide_ir::operand::Operand;
use oxide_parser::Expression;
impl Emitter {
pub(crate) fn emit_tagged_template_expression(
&self, tt: &oxide_parser::TaggedTemplateExpression, ctx: &mut CompileCtx,
) -> Result<u32, String> {
let quasis = &tt.quasi.quasis;
let expressions = &tt.quasi.expressions;
// 标签求值 + receiver 绑定:成员表达式标签保留对象作 this(与 call.rs 同款
// 约定),其余标签 this = undefined。
let (tag_reg, this_reg) = self.emit_tagged_tag(&tt.tag, ctx)?;
let tag_reg = self.emit_expression(&tt.tag, ctx)?;
let cooked_temp = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::NEW_ARRAY,
Operand::Reg(cooked_temp),
Operand::Imm(quasis.len() as u16),
Operand::None,
));
for (i, quasi) in quasis.iter().enumerate() {
let s = quasi.value.cooked.as_ref().map(|c| c.to_string()).unwrap_or_default();
let const_idx = ctx.add_constant(Constant::String(s));
let str_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(str_reg), const_idx));
let idx_const = ctx.add_constant(Constant::Int(i as i32));
let idx_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(idx_reg), idx_const));
ctx.inst(Inst::new(
OpCode::SET_ELEM,
Operand::Reg(cooked_temp),
Operand::Reg(idx_reg),
Operand::Reg(str_reg),
));
// 模板对象:GetTemplateObject 语义(cooked/raw 数组 + raw 属性,冻结 +
// 按 site 缓存)由 GET_TEMPLATE_OBJECT 指令在运行时统一构建/复用。
// cooked 为 None(非法转义)→ 元素 undefined;raw 恒为字符串。
let mut cooked_words: Vec<u32> = Vec::with_capacity(tt.quasi.quasis.len());
let mut raw_idxs: Vec<u16> = Vec::with_capacity(tt.quasi.quasis.len());
for quasi in &tt.quasi.quasis {
match quasi.value.cooked.as_ref() {
Some(c) => {
let idx = ctx.add_constant(Constant::String(c.to_string()));
cooked_words.push(idx as u32);
}
None => cooked_words.push(0x8000_0000),
}
let raw_idx = ctx.add_constant(Constant::String(quasi.value.raw.to_string()));
raw_idxs.push(raw_idx);
}
let raw_temp = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::NEW_ARRAY,
Operand::Reg(raw_temp),
Operand::Imm(quasis.len() as u16),
Operand::None,
));
for (i, quasi) in quasis.iter().enumerate() {
let raw = quasi.value.raw.to_string();
let const_idx = ctx.add_constant(Constant::String(raw));
let str_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(str_reg), const_idx));
let idx_const = ctx.add_constant(Constant::Int(i as i32));
let idx_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(idx_reg), idx_const));
ctx.inst(Inst::new(
OpCode::SET_ELEM,
Operand::Reg(raw_temp),
Operand::Reg(idx_reg),
Operand::Reg(str_reg),
));
}
// 模板对象:按 GetTemplateObject 语义把 raw 数组挂到 cooked 数组的 "raw"
// 属性,使标签函数首参为标准模板对象(cooked 数组 + raw 属性)。
// String.raw / 模板标签测试依赖 template.raw 存在。
let raw_key_idx = ctx.add_constant(Constant::String("raw".to_string()));
let raw_key_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(raw_key_reg), raw_key_idx));
ctx.inst(Inst::new(
OpCode::SET_PROP,
Operand::Reg(cooked_temp),
Operand::Reg(raw_temp),
Operand::Reg(raw_key_reg),
let site_no = ctx.next_template_site;
ctx.next_template_site += 1;
let template_reg = ctx.alloc_reg();
ctx.inst(Inst::get_template_object(
Operand::Reg(template_reg),
site_no,
&cooked_words,
&raw_idxs,
));
// 表达式实参:求值顺序在模板对象之后(规范 TaggedTemplate 求值序:
// 标签引用 → GetTemplateObject → 实参表达式)。
let mut expr_temps = Vec::new();
for expr in expressions {
for expr in &tt.quasi.expressions {
expr_temps.push(self.emit_expression(expr, ctx)?);
}
let cooked_slot = ctx.alloc_reg();
let mut expr_slots = Vec::new();
for _ in expressions {
for _ in &tt.quasi.expressions {
expr_slots.push(ctx.alloc_reg());
}
ctx.inst(Inst::new(
OpCode::LOAD_VAR,
Operand::Reg(cooked_slot),
Operand::Reg(cooked_temp),
Operand::Reg(template_reg),
Operand::None,
));
for (slot, temp) in expr_slots.iter().zip(expr_temps.iter()) {
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(*slot), Operand::Reg(*temp), Operand::None));
}
let undef_idx = ctx.add_constant(Constant::Undefined);
let undef_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx));
let arg_count = 1 + expressions.len();
let arg_count = 1 + tt.quasi.expressions.len();
ctx.inst(Inst::call(
Operand::Reg(tag_reg),
Operand::Reg(undef_reg),
Operand::Reg(this_reg),
Operand::Reg(cooked_slot),
arg_count as u8,
));
@@ -110,4 +76,101 @@ impl Emitter {
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::None, Operand::None));
Ok(result_reg)
}
}
/// 标签表达式的 `(callee, this)` 对:成员表达式(静态/计算/私有字段)保留
/// 对象作 receiver(GetThisValue(tagRef)),其余标签 this = undefined。
/// 与 call.rs 的 `emit_call_expression` 同款求值模式。
fn emit_tagged_tag(&self, tag: &Expression, ctx: &mut CompileCtx) -> Result<(u32, u32), String> {
let undefined_reg = {
let idx = ctx.add_constant(Constant::Undefined);
let r = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(r), idx));
r
};
let pair = match tag {
Expression::StaticMemberExpression(member) => {
if matches!(&member.object, Expression::Super(_)) {
if !ctx.in_instance_method && !ctx.in_static_method && !ctx.in_derived_constructor {
return Err("super property only supported in class methods".into());
}
let prop_name = member.property.name.as_str();
let key_reg = ctx.alloc_reg();
let idx = ctx.add_constant(Constant::String(prop_name.to_string()));
ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx));
let this_reg = ctx.alloc_reg();
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(this_reg), Operand::This, Operand::None));
let callee_reg = ctx.alloc_reg();
let op = if ctx.in_static_method {
OpCode::SUPER_STATIC_GET_PROP
} else {
OpCode::SUPER_GET_PROP
};
ctx.inst(Inst::new(op, Operand::Reg(callee_reg), Operand::Reg(this_reg), Operand::Reg(key_reg)));
(callee_reg, this_reg)
} else {
let obj_reg = self.emit_expression(&member.object, ctx)?;
let callee_reg = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::LOAD_VAR,
Operand::Reg(callee_reg),
Operand::Reg(obj_reg),
Operand::None,
));
let prop_name = member.property.name.as_str();
let key_reg = ctx.alloc_reg();
let idx = ctx.add_constant(Constant::String(prop_name.to_string()));
ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx));
ctx.inst(Inst::ic_get(Operand::Reg(callee_reg), Operand::Reg(key_reg)));
(callee_reg, obj_reg)
}
}
Expression::ComputedMemberExpression(member) => {
let obj_reg = self.emit_expression(&member.object, ctx)?;
if let Some(key) = crate::expr::member::computed_const_key(&member.expression) {
let key_reg = ctx.alloc_reg();
let idx = ctx.add_constant(Constant::String(key));
ctx.inst(Inst::load_const(Operand::Reg(key_reg), idx));
let callee_reg = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::LOAD_VAR,
Operand::Reg(callee_reg),
Operand::Reg(obj_reg),
Operand::None,
));
ctx.inst(Inst::ic_get(Operand::Reg(callee_reg), Operand::Reg(key_reg)));
(callee_reg, obj_reg)
} else {
let key_reg = self.emit_expression(&member.expression, ctx)?;
let callee_reg = ctx.alloc_reg();
ctx.inst(Inst::new(
OpCode::GET_PROP_DYNAMIC,
Operand::Reg(obj_reg),
Operand::Reg(key_reg),
Operand::Reg(callee_reg),
));
(callee_reg, obj_reg)
}
}
Expression::PrivateFieldExpression(member) => {
let obj_reg = self.emit_expression(&member.object, ctx)?;
let name = member.field.name.as_str();
let (brand_reg, brand_id) = self.private_access_brand(obj_reg, name, ctx)?;
let key_reg = self.emit_private_id_reg(name, ctx)?;
let callee_reg = ctx.alloc_reg();
ctx.inst(Inst::get_private(
Operand::Reg(callee_reg),
Operand::Reg(obj_reg),
Operand::Reg(key_reg),
brand_reg,
brand_id,
));
(callee_reg, obj_reg)
}
_ => {
let tag_reg = self.emit_expression(tag, ctx)?;
(tag_reg, undefined_reg)
}
};
Ok(pair)
}
}