feat: 调用实参 spread(f(...args) / new F(...args) / super(...args))

This commit is contained in:
爱特每羊队
2026-08-08 10:33:16 +08:00
parent ea86a2c0ac
commit 2777b00c82
14 changed files with 811 additions and 140 deletions
+3
View File
@@ -140,6 +140,9 @@ define_opcodes! {
CREATE_CLOSURE = 0x4A => "CREATE_CLOSURE",
CREATE_REGEXP = 0x4B => "CREATE_REGEXP",
STORE_UPVALUE = 0x4C => "STORE_UPVALUE",
CALL_SPREAD = 0x4D => "CALL_SPREAD",
NEW_EXPRESSION_SPREAD = 0x4E => "NEW_EXPRESSION_SPREAD",
SUPER_CALL_SPREAD = 0x4F => "SUPER_CALL_SPREAD",
// ── Object Property (0x50-0x5F) ──
IC_GET_PROP = 0x50 => "IC_GET_PROP",
@@ -528,6 +528,26 @@ fn compile_template_literal_emits_template_str() {
);
}
#[test]
fn compile_call_spread_emits_spread_opcodes() {
// 普通调用 / new 表达式含 spread 实参 → 发 spread 变体 opcode
let module = compile_source("function f(){}; f(...[1,2]); new f(...[3]);");
assert!(
module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::CALL_SPREAD),
"f(...args) should emit CALL_SPREAD"
);
assert!(
module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NEW_EXPRESSION_SPREAD),
"new f(...args) should emit NEW_EXPRESSION_SPREAD"
);
// 无 spread 的调用不发 spread 变体
let plain = compile_source("function f(a){}; f(1);");
assert!(
!plain.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::CALL_SPREAD),
"plain call should not emit CALL_SPREAD"
);
}
#[test]
fn compile_dynamic_member_ops() {
let module = compile_source("let obj = {}, k = 'x', arr = [1]; obj[k]; obj[k] = 1; arr[0];");
+29 -20
View File
@@ -305,17 +305,13 @@ impl Emitter {
Expression::CallExpression(ce) => {
self.collect_capture_names_expr(&ce.callee, ref_set, shadow, out);
for a in &ce.arguments {
if let Some(e) = a.as_expression() {
self.collect_capture_names_expr(e, ref_set, shadow, out);
}
self.collect_capture_names_arg(a, ref_set, shadow, out);
}
}
Expression::NewExpression(ne) => {
self.collect_capture_names_expr(&ne.callee, ref_set, shadow, out);
for a in &ne.arguments {
if let Some(e) = a.as_expression() {
self.collect_capture_names_expr(e, ref_set, shadow, out);
}
self.collect_capture_names_arg(a, ref_set, shadow, out);
}
}
Expression::SequenceExpression(se) => {
@@ -389,15 +385,25 @@ impl Emitter {
oxide_parser::ChainElement::CallExpression(call) => {
self.collect_capture_names_expr(&call.callee, ref_set, shadow, out);
for a in &call.arguments {
if let Some(e) = a.as_expression() {
self.collect_capture_names_expr(e, ref_set, shadow, out);
}
self.collect_capture_names_arg(a, ref_set, shadow, out);
}
}
_ => {}
}
}
/// 遍历调用实参:静态实参与 spread 内部表达式都纳入捕获扫描。
fn collect_capture_names_arg(
&self, arg: &oxide_parser::Argument, ref_set: &HashSet<String>, shadow: &HashSet<String>,
out: &mut HashSet<String>,
) {
if let Some(e) = arg.as_expression() {
self.collect_capture_names_expr(e, ref_set, shadow, out);
} else if let oxide_parser::Argument::SpreadElement(sp) = arg {
self.collect_capture_names_expr(&sp.argument, ref_set, shadow, out);
}
}
/// 分析本函数:哪些绑定被任意深度嵌套函数捕获 → captured_bindings。
pub(crate) fn collect_captured_bindings(
&self, stmts: &[Statement], extra_exprs: &[&oxide_parser::Expression], own: &HashSet<String>,
@@ -572,9 +578,7 @@ impl Emitter {
Expression::CallExpression(ce) => {
self.collect_captured_expr(&ce.callee, own, out);
for a in &ce.arguments {
if let Some(e) = a.as_expression() {
self.collect_captured_expr(e, own, out);
}
self.collect_captured_arg(a, own, out);
}
}
Expression::BinaryExpression(be) => {
@@ -614,9 +618,7 @@ impl Emitter {
Expression::NewExpression(ne) => {
self.collect_captured_expr(&ne.callee, own, out);
for a in &ne.arguments {
if let Some(e) = a.as_expression() {
self.collect_captured_expr(e, own, out);
}
self.collect_captured_arg(a, own, out);
}
}
Expression::ComputedMemberExpression(m) => {
@@ -659,22 +661,29 @@ impl Emitter {
oxide_parser::ChainElement::CallExpression(c) => {
self.collect_captured_expr(&c.callee, own, out);
for a in &c.arguments {
if let Some(e) = a.as_expression() {
self.collect_captured_expr(e, own, out);
}
self.collect_captured_arg(a, own, out);
}
}
_ => {}
}
}
/// 遍历调用实参:静态实参与 spread 内部表达式都纳入捕获判定。
fn collect_captured_arg(&self, arg: &oxide_parser::Argument, own: &HashSet<String>, out: &mut HashSet<String>) {
if let Some(e) = arg.as_expression() {
self.collect_captured_expr(e, own, out);
} else if let oxide_parser::Argument::SpreadElement(sp) = arg {
self.collect_captured_expr(&sp.argument, own, out);
}
}
/// 分析子函数 body:引用父级被捕获绑定的名字 → upvalue_captures。
/// 名字若在父 `captured_bindings`(父 own cell)则 `cell_idx` 索引定义方表;
/// 若在父 `upvalue_captures`(父自身从更外层捕获)则链式标记 `parent_uv_idx`,
/// 运行时从父闭包 upvalues 取 cell。enclosing_reg 由父 emit 完成后填充。
pub(crate) fn collect_upvalue_names(
&self, body_stmts: &[Statement], extra_exprs: &[&oxide_parser::Expression], parent_captured: &BTreeMap<String, u8>,
parent_upvalues: &[UpvalueCapture], sub_own: &HashSet<String>,
&self, body_stmts: &[Statement], extra_exprs: &[&oxide_parser::Expression],
parent_captured: &BTreeMap<String, u8>, parent_upvalues: &[UpvalueCapture], sub_own: &HashSet<String>,
) -> Vec<UpvalueCapture> {
let mut parent_names: HashSet<String> = parent_captured.keys().cloned().collect();
for u in parent_upvalues {
+60 -41
View File
@@ -14,19 +14,19 @@ impl Emitter {
if !ctx.in_derived_constructor {
return Err("super() only supported in derived constructors".into());
}
let mut arg_regs = Vec::new();
for arg in &call.arguments {
if let Some(expr) = arg.as_expression() {
arg_regs.push(self.emit_expression(expr, ctx)?);
}
}
let first_arg_reg = if arg_regs.is_empty() { 0u32 } else { pack_arg_regs(&mut arg_regs, ctx) };
let words = self.emit_call_args(&call.arguments, ctx)?;
let result_reg = ctx.alloc_reg();
ctx.inst(Inst::super_call(
Operand::Reg(result_reg),
Operand::Reg(first_arg_reg),
arg_regs.len() as u8,
));
if words.iter().any(|w| w >> 31 == 1) {
ctx.inst(Inst::super_call_spread(Operand::Reg(result_reg), &words));
} else {
let mut static_regs = words;
let first_arg_reg = if static_regs.is_empty() { 0u32 } else { pack_arg_regs(&mut static_regs, ctx) };
ctx.inst(Inst::super_call(
Operand::Reg(result_reg),
Operand::Reg(first_arg_reg),
static_regs.len() as u8,
));
}
if let Some(mut field_buffer) = ctx.field_buffer.take() {
let insert_inst = ctx.insts.len();
ctx.insts.append(&mut field_buffer.insts);
@@ -92,36 +92,37 @@ impl Emitter {
(callee_reg, this_reg)
}
};
let mut arg_regs = Vec::new();
for arg in &call.arguments {
if let Some(expr) = arg.as_expression() {
arg_regs.push(self.emit_expression(expr, ctx)?);
let words = self.emit_call_args(&call.arguments, ctx)?;
if words.iter().any(|w| w >> 31 == 1) {
// 任一实参是 spread → 运行期物化完整实参(有序字逐个读寄存器 / 迭代展开)。
ctx.inst(Inst::call_spread(Operand::Reg(callee_reg), Operand::Reg(this_reg), &words));
} else {
let mut static_regs = words;
let first_arg_reg = if static_regs.is_empty() { 0u32 } else { pack_arg_regs(&mut static_regs, ctx) };
let op = match &call.callee {
Expression::Identifier(ident) if ctx.is_builtin(ident.name.as_str()) => OpCode::CALL_NATIVE,
_ => OpCode::CALL,
};
match op {
OpCode::CALL => {
ctx.inst(Inst::call(
Operand::Reg(callee_reg),
Operand::Reg(this_reg),
Operand::Reg(first_arg_reg),
static_regs.len() as u8,
));
}
OpCode::CALL_NATIVE => {
ctx.inst(Inst::call_native(
Operand::Reg(callee_reg),
Operand::Reg(this_reg),
Operand::Reg(first_arg_reg),
static_regs.len() as u8,
));
}
_ => unreachable!(),
}
}
let first_arg_reg = if arg_regs.is_empty() { 0u32 } else { pack_arg_regs(&mut arg_regs, ctx) };
let op = match &call.callee {
Expression::Identifier(ident) if ctx.is_builtin(ident.name.as_str()) => OpCode::CALL_NATIVE,
_ => OpCode::CALL,
};
match op {
OpCode::CALL => {
ctx.inst(Inst::call(
Operand::Reg(callee_reg),
Operand::Reg(this_reg),
Operand::Reg(first_arg_reg),
arg_regs.len() as u8,
));
}
OpCode::CALL_NATIVE => {
ctx.inst(Inst::call_native(
Operand::Reg(callee_reg),
Operand::Reg(this_reg),
Operand::Reg(first_arg_reg),
arg_regs.len() as u8,
));
}
_ => unreachable!(),
}
let result_reg = ctx.alloc_reg();
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::None, Operand::None));
Ok(result_reg)
@@ -139,7 +140,7 @@ impl Emitter {
/// 各参数由独立 vreg 承载,复杂表达式(对象/数组字面量、嵌套调用)的临时寄存器
/// 会使参数 vreg 不连续——检测到不连续时用 MOV 打包到新连续块。
/// 返回首参寄存器;参数为空时返回 0。
fn pack_arg_regs(arg_regs: &mut [u32], ctx: &mut CompileCtx) -> u32 {
pub(crate) fn pack_arg_regs(arg_regs: &mut [u32], ctx: &mut CompileCtx) -> u32 {
let consecutive = arg_regs.windows(2).all(|w| w[1] == w[0] + 1);
if consecutive {
return arg_regs[0];
@@ -154,3 +155,21 @@ fn pack_arg_regs(arg_regs: &mut [u32], ctx: &mut CompileCtx) -> u32 {
}
base
}
impl Emitter {
/// 收集调用实参为有序实参字(保持源码求值序):静态实参 = 寄存器号,
/// spread 源 = `0x8000_0000 | 寄存器号`(高位标记区分)。
pub(crate) fn emit_call_args(
&self, args: &[oxide_parser::Argument], ctx: &mut CompileCtx,
) -> Result<Vec<u32>, String> {
let mut words = Vec::new();
for arg in args {
if let Some(expr) = arg.as_expression() {
words.push(self.emit_expression(expr, ctx)?);
} else if let oxide_parser::Argument::SpreadElement(sp) = arg {
words.push(0x8000_0000 | self.emit_expression(&sp.argument, ctx)?);
}
}
Ok(words)
}
}
+14 -14
View File
@@ -2,6 +2,7 @@
//! 函数:`emit_arrow_function_expression`、`emit_function_expression`、
//! `emit_class_expression`、`emit_new_expression`、`emit_function_domain`。
use crate::expr::call::pack_arg_regs;
use crate::{CompileCtx, Emitter, ParamSpec};
use oxide_ir::inst::Inst;
use oxide_ir::operand::Operand;
@@ -94,20 +95,20 @@ impl Emitter {
fn emit_new_expression(&self, ne: &oxide_parser::NewExpression, ctx: &mut CompileCtx) -> Result<u32, String> {
let constructor_reg = self.emit_expression(&ne.callee, ctx)?;
let mut arg_regs = Vec::new();
for arg in &ne.arguments {
if let Some(expr) = arg.as_expression() {
arg_regs.push(self.emit_expression(expr, ctx)?);
}
}
let first_arg_reg = if arg_regs.is_empty() { 0u32 } else { arg_regs[0] };
let words = self.emit_call_args(&ne.arguments, ctx)?;
let r = ctx.alloc_reg();
ctx.inst(Inst::new_expression(
Operand::Reg(r),
Operand::Reg(constructor_reg),
Operand::Reg(first_arg_reg),
arg_regs.len() as u8,
));
if words.iter().any(|w| w >> 31 == 1) {
ctx.inst(Inst::new_expression_spread(Operand::Reg(r), Operand::Reg(constructor_reg), &words));
} else {
let mut static_regs = words;
let first_arg_reg = if static_regs.is_empty() { 0u32 } else { pack_arg_regs(&mut static_regs, ctx) };
ctx.inst(Inst::new_expression(
Operand::Reg(r),
Operand::Reg(constructor_reg),
Operand::Reg(first_arg_reg),
static_regs.len() as u8,
));
}
Ok(r)
}
@@ -121,4 +122,3 @@ impl Emitter {
}
}
}
+12 -9
View File
@@ -135,17 +135,13 @@ impl Emitter {
Expression::CallExpression(call) => {
self.pre_scan_builtin_expr(&call.callee, ctx);
for arg in &call.arguments {
if let Some(e) = arg.as_expression() {
self.pre_scan_builtin_expr(e, ctx);
}
self.pre_scan_builtin_arg(arg, ctx);
}
}
Expression::NewExpression(ne) => {
self.pre_scan_builtin_expr(&ne.callee, ctx);
for arg in &ne.arguments {
if let Some(e) = arg.as_expression() {
self.pre_scan_builtin_expr(e, ctx);
}
self.pre_scan_builtin_arg(arg, ctx);
}
}
Expression::LogicalExpression(log) => {
@@ -254,15 +250,22 @@ impl Emitter {
oxide_parser::ChainElement::CallExpression(call) => {
self.pre_scan_builtin_expr(&call.callee, ctx);
for arg in &call.arguments {
if let Some(e) = arg.as_expression() {
self.pre_scan_builtin_expr(e, ctx);
}
self.pre_scan_builtin_arg(arg, ctx);
}
}
_ => {}
}
}
/// 遍历调用实参:spread 内部表达式也纳入 builtin 预扫描。
fn pre_scan_builtin_arg(&self, arg: &oxide_parser::Argument, ctx: &mut CompileCtx) {
if let Some(e) = arg.as_expression() {
self.pre_scan_builtin_expr(e, ctx);
} else if let oxide_parser::Argument::SpreadElement(sp) = arg {
self.pre_scan_builtin_expr(&sp.argument, ctx);
}
}
pub(crate) fn predeclare_function_declarations(&self, statements: &[Statement], ctx: &mut CompileCtx) {
for statement in statements {
let Statement::FunctionDeclaration(function) = statement else {
+29 -28
View File
@@ -1,6 +1,7 @@
//! 可选链(`?.`)emit:短路守卫、链式成员访问/调用与逻辑赋值短路测试。
//! 函数:`emit_chainable_expression`、`emit_optional_guard`、`emit_chain_call` 等。
use crate::expr::call::pack_arg_regs;
use crate::{CompileCtx, Emitter};
use oxide_bytecode::module::Constant;
use oxide_bytecode::opcode::OpCode;
@@ -106,36 +107,36 @@ impl Emitter {
self.emit_optional_guard(callee_reg, label, ctx)?;
}
}
let mut arg_regs = Vec::new();
for arg in &call.arguments {
if let Some(expr) = arg.as_expression() {
arg_regs.push(self.emit_expression(expr, ctx)?);
let words = self.emit_call_args(&call.arguments, ctx)?;
if words.iter().any(|w| w >> 31 == 1) {
ctx.inst(Inst::call_spread(Operand::Reg(callee_reg), Operand::Reg(this_reg), &words));
} else {
let mut static_regs = words;
let first_arg_reg = if static_regs.is_empty() { 0u32 } else { pack_arg_regs(&mut static_regs, ctx) };
let op = match &call.callee {
Expression::Identifier(ident) if ctx.is_builtin(ident.name.as_str()) => OpCode::CALL_NATIVE,
_ => OpCode::CALL,
};
match op {
OpCode::CALL => {
ctx.inst(Inst::call(
Operand::Reg(callee_reg),
Operand::Reg(this_reg),
Operand::Reg(first_arg_reg),
static_regs.len() as u8,
));
}
OpCode::CALL_NATIVE => {
ctx.inst(Inst::call_native(
Operand::Reg(callee_reg),
Operand::Reg(this_reg),
Operand::Reg(first_arg_reg),
static_regs.len() as u8,
));
}
_ => unreachable!(),
}
}
let first_arg_reg = if arg_regs.is_empty() { 0u32 } else { arg_regs[0] };
let op = match &call.callee {
Expression::Identifier(ident) if ctx.is_builtin(ident.name.as_str()) => OpCode::CALL_NATIVE,
_ => OpCode::CALL,
};
match op {
OpCode::CALL => {
ctx.inst(Inst::call(
Operand::Reg(callee_reg),
Operand::Reg(this_reg),
Operand::Reg(first_arg_reg),
arg_regs.len() as u8,
));
}
OpCode::CALL_NATIVE => {
ctx.inst(Inst::call_native(
Operand::Reg(callee_reg),
Operand::Reg(this_reg),
Operand::Reg(first_arg_reg),
arg_regs.len() as u8,
));
}
_ => unreachable!(),
}
let result_reg = ctx.alloc_reg();
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(result_reg), Operand::None, Operand::None));
Ok(result_reg)
+41 -1
View File
@@ -18,7 +18,7 @@ impl Inst {
pub fn def_reg(&self) -> Option<u32> {
match self.op {
// CALL 系:结果隐式写 reg 0,rd 是 callee 的 use
OpCode::CALL | OpCode::CALL_NATIVE => Some(0),
OpCode::CALL | OpCode::CALL_NATIVE | OpCode::CALL_SPREAD => Some(0),
// GET_PROP 系:结果写 a/b 槽而非 rd
OpCode::GET_PROP | OpCode::IC_GET_PROP => reg_of(&self.a),
OpCode::GET_PROP_DYNAMIC => reg_of(&self.b),
@@ -91,6 +91,25 @@ impl Inst {
let nargs = self.ext.first().copied().unwrap_or(0);
push_range(&mut uses, reg_of(&self.a), nargs);
}
// spread 调用系:ext 内嵌有序实参字(静态寄存器号 / `0x8000_0000|reg` spread 源)
OpCode::CALL_SPREAD => {
push_operand(&mut uses, &self.rd);
push_operand(&mut uses, &self.a);
for &w in self.ext.iter().skip(1) {
uses.push(w & 0x7FFF_FFFF);
}
}
OpCode::NEW_EXPRESSION_SPREAD => {
push_operand(&mut uses, &self.a);
for &w in self.ext.iter().skip(1) {
uses.push(w & 0x7FFF_FFFF);
}
}
OpCode::SUPER_CALL_SPREAD => {
for &w in self.ext.iter().skip(1) {
uses.push(w & 0x7FFF_FFFF);
}
}
// GET_PROP 系:结果写 a/b 槽,rd=obj 是 use
OpCode::GET_PROP => {
push_operand(&mut uses, &self.rd);
@@ -435,6 +454,24 @@ mod tests {
assert_eq!(inst.use_regs().as_slice(), &[0]);
}
#[test]
fn spread_call_def_and_uses() {
// CALL_SPREAD:结果隐式写 reg 0;use = callee + this + ext 有序实参字(静态/spread 同编)
let call = Inst::call_spread(Operand::Reg(0), Operand::Reg(1), &[5, 0x8000_0000 | 9, 300]);
assert_eq!(call.def_reg(), Some(0));
assert_eq!(call.use_regs().as_slice(), &[0, 1, 5, 9, 300]);
// NEW_EXPRESSION_SPREAD:rd 即 def;use = ctor + 实参字
let ne = Inst::new_expression_spread(Operand::Reg(3), Operand::Reg(0), &[7, 0x8000_0000 | 12]);
assert_eq!(ne.def_reg(), Some(3));
assert_eq!(ne.use_regs().as_slice(), &[0, 7, 12]);
// SUPER_CALL_SPREAD:无 callee/this 槽
let sc = Inst::super_call_spread(Operand::Reg(3), &[4, 0x8000_0000 | 8]);
assert_eq!(sc.def_reg(), Some(3));
assert_eq!(sc.use_regs().as_slice(), &[4, 8]);
}
#[test]
fn void_defs_rd_no_use() {
// VM 的 VOID handler 不读 a 槽
@@ -630,6 +667,9 @@ mod tests {
assert!(!Inst::call_native(Operand::Reg(0), Operand::Reg(1), Operand::Reg(2), 0).is_pure(&f));
assert!(!Inst::new_expression(Operand::Reg(3), Operand::Reg(0), Operand::Reg(1), 0).is_pure(&f));
assert!(!Inst::super_call(Operand::Reg(3), Operand::Reg(1), 0).is_pure(&f));
assert!(!Inst::call_spread(Operand::Reg(0), Operand::Reg(1), &[3]).is_pure(&f));
assert!(!Inst::new_expression_spread(Operand::Reg(3), Operand::Reg(0), &[2]).is_pure(&f));
assert!(!Inst::super_call_spread(Operand::Reg(3), &[2]).is_pure(&f));
assert!(!Inst::new(OpCode::CREATE_REGEXP, Operand::Reg(0), Operand::Reg(1), Operand::Reg(2)).is_pure(&f));
assert!(!Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None).is_pure(&f));
assert!(!Inst::new(OpCode::THROW, Operand::Reg(2), Operand::None, Operand::None).is_pure(&f));
+54
View File
@@ -116,6 +116,26 @@ impl Inst {
Self::with_ext(OpCode::SUPER_CALL, result, first_arg, Operand::None, &[nargs as u32])
}
/// spread 实参调用:rd=callee,a=this,b 槽未用。
/// ext=[nstatic|(nspread<<8), 有序实参字…];每个实参字:静态实参为寄存器号,
/// spread 源为 `0x8000_0000 | 寄存器号`(高位标记),按源码求值序排列。
pub fn call_spread(callee: Operand, this: Operand, words: &[u32]) -> Self {
let ext = spread_ext(words);
Self::with_ext(OpCode::CALL_SPREAD, callee, this, Operand::None, &ext)
}
/// spread 实参 `new`:结果写入 `result`,构造函数为 `constructor`。
pub fn new_expression_spread(result: Operand, constructor: Operand, words: &[u32]) -> Self {
let ext = spread_ext(words);
Self::with_ext(OpCode::NEW_EXPRESSION_SPREAD, result, constructor, Operand::None, &ext)
}
/// 派生类构造中的 `super(...spread)`:结果写入 `result`。
pub fn super_call_spread(result: Operand, words: &[u32]) -> Self {
let ext = spread_ext(words);
Self::with_ext(OpCode::SUPER_CALL_SPREAD, result, Operand::None, Operand::None, &ext)
}
// ── 其他带 ext 字 ──
/// 定义访问器属性:home 为宿主对象,get/set 为访问器函数寄存器,key_idx 为属性名常量下标。
@@ -217,6 +237,17 @@ impl Inst {
}
}
/// spread 调用系 ext 构造:首字打包 `nstatic | (nspread << 8)`,后续按源码求值序排列
/// 每个实参字(静态实参 = 寄存器号,spread 源 = `0x8000_0000 | 寄存器号`)。
fn spread_ext(words: &[u32]) -> SmallVec<[u32; 4]> {
let nspread = words.iter().filter(|w| *w >> 31 == 1).count() as u32;
let nstatic = (words.len() as u32) - nspread;
let mut ext = SmallVec::with_capacity(1 + words.len());
ext.push(nstatic | (nspread << 8));
ext.extend_from_slice(words);
ext
}
#[cfg(test)]
mod tests {
use super::*;
@@ -282,6 +313,29 @@ mod tests {
assert_eq!(rest.ext.as_slice(), &[7]);
}
#[test]
fn spread_call_constructors_carry_header_and_source_regs() {
let call = Inst::call_spread(Operand::Reg(0), Operand::Reg(1), &[10, 0x8000_0000 | 300]);
assert_eq!(call.op, OpCode::CALL_SPREAD);
assert_eq!(call.rd, Operand::Reg(0));
assert_eq!(call.a, Operand::Reg(1));
assert_eq!(call.b, Operand::None);
assert_eq!(call.ext.as_slice(), &[1 | (1 << 8), 10, 0x8000_0000 | 300]);
let ne = Inst::new_expression_spread(Operand::Reg(3), Operand::Reg(0), &[0x8000_0000 | 7]);
assert_eq!(ne.op, OpCode::NEW_EXPRESSION_SPREAD);
assert_eq!(ne.rd, Operand::Reg(3));
assert_eq!(ne.a, Operand::Reg(0));
assert_eq!(ne.ext.as_slice(), &[1 << 8, 0x8000_0000 | 7]);
let sc = Inst::super_call_spread(Operand::Reg(3), &[1, 0x8000_0000 | 5]);
assert_eq!(sc.op, OpCode::SUPER_CALL_SPREAD);
assert_eq!(sc.rd, Operand::Reg(3));
assert_eq!(sc.a, Operand::None);
assert_eq!(sc.b, Operand::None);
assert_eq!(sc.ext.as_slice(), &[1 | (1 << 8), 1, 0x8000_0000 | 5]);
}
#[test]
fn regalloc_constructors_carry_expected_slots() {
let mov = Inst::inst_mov(Operand::Reg(3), Operand::Reg(7));
+10
View File
@@ -70,6 +70,15 @@ pub(super) fn collect_real_vregs(f: &IRFunction) -> BTreeSet<u32> {
}
}
}
// spread 调用系:ext 首字之后每个有序实参字是寄存器号(spread 源带高位标记)
if matches!(inst.op, OpCode::CALL_SPREAD | OpCode::NEW_EXPRESSION_SPREAD | OpCode::SUPER_CALL_SPREAD) {
for &w in inst.ext.iter().skip(1) {
let r = w & 0x7FFF_FFFF;
if r != 0 {
real.insert(r);
}
}
}
}
real
}
@@ -116,6 +125,7 @@ pub(super) fn build(
}
// ── 可分配色集 ──
// 窗口只为参数连续性 MOV 桥预留;spread 调用实参经 ext 逐个读寄存器,无连续性要求。
let max_nargs = f
.insts
.iter()
+41 -19
View File
@@ -134,7 +134,8 @@ pub(super) fn run(f: &mut IRFunction, map: &AllocMap) {
fn rewrite_inst(
f: &IRFunction, inst: &Inst, map: &AllocMap, slot_color: &BTreeMap<u32, u32>, i: usize,
) -> (Inst, Vec<(u32, u32)>) {
let nargs = inst.ext.first().copied().unwrap_or(0);
let raw_nargs = inst.ext.first().copied().unwrap_or(0);
let nargs = raw_nargs;
let first_arg = match inst.op {
OpCode::CALL | OpCode::CALL_NATIVE | OpCode::NEW_EXPRESSION => Some(inst.b),
OpCode::SUPER_CALL => Some(inst.a),
@@ -197,26 +198,34 @@ fn rewrite_inst(
let mut a = rewrite(inst.a);
let mut b = rewrite(inst.b);
// TEMPLATE_STR 的 ext 编码表达式寄存器(seg>>31==1 时低 8 位为 expr_reg)——必须随
// RegAlloc 重映射,否则读旧 vreg 号对应的物理槽(错值)。
let ext = if inst.op == OpCode::TEMPLATE_STR {
let mut ext = inst.ext.clone();
for seg in ext.iter_mut().skip(1) {
if *seg >> 31 == 1 {
let r = *seg & 0xFF;
let nr = if let Some(&c) = slot_color.get(&r) {
c
} else {
match map.map.get(&r) {
Some(Alloc::Phys(p)) => *p,
_ => r, // 非真实 vreg / spilled 未覆盖(防御保留原号)
}
};
*seg = (*seg & !0xFFu32) | (nr & 0xFF);
// RegAlloc 重映射,否则读旧 vreg 号对应的物理槽(错值)。spread 调用系 ext[1..] 每个
// 字是完整 spread 源 vreg,同样需重映射到物理号。
let ext = match inst.op {
OpCode::TEMPLATE_STR => {
let mut ext = inst.ext.clone();
for seg in ext.iter_mut().skip(1) {
if *seg >> 31 == 1 {
let r = *seg & 0xFF;
let nr = remap_ext_reg(r, slot_color, map);
*seg = (*seg & !0xFFu32) | (nr & 0xFF);
}
}
ext
}
ext
} else {
inst.ext.clone()
OpCode::CALL_SPREAD | OpCode::NEW_EXPRESSION_SPREAD | OpCode::SUPER_CALL_SPREAD => {
// 有序实参字:静态字直接重映射,spread 字保留高位标记、低 31 位重映射。
let mut ext = inst.ext.clone();
for seg in ext.iter_mut().skip(1) {
if *seg >> 31 == 1 {
let nr = remap_ext_reg(*seg & 0x7FFF_FFFF, slot_color, map);
*seg = 0x8000_0000 | nr;
} else {
*seg = remap_ext_reg(*seg, slot_color, map);
}
}
ext
}
_ => inst.ext.clone(),
};
// 调用点首参槽改指 arg_window_base(桥接后)
if let Some(ab) = arg_base {
@@ -257,6 +266,19 @@ fn rewrite_inst(
(Inst { op: inst.op, rd, a, b, ext }, arg_movs)
}
/// ext 内嵌寄存器重映射:优先用本指令点的 fresh 色(spilled vreg 的 use 点 UNSPILL),
/// 其次 AllocMap 物理色;未映射(防御)保留原号。
fn remap_ext_reg(r: u32, slot_color: &BTreeMap<u32, u32>, map: &AllocMap) -> u32 {
if let Some(&c) = slot_color.get(&r) {
c
} else {
match map.map.get(&r) {
Some(Alloc::Phys(p)) => *p,
_ => r,
}
}
}
/// terminator 判定:跳转族 + RETURN/HALT/THROW(def 结果必死,SPILL 跳过)。
fn is_terminator(op: OpCode) -> bool {
matches!(
+349 -8
View File
@@ -1,9 +1,10 @@
use crate::native::NativeFn;
use crate::vm::{native_fn_ptr_to_fn, CallFrame, FrameContinuation, Vm};
use crate::{vm_debug, vm_trace};
use oxide_builtins::iterator::make_iterator_for_value;
use oxide_builtins::{builtins_debug, builtins_trace};
use oxide_bytecode::opcode;
use oxide_runtime_api::NativeResult;
use oxide_runtime_api::{to_boolean, NativeResult};
use oxide_types::object::{Cell, JsObject};
use oxide_types::value::JsValue;
use std::sync::Arc;
@@ -130,14 +131,15 @@ impl Vm {
// 函数名推断:emit 端在变量声明/对象属性赋值点设置 function_name。
let func_obj = unsafe { &mut *result.as_js_object_ptr() };
let name_si = self.kernel_core.perm_interner().intern("name").0;
let name_val = function_name.as_deref().map(|n| self.new_string(n)).unwrap_or_else(|| self.new_string(""));
let name_val = function_name
.as_deref()
.map(|n| self.new_string(n))
.unwrap_or_else(|| self.new_string(""));
self.set_or_create_prop_value(func_obj, name_si, name_val);
if !upvalue_captures.is_empty() {
// 链式捕获(parent_uv_idx):从父闭包(创建者)的 upvalues 取 cell。
let parent_upvalues: Vec<*mut Cell> = match self.current_callee() {
Some(callee) if callee.is_object() => {
unsafe { &*callee.as_js_object_ptr() }.upvalues_slice().to_vec()
}
Some(callee) if callee.is_object() => unsafe { &*callee.as_js_object_ptr() }.upvalues_slice().to_vec(),
_ => Vec::new(),
};
if let Some(current_cells) = self.cell_stack.last_mut() {
@@ -156,8 +158,7 @@ impl Vm {
if current_cells[cell_idx].is_null() {
// 占位 Cell 保持未初始化(TDZ)直到 MAKE_CELL 置位;
// 若绑定为 var 则其初始化 MAKE_CELL 在函数序言先于任何读取执行。
let cell =
self.gc_state.session_epoch.alloc(Cell::new(JsValue::undefined(), false));
let cell = self.gc_state.session_epoch.alloc(Cell::new(JsValue::undefined(), false));
current_cells[cell_idx] = cell as *mut Cell;
}
current_cells[cell_idx]
@@ -254,7 +255,8 @@ impl Vm {
if !cell.is_null() {
let c = unsafe { &*cell };
if !c.is_initialized() {
return self.raise_error_kind("ReferenceError", "Cannot access variable before initialization");
return self
.raise_error_kind("ReferenceError", "Cannot access variable before initialization");
}
self.regs[rd] = c.value;
return Ok(());
@@ -590,3 +592,342 @@ impl Vm {
Ok(false)
}
}
impl Vm {
/// 解析 spread 调用系 ext:读走 `nstatic | (nspread<<8)` 首字与 `nstatic+nspread`
/// 个有序实参字(静态字 = 寄存器号,spread 字高位标记)。
fn read_spread_ext(&mut self) -> Vec<usize> {
let header = self.bytecode[self.pc];
self.pc += 1;
let nstatic = (header & 0xFF) as usize;
let nspread = (header >> 8) as usize;
let mut words = Vec::with_capacity(nstatic + nspread);
for _ in 0..nstatic + nspread {
words.push(self.bytecode[self.pc] as usize);
self.pc += 1;
}
words
}
/// 按源码求值序物化完整实参 Vec:静态字直接读寄存器,spread 字迭代展开。
///
/// # 步骤
/// 1. 静态实参逐个从寄存器读入;spread 源经 `make_iterator_for_value` 迭代追加。
/// 2. 迭代中途抛错 → 当前迭代器经 return() 关闭(IteratorClose),异常恢复为 JS
/// 异常后 unwind。
/// 3. 展开总数超过 u16 上限(arguments_count 字段)→ RangeError。
///
/// # 返回值
/// - `Ok(Some(vec))` 成功;
/// - `Ok(None)` 异常已展开(调用方按 continue 处理);
/// - `Err` 引擎错误直接传播。
fn materialize_spread_args(&mut self, words: &[usize]) -> Result<Option<Vec<JsValue>>, String> {
let mut args = Vec::with_capacity(words.len());
let next_si = self.kernel_core.perm_interner().intern("next").0;
let done_si = self.kernel_core.perm_interner().intern("done").0;
let value_si = self.kernel_core.perm_interner().intern("value").0;
for &w in words {
if w >> 31 == 1 {
let value = self.regs[w & 0x7FFF_FFFF];
let iterator = match make_iterator_for_value(self, value) {
Ok(it) => it,
Err(err) => {
self.exception_value = Some(err);
self.pending_error_kind = Some(self.thrown_error_kind(err));
return self.unwind().map(|_| None);
}
};
loop {
let iter_obj = unsafe { &*iterator.as_js_object_ptr() };
let next_fn = match self.ordinary_get(iter_obj, next_si, iterator) {
Ok(v) => v,
Err(e) => return self.spread_iter_error(iterator, e, "Error"),
};
let result = match self.call_function_sync(next_fn, iterator, &[]) {
Ok(v) => v,
Err(e) => return self.spread_iter_error(iterator, e, "Error"),
};
if !result.is_object() {
return self.spread_iter_error(
iterator,
self.error_message_text("TypeError", "iterator result is not an object"),
"TypeError",
);
}
let result_obj = unsafe { &*result.as_js_object_ptr() };
let done = match self.ordinary_get(result_obj, done_si, result) {
Ok(v) => to_boolean(v),
Err(e) => return self.spread_iter_error(iterator, e, "Error"),
};
if done {
break;
}
let val = match self.ordinary_get(result_obj, value_si, result) {
Ok(v) => v,
Err(e) => return self.spread_iter_error(iterator, e, "Error"),
};
args.push(val);
if args.len() > u16::MAX as usize {
self.raise_error_kind("RangeError", "Too many arguments in function call")?;
return Ok(None);
}
}
} else {
args.push(self.regs[w]);
if args.len() > u16::MAX as usize {
self.raise_error_kind("RangeError", "Too many arguments in function call")?;
return Ok(None);
}
}
}
Ok(Some(args))
}
/// spread 迭代中途异常:先关闭当前迭代器(suppress return 自身错误,保留在途异常),
/// 再把原异常(native 错误经 last_uncaught_value 恢复)重新抛出并展开。
fn spread_iter_error(
&mut self, iterator: JsValue, msg: String, fallback_kind: &'static str,
) -> Result<Option<Vec<JsValue>>, String> {
let exc = self
.last_uncaught_value
.take()
.unwrap_or_else(|| oxide_builtins::error::create_kind_error(self, fallback_kind, &msg));
self.close_for_of_iterator(iterator, true)?;
self.exception_value = Some(exc);
self.pending_error_kind = Some(self.thrown_error_kind(exc));
self.unwind().map(|_| None)
}
/// CALL_SPREAD:运行期物化完整实参后按目标分派(native 走值传递,字节码走帧)。
pub(crate) fn dispatch_call_spread(&mut self, rd: usize, a: usize, _b: usize) -> Result<bool, String> {
let callee = self.regs[rd];
let this_value = self.regs[a];
let words = self.read_spread_ext();
let args = match self.materialize_spread_args(&words)? {
Some(args) => args,
None => return Ok(true),
};
if callee.is_object() {
let obj_ptr = callee.as_js_object_ptr();
if !obj_ptr.is_null() {
let obj = unsafe { &*obj_ptr };
if obj.is_function() {
if obj.is_class_constructor() {
return self
.raise_type_error("class constructor cannot be invoked without 'new'")
.map(|_| true);
}
if obj.native_fn().is_some() {
match self.call_function_sync(callee, this_value, &args) {
Ok(v) => {
self.regs[0] = v;
return Ok(false);
}
Err(_) => {
let exc = self
.last_uncaught_value
.take()
.unwrap_or_else(|| oxide_builtins::error::create_error(self, "call failed"));
self.exception_value = Some(exc);
self.pending_error_kind = Some(self.thrown_error_kind(exc));
return self.unwind().map(|_| true);
}
}
} else if obj.sub_module_index() > 0 {
self.push_bytecode_frame(
callee,
this_value,
&args,
None,
None,
JsValue::undefined(),
FrameContinuation::None,
)?;
return Ok(true);
}
}
}
}
self.raise_type_error("CALL target is not callable").map(|_| true)
}
/// NEW_EXPRESSION_SPREAD:物化实参后按构造函数目标分派(native 走值传递,字节码走帧)。
pub(crate) fn dispatch_new_expression_spread(&mut self, rd: usize, a: usize, _b: usize) -> Result<bool, String> {
let constructor_reg = a;
let constructor = self.regs[constructor_reg];
if !constructor.is_object() {
return self
.raise_type_error("NEW_EXPRESSION: constructor is not an object")
.map(|_| true);
}
let ctor_ptr = constructor.as_js_object_ptr();
if ctor_ptr.is_null() {
return self.raise_type_error("NEW_EXPRESSION: constructor is null").map(|_| true);
}
let ctor_obj = unsafe { &*ctor_ptr };
if !ctor_obj.is_function() {
return self
.raise_type_error("NEW_EXPRESSION: constructor is not a function")
.map(|_| true);
}
if ctor_obj.is_arrow() {
return self
.raise_type_error("arrow functions cannot be used as constructors")
.map(|_| true);
}
// native 方法(非构造器)不可 new。
if ctor_obj.native_fn().is_some() && ctor_obj.type_tag != oxide_types::object::JsObject::OBJ_TYPE_CONSTRUCTOR {
return self.raise_type_error("object is not a constructor").map(|_| true);
}
let words = self.read_spread_ext();
let args = match self.materialize_spread_args(&words)? {
Some(args) => args,
None => return Ok(true),
};
let proto_ptr = &*self.object_prototype as *const JsObject as *mut JsObject;
let new_obj = self.alloc_object(JsObject::new_empty(
oxide_kernel::shape_forge::EMPTY_SHAPE_ID,
JsValue::from_js_object(proto_ptr),
));
let proto_si = self.kernel_core.perm_interner().intern("prototype").0;
if let Some(proto_val) = self.resolve_property(ctor_obj, proto_si) {
if proto_val.is_object() {
let new_obj_mut = unsafe { &mut *new_obj };
let proto_obj_ptr = proto_val.as_js_object_ptr();
let _ = new_obj_mut.set_proto(JsValue::from_js_object(proto_obj_ptr));
}
}
let new_obj_val = JsValue::object(new_obj as *mut u8);
if ctor_obj.native_fn().is_some() {
// native 构造器:receiver 为新对象,值传递调用。
match self.call_function_sync(constructor, new_obj_val, &args) {
Ok(v) => {
self.regs[rd] = if v.is_object() { v } else { new_obj_val };
Ok(false)
}
Err(_) => {
let exc = self
.last_uncaught_value
.take()
.unwrap_or_else(|| oxide_builtins::error::create_error(self, "constructor call failed"));
self.exception_value = Some(exc);
self.pending_error_kind = Some(self.thrown_error_kind(exc));
self.unwind().map(|_| true)
}
}
} else if ctor_obj.sub_module_index() > 0 {
if self.frames.len() >= self.kernel_core.config.max_call_depth {
return Err(self.error_message_text("RangeError", "Maximum call stack size exceeded"));
}
let this_value = if ctor_obj.is_derived_constructor() {
JsValue::undefined()
} else {
new_obj_val
};
self.push_bytecode_frame(
constructor,
this_value,
&args,
Some(rd as u8),
Some(new_obj_val),
constructor,
FrameContinuation::None,
)?;
Ok(true)
} else {
let error =
oxide_builtins::error::create_error(self, "NEW_EXPRESSION: bytecode constructors not yet supported");
self.exception_value = Some(error);
self.pending_error_kind = Some(self.thrown_error_kind(error));
self.unwind().map(|_| true)
}
}
/// SUPER_CALL_SPREAD:物化实参后按父构造器目标分派(native 走值传递,字节码走帧)。
pub(crate) fn dispatch_super_call_spread(&mut self, rd: usize, _a: usize) -> Result<bool, String> {
let words = self.read_spread_ext();
let Some(frame) = self.frames.last() else {
self.raise_error_kind("ReferenceError", "super() used outside class constructor")?;
return Ok(true);
};
if !frame.is_derived_constructor {
self.raise_error_kind("ReferenceError", "super() used outside derived constructor")?;
return Ok(true);
}
if !self.regs[254].is_undefined() {
self.raise_error_kind("ReferenceError", "super() called more than once")?;
return Ok(true);
}
let Some(derived_this) = frame.constructed_this else {
self.raise_error_kind("ReferenceError", "super() without derived this")?;
return Ok(true);
};
let new_target = self.regs[255];
if !new_target.is_object() {
self.raise_error_kind("TypeError", "super() new.target is not an object")?;
return Ok(true);
}
let new_target_obj = unsafe { &*new_target.as_js_object_ptr() };
let super_ctor = new_target_obj.proto();
if !super_ctor.is_object() {
self.raise_error_kind("TypeError", "super constructor is not an object")?;
return Ok(true);
}
let super_obj = unsafe { &*super_ctor.as_js_object_ptr() };
if !super_obj.is_function() {
self.raise_error_kind("TypeError", "super constructor is not a function")?;
return Ok(true);
}
let args = match self.materialize_spread_args(&words)? {
Some(args) => args,
None => return Ok(true),
};
if super_obj.native_fn().is_some() {
match self.call_function_sync(super_ctor, derived_this, &args) {
Ok(val) => {
// super() 返回实例的 [[Prototype]] 须设为 new.target.prototype
let instance = if val.is_object() { val } else { derived_this };
if instance.is_object() {
self.set_constructed_proto(instance, new_target_obj)?;
}
self.regs[254] = instance;
self.regs[rd] = instance;
}
Err(_) => {
let exc = self
.last_uncaught_value
.take()
.unwrap_or_else(|| oxide_builtins::error::create_error(self, "super() call failed"));
self.exception_value = Some(exc);
self.pending_error_kind = Some(self.thrown_error_kind(exc));
self.unwind().map(|_| true)?;
}
}
Ok(false)
} else if super_obj.sub_module_index() > 0 {
if self.frames.len() >= self.kernel_core.config.max_call_depth {
return Err(self.error_message_text("RangeError", "Maximum call stack size exceeded"));
}
self.push_bytecode_frame(
super_ctor,
derived_this,
&args,
Some(254),
Some(derived_this),
new_target,
FrameContinuation::None,
)?;
Ok(true)
} else {
self.raise_error_kind("TypeError", "super constructor is not callable")?;
Ok(true)
}
}
}
+18
View File
@@ -1110,18 +1110,36 @@ impl Vm {
self.dispatch_call_native(rd, a, b)?;
}
OpCode::CALL_SPREAD => match self.dispatch_call_spread(rd, a, b) {
Ok(true) => continue,
Ok(false) => {}
Err(e) => return Err(e),
},
OpCode::NEW_EXPRESSION => match self.dispatch_new_expression(rd, a, b) {
Ok(true) => continue,
Ok(false) => {}
Err(e) => return Err(e),
},
OpCode::NEW_EXPRESSION_SPREAD => match self.dispatch_new_expression_spread(rd, a, b) {
Ok(true) => continue,
Ok(false) => {}
Err(e) => return Err(e),
},
OpCode::SUPER_CALL => match self.dispatch_super_call(rd, a) {
Ok(true) => continue,
Ok(false) => {}
Err(e) => return Err(e),
},
OpCode::SUPER_CALL_SPREAD => match self.dispatch_super_call_spread(rd, a) {
Ok(true) => continue,
Ok(false) => {}
Err(e) => return Err(e),
},
OpCode::SUPER_GET_PROP | OpCode::SUPER_STATIC_GET_PROP => {
match self.dispatch_super_get_prop(rd, a, b) {
Ok(true) => continue,
+131
View File
@@ -0,0 +1,131 @@
//! 调用实参 spread 执行语义测试:`f(...args)`、`new F(...args)`、`super(...args)`。
//! 覆盖静态/混合实参顺序、迭代器协议、IteratorClose、native 目标、求值顺序。
use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval(vm: &mut Vm, source: &str) -> Result<JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
vm.run(&module)
}
fn to_str(vm: &Vm, val: JsValue) -> String {
if val.is_string() {
vm.lookup_str(val).unwrap_or_default()
} else {
format!("{}", val)
}
}
fn to_num(val: JsValue) -> f64 {
if val.is_int() {
val.as_int() as f64
} else {
val.as_double()
}
}
#[test]
fn spread_single_literal_preserves_all_args() {
let mut vm = Vm::new();
let result = eval(&mut vm, "function f(){return arguments.length}; f(...[1,2,3])").unwrap();
assert_eq!(result.as_int(), 3);
}
#[test]
fn spread_interleaved_static_and_spread_keeps_source_order() {
let mut vm = Vm::new();
let result = eval(&mut vm, "function f(a,b,c){return [a,b,c].join('-')}; f('x', ...['y'], 'z')").unwrap();
assert_eq!(to_str(&vm, result), "x-y-z");
}
#[test]
fn spread_multiple_sources_append_in_order() {
let mut vm = Vm::new();
let result = eval(&mut vm, "function f(){return arguments.length}; f(...[1], 2, ...[3,4])").unwrap();
assert_eq!(result.as_int(), 4);
}
#[test]
fn spread_native_target() {
let mut vm = Vm::new();
let result = eval(&mut vm, "Math.max(...[1,5,3])").unwrap();
assert_eq!(to_num(result), 5.0);
}
#[test]
fn spread_new_expression_bytecode_ctor() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new (function(a,b){this.sum=a+b})(...[2,3]).sum").unwrap();
assert_eq!(to_num(result), 5.0);
}
#[test]
fn spread_new_expression_native_ctor() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new Date(...[2020,1,1]).getFullYear()").unwrap();
assert_eq!(to_num(result), 2020.0);
}
#[test]
fn spread_super_call_passes_args() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"class A { constructor(a,b,c){ this.v=[a,b,c].join('') } }; \
class B extends A { constructor(){ super(...[1,2,3]) } }; new B().v",
)
.unwrap();
assert_eq!(to_str(&vm, result), "123");
}
#[test]
fn spread_non_iterable_throws_type_error() {
let mut vm = Vm::new();
let result = eval(&mut vm, "try { (function(){})(...null); 'no-throw' } catch(e) { e.constructor.name }").unwrap();
assert_eq!(to_str(&vm, result), "TypeError");
}
#[test]
fn spread_invokes_iterator_close_on_abrupt_completion() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var closed=0; try { (function(){}) (...{[Symbol.iterator]:function(){ return { \
next:function(){ throw new Error('boom') }, \
return:function(){ closed=1; return {} } } }}) } catch(e) {}; closed",
)
.unwrap();
assert_eq!(result.as_int(), 1);
}
#[test]
fn spread_does_not_close_iterator_on_normal_completion() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var closed=0; var i=0; (function(){}) (...{[Symbol.iterator]:function(){ return { \
next:function(){ return ++i<=2 ? {done:false,value:i} : {done:true} }, \
return:function(){ closed=1; return {} } } }}); closed",
)
.unwrap();
assert_eq!(result.as_int(), 0);
}
#[test]
fn spread_closure_capture_survives() {
let mut vm = Vm::new();
let result =
eval(&mut vm, "function outer(){var x=42; function g(a){return a+x}; return g(...[x])} outer()").unwrap();
assert_eq!(to_num(result), 84.0);
}
#[test]
fn spread_string_source() {
let mut vm = Vm::new();
let result = eval(&mut vm, "function f(){return arguments.length}; f(...'ab')").unwrap();
assert_eq!(result.as_int(), 2);
}