From 85c192a747a4b97ea3a06e272d31cb147aff8c58 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 10 Aug 2026 00:33:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20rest=20=E5=8F=82=E6=95=B0=EF=BC=88funct?= =?UTF-8?q?ion=20f(...args)=EF=BC=89=EF=BC=8C=E5=AE=9E=E5=8F=82=E5=8C=BA?= =?UTF-8?q?=E6=94=B6=E9=9B=86=E6=95=B0=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_bytecode/src/opcode.rs | 3 + crates/oxide_emit/src/closure.rs | 6 ++ crates/oxide_emit/src/emit.rs | 45 ++++++++++- crates/oxide_emit/src/expr/function/mod.rs | 11 +-- .../src/stmt/declaration/function_decl.rs | 3 + crates/oxide_ir/src/contract.rs | 1 + crates/oxide_ir/src/inst.rs | 7 ++ crates/oxide_vm/src/dispatch/misc.rs | 37 ++++++++++ crates/oxide_vm/src/vm.rs | 5 ++ crates/oxide_vm/tests/rest_params_tests.rs | 74 +++++++++++++++++++ 10 files changed, 184 insertions(+), 8 deletions(-) create mode 100644 crates/oxide_vm/tests/rest_params_tests.rs diff --git a/crates/oxide_bytecode/src/opcode.rs b/crates/oxide_bytecode/src/opcode.rs index ce050ce9..985d0e3f 100644 --- a/crates/oxide_bytecode/src/opcode.rs +++ b/crates/oxide_bytecode/src/opcode.rs @@ -172,6 +172,8 @@ define_opcodes! { // ── 对象 (0x63) ── CREATE_ARGUMENTS = 0x63 => "CREATE_ARGUMENTS", + // rest 参数数组:rd=目标寄存器,b=固定形参数(rest 之前的形参个数)。 + CREATE_REST_ARRAY = 0x6E => "CREATE_REST_ARRAY", // ── define 语义属性写入 (0x6D) ── DEFINE_PROP = 0x6D => "DEFINE_PROP", @@ -363,6 +365,7 @@ mod tests { assert_eq!(OpCode::UNSPILL as u8, 0x0E); assert_eq!(OpCode::SPREAD_OBJECT as u8, 0x0F); assert_eq!(OpCode::CREATE_ARGUMENTS as u8, 0x63); + assert_eq!(OpCode::CREATE_REST_ARRAY as u8, 0x6E); assert_eq!(OpCode::ADD.to_string(), "ADD"); assert_eq!(OpCode::COMPOUND_MEMBER_EXP.to_string(), "COMPOUND_MEMBER_EXP"); assert_eq!(OpCode::MOV.to_string(), "MOV"); diff --git a/crates/oxide_emit/src/closure.rs b/crates/oxide_emit/src/closure.rs index 12983ccc..c8b48607 100644 --- a/crates/oxide_emit/src/closure.rs +++ b/crates/oxide_emit/src/closure.rs @@ -19,6 +19,12 @@ impl Emitter { names.insert(bi.name.to_string()); } } + // rest 形参也是本函数作用域绑定,遮蔽外层同名变量。 + if let Some(rest) = ¶ms.rest { + if let oxide_parser::BindingPattern::BindingIdentifier(bi) = &rest.rest.argument { + names.insert(bi.name.to_string()); + } + } names } diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index 68e844c7..3ed531a7 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -204,7 +204,8 @@ pub enum FunctionBodyContext { ClassElement, } -/// 参数规格:普通形参为标识符(可带默认值 initializer),解构形参用合成名 + 原始 pattern。 +/// 参数规格:普通形参为标识符(可带默认值 initializer),解构形参用合成名 + 原始 pattern, +/// rest 形参为数组(无默认值,只能是最末形参)。 pub enum ParamSpec<'a> { Identifier { name: String, @@ -215,6 +216,9 @@ pub enum ParamSpec<'a> { pattern: &'a oxide_parser::BindingPattern<'a>, initializer: Option<&'a Expression<'a>>, }, + Rest { + name: String, + }, } impl ParamSpec<'_> { @@ -222,6 +226,7 @@ impl ParamSpec<'_> { match self { Self::Identifier { name, .. } => name, Self::Pattern { synthetic_name, .. } => synthetic_name, + Self::Rest { name } => name, } } } @@ -675,6 +680,21 @@ impl Emitter { // ── 闭包捕获分析(AST 级,时序无关)── + /// 把 rest 形参的绑定模式追加为 `ParamSpec::Rest`:仅支持标识符形态(解构 rest 未支持)。 + pub(crate) fn push_rest_param<'a>( + &self, argument: &'a oxide_parser::BindingPattern<'a>, out: &mut Vec>, + ) -> Result<(), String> { + match argument { + oxide_parser::BindingPattern::BindingIdentifier(bi) => { + out.push(ParamSpec::Rest { + name: bi.name.to_string(), + }); + } + _ => return Err("rest parameters with destructuring patterns not yet supported".into()), + } + Ok(()) + } + /// 收集函数参数的绑定名(BindingIdentifier 形态)。 pub(crate) fn extract_function_parts<'a>( &self, function: &'a oxide_parser::Function<'a>, @@ -697,6 +717,9 @@ impl Emitter { } } } + if let Some(rest) = &function.params.rest { + self.push_rest_param(&rest.rest.argument, &mut param_specs)?; + } let body_stmts: &[Statement] = if let Some(body) = &function.body { &body.statements } else { &[] }; Ok((param_specs, body_stmts)) } @@ -828,12 +851,13 @@ impl Emitter { ctx.is_async = is_async; // length = 第一个带默认值形参之前的形参数(解构默认与标识符默认同规则); - // rest 参数不在 param_specs 中(独立字段),天然不计入。 + // rest 参数不计入 length(以 0 结尾即止)。 ctx.function_length = param_specs .iter() .take_while(|spec| match spec { ParamSpec::Identifier { initializer, .. } => initializer.is_none(), ParamSpec::Pattern { initializer, .. } => initializer.is_none(), + ParamSpec::Rest { .. } => false, }) .count() as u32; @@ -961,10 +985,15 @@ impl Emitter { ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(undef_reg), Operand::None, Operand::None)); } + // 调用契约参数段只含固定形参:rest 是函数体内普通变量,不在 VM 实参传递区。 + let fixed_count = param_specs + .iter() + .filter(|spec| !matches!(spec, ParamSpec::Rest { .. })) + .count() as u32; let ir = ctx.assemble_ir( oxide_ir::ParamLayout { base: param_base, - count: param_specs.len() as u32, + count: fixed_count, }, Some(parent_ctx), ); @@ -1006,6 +1035,7 @@ impl Emitter { // 内部闭包引用,需纳入捕获分析。 self.collect_pattern_default_exprs(pattern, &mut param_defaults); } + ParamSpec::Rest { .. } => {} } } ctx.own_bindings = self.collect_own_binding_names(¶m_names, body_stmts); @@ -1077,6 +1107,15 @@ impl Emitter { self.emit_default_if_undefined(reg, init, Some(name), ctx)?; } } + ParamSpec::Rest { name } => { + // rest 数组:从实参区收集固定形参之后的实参,绑定为普通变量。 + let reg = ctx.lookup(name)?; + let fixed_count = param_specs + .iter() + .filter(|s| !matches!(s, ParamSpec::Rest { .. })) + .count() as u32; + ctx.inst(Inst::create_rest_array(Operand::Reg(reg), fixed_count)); + } } } diff --git a/crates/oxide_emit/src/expr/function/mod.rs b/crates/oxide_emit/src/expr/function/mod.rs index 6e40ab2a..91db3433 100644 --- a/crates/oxide_emit/src/expr/function/mod.rs +++ b/crates/oxide_emit/src/expr/function/mod.rs @@ -12,11 +12,6 @@ impl Emitter { fn emit_arrow_function_expression( &self, arrow: &oxide_parser::ArrowFunctionExpression, ctx: &mut CompileCtx, ) -> Result { - // 未支持:箭头函数 rest 参数 - if let Some(_rest) = &arrow.params.rest { - return Err("rest params in arrow functions not yet supported".into()); - } - // 提取形参名(与函数表达式相同的形态) let mut param_names = Vec::new(); for (idx, param) in arrow.params.items.iter().enumerate() { @@ -36,6 +31,9 @@ impl Emitter { } } } + if let Some(rest) = &arrow.params.rest { + self.push_rest_param(&rest.rest.argument, &mut param_names)?; + } // 表达式体:以 is_expression_body=true 编译;语句体:以 false 编译。 let body_stmts = &arrow.body.statements; @@ -77,6 +75,9 @@ impl Emitter { } } } + if let Some(rest) = &fe.params.rest { + self.push_rest_param(&rest.rest.argument, &mut param_names)?; + } let body_stmts: &[Statement] = if let Some(body) = &fe.body { &body.statements } else { &[] }; diff --git a/crates/oxide_emit/src/stmt/declaration/function_decl.rs b/crates/oxide_emit/src/stmt/declaration/function_decl.rs index 843d4b4a..041444ac 100644 --- a/crates/oxide_emit/src/stmt/declaration/function_decl.rs +++ b/crates/oxide_emit/src/stmt/declaration/function_decl.rs @@ -32,6 +32,9 @@ impl Emitter { }), } } + if let Some(rest) = &fd.params.rest { + self.push_rest_param(&rest.rest.argument, &mut param_names)?; + } let body_stmts: &[Statement] = if let Some(body) = &fd.body { &body.statements } else { &[] }; let mut sub_module = if fd.generator { self.compile_generator_body(¶m_names, body_stmts, ctx)? diff --git a/crates/oxide_ir/src/contract.rs b/crates/oxide_ir/src/contract.rs index 1bd94610..306b4baa 100644 --- a/crates/oxide_ir/src/contract.rs +++ b/crates/oxide_ir/src/contract.rs @@ -242,6 +242,7 @@ impl Inst { | OpCode::NEW_OBJECT | OpCode::NEW_ARRAY | OpCode::CREATE_ARGUMENTS + | OpCode::CREATE_REST_ARRAY | OpCode::NOP => {} // 变量读写:LOAD_VAR/STORE_VAR/CELL_GET 读 a(None→0) OpCode::LOAD_VAR | OpCode::STORE_VAR | OpCode::CELL_GET => { diff --git a/crates/oxide_ir/src/inst.rs b/crates/oxide_ir/src/inst.rs index ebf73119..065bd6bf 100644 --- a/crates/oxide_ir/src/inst.rs +++ b/crates/oxide_ir/src/inst.rs @@ -267,6 +267,13 @@ impl Inst { Self::new(OpCode::CREATE_ARGUMENTS, dst, Operand::None, Operand::None) } + /// 创建 rest 参数数组:`dst = 当前帧实参区中下标 ≥ fixed_count 的实参组成的数组`。 + /// `fixed_count` 为 rest 之前的固定形参数,编码进 b 槽(Imm 单字节),运行时 + /// 与 CREATE_ARGUMENTS 同源读实参区,无寄存器 use。 + pub fn create_rest_array(dst: Operand, fixed_count: u32) -> Self { + Self::new(OpCode::CREATE_REST_ARRAY, dst, Operand::None, Operand::Imm(fixed_count as u16)) + } + /// 生成器让出:`rd` 为被让出的值;恢复时 `next(v)` 的 `v` 经 reg 0 交付 /// (与 CALL 同协议,emit 用 `LOAD_VAR(None)` 读取)。 pub fn yield_value(src: Operand) -> Self { diff --git a/crates/oxide_vm/src/dispatch/misc.rs b/crates/oxide_vm/src/dispatch/misc.rs index fe8be359..99f3feee 100644 --- a/crates/oxide_vm/src/dispatch/misc.rs +++ b/crates/oxide_vm/src/dispatch/misc.rs @@ -196,6 +196,43 @@ impl Vm { Ok(()) } + /// 创建 rest 参数数组:实参区下标 ≥ `fixed_count` 的实参收集为数组存入 `rd`。 + /// + /// # 步骤 + /// 1. 读当前帧(或 inline 同步调用)的实参区基址与个数。 + /// 2. 实参个数超出固定形参数的部分作为数组元素写入。 + /// + /// # 边界与前提 + /// - 实参来源与 CREATE_ARGUMENTS 相同(push_bytecode_frame / inline 路径写入 spill 栈)。 + /// - 实参不足 `fixed_count` 时数组为空。 + pub(crate) fn dispatch_create_rest_array(&mut self, rd: usize, fixed_count: usize) -> Result<(), String> { + let (base, count) = match self.frames.last() { + Some(frame) => (frame.arguments_base, frame.arguments_count), + None => (self.inline_args_base, self.inline_args_count), + }; + let n = count as usize; + let rest_len = n.saturating_sub(fixed_count); + let proto_ptr = self.session.builtin_world().array_proto.as_ptr() as *mut JsObject; + let bump = self.epoch.bump(); + let obj_ptr = self.alloc_object(JsObject::new_array( + EMPTY_SHAPE_ID, + JsValue::from_js_object(proto_ptr), + rest_len, + bump, + )); + let obj = unsafe { &mut *obj_ptr }; + for i in 0..rest_len { + let val = self + .spill_stack + .get(base as usize + fixed_count + i) + .copied() + .unwrap_or(JsValue::undefined()); + obj.set_prop_at(i as u32, val); + } + self.regs[rd] = JsValue::object(obj_ptr as *mut u8); + Ok(()) + } + #[inline(always)] pub(crate) fn dispatch_new_array(&mut self, rd: usize, instr: u32) { let n = opcode::imm16(instr) as usize; diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index b4a78a98..944e1a45 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -1330,6 +1330,11 @@ impl Vm { self.dispatch_create_arguments(rd)?; } + OpCode::CREATE_REST_ARRAY => { + let fixed_count = opcode::b(instr) as usize; + self.dispatch_create_rest_array(rd, fixed_count)?; + } + OpCode::NEW_ARRAY => { self.dispatch_new_array(rd, instr); } diff --git a/crates/oxide_vm/tests/rest_params_tests.rs b/crates/oxide_vm/tests/rest_params_tests.rs new file mode 100644 index 00000000..20f82b7a --- /dev/null +++ b/crates/oxide_vm/tests/rest_params_tests.rs @@ -0,0 +1,74 @@ +//! rest 参数集成测试:实参收集、length 交互、与 spread/箭头/闭包组合。 +//! 每个用例独立编译执行,断言顶层表达式结果。 + +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_types::value::JsValue; +use oxide_vm::vm::Vm; + +fn eval(source: &str) -> JsValue { + let allocator = Allocator::default(); + let program = oxide_parser::parse(&allocator, source).expect("parse failed"); + let module = Compiler::new().compile(&program).expect("compile failed"); + let mut vm = Vm::new(); + vm.run(&module).expect("vm run failed") +} + +fn eval_int(source: &str) -> i32 { + let v = eval(source); + if v.is_int() { + v.as_int() + } else if v.is_double() { + v.as_double() as i32 + } else { + panic!("expected number, got {v}") + } +} + +fn eval_str(source: &str) -> String { + let v = eval(source); + if v.is_string() { + unsafe { oxide_runtime_api::string_data(v) }.to_string() + } else { + format!("{v}") + } +} + +#[test] +fn rest_collects_remaining_arguments() { + assert_eq!(eval_int("function f(...args){return args.length} f(1,2,3)"), 3); + assert_eq!(eval_str("function f(...args){return args.join(',')} f(1,2,3)"), "1,2,3"); + assert_eq!(eval_int("function f(...args){return args.length} f()"), 0); + assert_eq!(eval_int("function f(...args){return args[0]+args[1]} f(7,8)"), 15); +} + +#[test] +fn rest_after_fixed_params_starts_at_fixed_count() { + assert_eq!(eval_str("function f(a,...rest){return a+':'+rest.join(',')} f(1,2,3)"), "1:2,3"); + assert_eq!(eval_int("function f(a,b,...rest){return rest.length} f(1,2,3,4,5)"), 3); + assert_eq!(eval_int("function f(a,b,...rest){return rest.length} f(1)"), 0); +} + +#[test] +fn rest_is_a_mutable_array() { + assert_eq!(eval_str("function f(...args){return Array.isArray(args)&&'y'} f(1)"), "y"); + assert_eq!(eval_str("function f(a,...r){r.push(4); return r.join(',')} f(1,2,3)"), "2,3,4"); + assert_eq!(eval_str("function f(...a){a[0]=99; return a.join(',')} f(1,2,3)"), "99,2,3"); +} + +#[test] +fn rest_combines_with_spread_and_arrow() { + assert_eq!(eval_int("function f(...rest){return rest.length} f(...[1,2,3])"), 3); + assert_eq!(eval_int("var f=(...args)=>args.length; f(1,2,3)"), 3); +} + +#[test] +fn rest_captured_by_closure() { + assert_eq!(eval_int("function f(...a){return ()=>a.length} f(1,2,3)()"), 3); +} + +#[test] +fn rest_does_not_count_toward_function_length() { + assert_eq!(eval("function f(...r){}; f.length"), JsValue::int(0)); + assert_eq!(eval("function f(a,b=1,...r){}; f.length"), JsValue::int(1)); +}