From 6c295156244ff0bfed2ffa7898249e10452edd06 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 8 Aug 2026 11:24:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AF=B9=E8=B1=A1=E5=AD=97=E9=9D=A2?= =?UTF-8?q?=E9=87=8F=20spread=EF=BC=88{...obj}=20=E5=B1=95=E5=BC=80?= =?UTF-8?q?=E5=8F=AF=E6=9E=9A=E4=B8=BE=E8=87=AA=E6=9C=89=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/object.rs | 3 +- crates/oxide_bytecode/src/opcode.rs | 3 + crates/oxide_compiler/src/hash/property.rs | 5 +- .../tests/structural_hash_tests.rs | 13 ++ .../src/expr/object/object_literal.rs | 130 ++++++++++-------- crates/oxide_ir/src/contract.rs | 15 ++ crates/oxide_ir/src/inst.rs | 14 ++ crates/oxide_vm/src/vm.rs | 4 + crates/oxide_vm/src/vm_dispatch_misc.rs | 93 +++++++++++++ crates/oxide_vm/tests/object_tests.rs | 34 +++++ 10 files changed, 251 insertions(+), 63 deletions(-) diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index deafc624..9bf7375f 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -14,7 +14,8 @@ fn is_integer_index(key: &str) -> bool { } /// 收集对象全部自身属性(shape 链),按规范顺序排列:整数索引在前升序,其余保持插入序。 -pub(crate) fn walk_own_keys(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> { +/// 数组元素区(整数下标)不在此列,调用方需另行枚举。 +pub fn walk_own_keys(vm: &H, obj: &JsObject) -> Vec<(u32, u32)> { let mut keys: Vec<(u32, u32)> = Vec::new(); let shape_id = obj.shape_id(); let mut pos: u32 = 0; diff --git a/crates/oxide_bytecode/src/opcode.rs b/crates/oxide_bytecode/src/opcode.rs index f6143ffd..b9fcf3a2 100644 --- a/crates/oxide_bytecode/src/opcode.rs +++ b/crates/oxide_bytecode/src/opcode.rs @@ -65,6 +65,7 @@ define_opcodes! { MOV = 0x0C => "MOV", // rd=dst, a=src(寄存器复制,区间拆分搬值) SPILL = 0x0D => "SPILL", // rd=src, ext=[slot u16](寄存器值写 VM spill 栈) UNSPILL = 0x0E => "UNSPILL", // rd=dst, ext=[slot u16](从 VM spill 栈恢复寄存器) + SPREAD_OBJECT = 0x0F => "SPREAD_OBJECT", // rd=目标对象, a=源(对象字面量 ... 展开,原地写目标) // ── 比较 (0x10-0x1F) ── EQ = 0x10 => "EQ", @@ -331,12 +332,14 @@ mod tests { assert_eq!(OpCode::MOV as u8, 0x0C); assert_eq!(OpCode::SPILL as u8, 0x0D); 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::ADD.to_string(), "ADD"); assert_eq!(OpCode::COMPOUND_MEMBER_EXP.to_string(), "COMPOUND_MEMBER_EXP"); assert_eq!(OpCode::MOV.to_string(), "MOV"); assert_eq!(OpCode::SPILL.to_string(), "SPILL"); assert_eq!(OpCode::UNSPILL.to_string(), "UNSPILL"); + assert_eq!(OpCode::SPREAD_OBJECT.to_string(), "SPREAD_OBJECT"); assert!(OpCode::try_from(0x1B).is_err()); assert!(OpCode::try_from(0xFF).is_err()); diff --git a/crates/oxide_compiler/src/hash/property.rs b/crates/oxide_compiler/src/hash/property.rs index 9e3ffd61..b09f660e 100644 --- a/crates/oxide_compiler/src/hash/property.rs +++ b/crates/oxide_compiler/src/hash/property.rs @@ -36,6 +36,9 @@ pub(super) fn hash_object_property_kind( hash_property_key(&p.key, h, include_binding_names); expression::hash_expression(&p.value, h, include_binding_names); } - ObjectPropertyKind::SpreadProperty(_) => {} + ObjectPropertyKind::SpreadProperty(spread) => { + // spread 的源表达式计入哈希:`{...a}` 与 `{...b}` 结构不同,避免缓存碰撞。 + expression::hash_expression(&spread.argument, h, include_binding_names); + } }); } diff --git a/crates/oxide_compiler/tests/structural_hash_tests.rs b/crates/oxide_compiler/tests/structural_hash_tests.rs index 0efb803b..914664cb 100644 --- a/crates/oxide_compiler/tests/structural_hash_tests.rs +++ b/crates/oxide_compiler/tests/structural_hash_tests.rs @@ -167,6 +167,19 @@ fn structural_hash_distinguishes_optional_chain_shape() { assert_ne!(optional_c, plain_b); } +#[test] +fn object_spread_source_shapes_differ() { + // 编译模块哈希含绑定名:{...a} 与 {...b} 展开源不同,缓存键必须不同。 + let spread_a = parse_to_compiled_hash("var o = { ...a };"); + let spread_b = parse_to_compiled_hash("var o = { ...b };"); + assert_ne!(spread_a, spread_b, "different spread sources must produce different hashes"); + + // 结构哈希区分 spread 与非 spread 的对象字面量。 + let plain = parse_to_hash("var o = { a: 1 };"); + let spread_after = parse_to_hash("var o = { a: 1, ...x };"); + assert_ne!(plain, spread_after, "spread vs non-spread object must hash differently"); +} + #[test] fn compiled_module_rejects_optional_chain_assignment_target() { let allocator = Allocator::default(); diff --git a/crates/oxide_emit/src/expr/object/object_literal.rs b/crates/oxide_emit/src/expr/object/object_literal.rs index 16f691a7..13e70317 100644 --- a/crates/oxide_emit/src/expr/object/object_literal.rs +++ b/crates/oxide_emit/src/expr/object/object_literal.rs @@ -14,69 +14,77 @@ impl Emitter { let obj_reg = ctx.alloc_reg(); ctx.inst(Inst::new(OpCode::NEW_OBJECT, Operand::Reg(obj_reg), Operand::None, Operand::None)); for prop in &obj.properties { - let ObjectPropertyKind::ObjectProperty(p) = prop else { - return Err("spread properties not yet supported".into()); + let ObjectPropertyKind::SpreadProperty(spread) = prop else { + self.emit_object_property(obj_reg, prop, ctx)?; + continue; }; - let computed = p.computed; - let prop_name = if computed { - "".to_string() - } else { - self.class_property_name(&p.key)? - }; - match p.kind { - PropertyKind::Get | PropertyKind::Set => { - if computed { - return Err("computed object accessors not yet supported".into()); - } - let accessor_reg = self.emit_expression(&p.value, ctx)?; - if let Some(sub_mod) = ctx.nested.last_mut() { - sub_mod.function_name = Some(prop_name.to_string()); - } - let undef_reg = self.emit_undefined(ctx); - let (get_reg, set_reg) = if p.kind == PropertyKind::Get { - (accessor_reg, undef_reg) - } else { - (undef_reg, accessor_reg) - }; - let idx = ctx.add_constant(Constant::String(prop_name.to_string())); - ctx.inst(Inst::define_accessor( - Operand::Reg(obj_reg), - Operand::Reg(get_reg), - Operand::Reg(set_reg), - idx as u32, - )); - } - _ => { - let key_reg = if computed { - self.emit_expression(p.key.to_expression(), ctx)? - } else { - let idx = ctx.add_constant(Constant::String(prop_name.to_string())); - let reg = ctx.alloc_reg(); - ctx.inst(Inst::load_const(Operand::Reg(reg), idx)); - reg - }; - let val_reg = self.emit_expression(&p.value, ctx)?; - if crate::is_anonymous_function_definition(&p.value) { - if let Some(sub_mod) = ctx.nested.last_mut() { - sub_mod.function_name = Some(prop_name.to_string()); - } - } - let op = if computed { OpCode::SET_PROP_DYNAMIC } else { OpCode::SET_PROP }; - let operands = if computed { - (Operand::Reg(key_reg), Operand::Reg(val_reg)) - } else { - (Operand::Reg(val_reg), Operand::Reg(key_reg)) - }; - ctx.inst(Inst::new( - op, - Operand::Reg(obj_reg), - operands.0, - operands.1, - )); - } - } + // spread 展开:把源表达式的可枚举自有属性写入目标对象(原地改)。 + // 顺序语义:{...b, a:1} 在 spread 之后定义 a,后者覆盖前者(从左到右求值)。 + let src_reg = self.emit_expression(&spread.argument, ctx)?; + ctx.inst(Inst::spread_object(Operand::Reg(obj_reg), Operand::Reg(src_reg))); } Ok(obj_reg) } -} + fn emit_object_property( + &self, obj_reg: u32, prop: &oxide_parser::ObjectPropertyKind, ctx: &mut CompileCtx, + ) -> Result<(), String> { + let ObjectPropertyKind::ObjectProperty(p) = prop else { + return Err("unsupported object property kind".into()); + }; + let computed = p.computed; + let prop_name = if computed { + "".to_string() + } else { + self.class_property_name(&p.key)? + }; + match p.kind { + PropertyKind::Get | PropertyKind::Set => { + if computed { + return Err("computed object accessors not yet supported".into()); + } + let accessor_reg = self.emit_expression(&p.value, ctx)?; + if let Some(sub_mod) = ctx.nested.last_mut() { + sub_mod.function_name = Some(prop_name.to_string()); + } + let undef_reg = self.emit_undefined(ctx); + let (get_reg, set_reg) = if p.kind == PropertyKind::Get { + (accessor_reg, undef_reg) + } else { + (undef_reg, accessor_reg) + }; + let idx = ctx.add_constant(Constant::String(prop_name.to_string())); + ctx.inst(Inst::define_accessor( + Operand::Reg(obj_reg), + Operand::Reg(get_reg), + Operand::Reg(set_reg), + idx as u32, + )); + } + _ => { + let key_reg = if computed { + self.emit_expression(p.key.to_expression(), ctx)? + } else { + let idx = ctx.add_constant(Constant::String(prop_name.to_string())); + let reg = ctx.alloc_reg(); + ctx.inst(Inst::load_const(Operand::Reg(reg), idx)); + reg + }; + let val_reg = self.emit_expression(&p.value, ctx)?; + if crate::is_anonymous_function_definition(&p.value) { + if let Some(sub_mod) = ctx.nested.last_mut() { + sub_mod.function_name = Some(prop_name.to_string()); + } + } + let op = if computed { OpCode::SET_PROP_DYNAMIC } else { OpCode::SET_PROP }; + let operands = if computed { + (Operand::Reg(key_reg), Operand::Reg(val_reg)) + } else { + (Operand::Reg(val_reg), Operand::Reg(key_reg)) + }; + ctx.inst(Inst::new(op, Operand::Reg(obj_reg), operands.0, operands.1)); + } + } + Ok(()) + } +} diff --git a/crates/oxide_ir/src/contract.rs b/crates/oxide_ir/src/contract.rs index c2b67f94..e0c3b392 100644 --- a/crates/oxide_ir/src/contract.rs +++ b/crates/oxide_ir/src/contract.rs @@ -266,6 +266,11 @@ impl Inst { OpCode::FOR_IN_NEXT | OpCode::FOR_IN_DONE | OpCode::FOR_OF_NEXT | OpCode::FOR_OF_DONE => {} // REST_OBJECT:读 a(ext 是 excluded_idx 常量) OpCode::REST_OBJECT => push_operand(&mut uses, &self.a), + // SPREAD_OBJECT:原地改目标对象,读目标(rd)与源(a) + OpCode::SPREAD_OBJECT => { + push_operand(&mut uses, &self.rd); + push_operand(&mut uses, &self.a); + } // TEMPLATE_STR:解析 ext,跳过 ext[0],后续 seg>>31==1 则低 8 位是 expr_reg OpCode::TEMPLATE_STR => { for seg in self.ext.iter().skip(1) { @@ -567,6 +572,16 @@ mod tests { assert_eq!(inst.use_regs().as_slice(), &[1]); } + #[test] + fn spread_object_def_rd_uses_rd_and_a() { + // SPREAD_OBJECT 原地改目标对象:def=rd,use=[rd, a],不可删除(有观察副作用) + let inst = Inst::spread_object(Operand::Reg(3), Operand::Reg(7)); + assert_eq!(inst.def_reg(), Some(3)); + assert_eq!(inst.use_regs().as_slice(), &[3, 7]); + let f = IRFunction::new(); + assert!(!inst.is_pure(&f)); + } + #[test] fn create_arguments_defs_rd_no_register_use_impure() { // CREATE_ARGUMENTS 写 rd;实参从 spill 栈读取,无寄存器 use; diff --git a/crates/oxide_ir/src/inst.rs b/crates/oxide_ir/src/inst.rs index a3fe2030..7d47d148 100644 --- a/crates/oxide_ir/src/inst.rs +++ b/crates/oxide_ir/src/inst.rs @@ -153,6 +153,13 @@ impl Inst { Self::with_ext(OpCode::REST_OBJECT, rest, src, Operand::None, &[excluded_idx]) } + /// 对象字面量 spread 展开:把源 `src` 的可枚举自有属性写入目标对象 `rd`(原地改)。 + /// 语义 = CopyDataProperties 的"非 null/undefined 源":null/undefined 合法(空展开), + /// 与 REST_OBJECT(对 null/undefined 抛 TypeError)不同。 + pub fn spread_object(target: Operand, src: Operand) -> Self { + Self::new(OpCode::SPREAD_OBJECT, target, src, Operand::None) + } + /// TEMPLATE_STR:变长 ext。首字打包 `(segment_count<<16) | total_len_hint`, /// 后续每 quasi 一项 `quasi_const_idx & 0x7FFF_FFFF`,其后若跟表达式再一项 `0x8000_0000 | expr_reg`。 pub fn template_str(dst: Operand, segment_count: u32, total_len_hint: u16, parts: &[u32]) -> Self { @@ -311,6 +318,13 @@ mod tests { let rest = Inst::rest_object(Operand::Reg(0), Operand::Reg(1), 7); assert_eq!(rest.ext.as_slice(), &[7]); + + let spread = Inst::spread_object(Operand::Reg(0), Operand::Reg(1)); + assert_eq!(spread.op, OpCode::SPREAD_OBJECT); + assert_eq!(spread.rd, Operand::Reg(0)); + assert_eq!(spread.a, Operand::Reg(1)); + assert_eq!(spread.b, Operand::None); + assert!(spread.ext.is_empty()); } #[test] diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 12a11456..5a46dadd 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -1351,6 +1351,10 @@ impl Vm { self.dispatch_rest_object(rd, a)?; } + OpCode::SPREAD_OBJECT => { + self.dispatch_spread_object(rd, a)?; + } + OpCode::THROW => match self.dispatch_throw(rd) { Ok(true) => continue, Ok(false) => {} diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index 82d0fc6c..4b752802 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -657,4 +657,97 @@ impl Vm { self.regs[rd] = JsValue::from_js_object(rest_ptr); Ok(()) } + + /// SPREAD_OBJECT:对象字面量 `...` 展开,把源的可枚举自有属性写入目标对象(原地改)。 + /// + /// 语义 = CopyDataProperties 的"非 null/undefined 源"分支:`{...null}` / `{...undefined}` + /// 合法(产出空展开),与 REST_OBJECT 对 null/undefined 抛 TypeError 不同。 + /// + /// # 步骤 + /// 1. null/undefined 源直接返回(空展开) + /// 2. 字符串源按 UTF-16 code unit 下标复制为可枚举索引属性 + /// 3. 对象源先收集可枚举自有属性(数组元素区 + shape 链),取值经 ordinary_get + /// 触发访问器 getter,再统一写入目标 + /// + /// # 边界与前提 + /// - 其它原始值(number/boolean/symbol)无自有可枚举属性 → 空展开 + /// - 目标已有同名属性被覆盖(后定义/后展开者胜) + /// + /// # 副作用 + /// - 原地修改 rd 指向的对象;取值可能触发 getter 并抛异常(异常传播) + pub(crate) fn dispatch_spread_object(&mut self, rd: usize, a: usize) -> Result<(), String> { + vm_trace!("SPREAD_OBJECT rd={}", rd); + let target_val = self.regs[rd]; + let src = self.regs[a]; + + // null/undefined 源合法:展开为空,直接返回。 + if src.is_null() || src.is_undefined() { + return Ok(()); + } + + // 字符串源:按索引复制字符(可枚举索引属性)。 + if src.is_string() { + let target = unsafe { &mut *target_val.as_js_object_ptr() }; + let code_units: Vec = unsafe { (*src.as_string_ptr()).data.encode_utf16().collect() }; + for (i, unit) in code_units.iter().enumerate() { + let s = char::from_u32(*unit as u32).map(|c| c.to_string()).unwrap_or_default(); + let si = self.kernel_core.perm_interner().intern(&i.to_string()).0; + let ch_val = self.new_string(&s); + self.set_or_create_prop_value(target, si, ch_val); + } + return Ok(()); + } + + // 其它原始值无自有可枚举属性 → 空展开。 + if !src.is_object() { + return Ok(()); + } + + let src_obj = unsafe { &*src.as_js_object_ptr() }; + + // 收集-提交:先取齐 (键, 值),取值阶段触发 getter(可能抛异常), + // 再统一写目标,避免提交写与取值互相交错。 + let mut assignments: Vec<(u32, JsValue)> = Vec::new(); + + // 数组元素区:整数下标可枚举元素(hole 跳过)。 + if src_obj.is_array() { + for i in 0..src_obj.array_prop_count { + if src_obj.prop_meta_at(i).is_some_and(|m| m.is_hole()) { + continue; + } + let enumerable = src_obj + .prop_meta_at(i) + .map(|m| m.attributes.enumerable()) + .unwrap_or(PropAttributes::DEFAULT_DATA.enumerable()); + if enumerable { + let si = self.kernel_core.perm_interner().intern(&i.to_string()).0; + let val = self.ordinary_get(src_obj, si, src)?; + assignments.push((si, val)); + } + } + } + + // 命名属性:shape 链(walk_own_keys 规范顺序),仅可枚举。 + let keys = oxide_builtins::object::walk_own_keys(self, src_obj); + for (si, pos) in keys { + let store = if src_obj.is_array() { src_obj.array_prop_count + pos } else { pos }; + let enumerable = src_obj + .prop_meta_at(store) + .map(|m| m.attributes.enumerable()) + .unwrap_or(PropAttributes::DEFAULT_DATA.enumerable()); + if !enumerable { + continue; + } + let val = self.ordinary_get(src_obj, si, src)?; + assignments.push((si, val)); + } + + // 提交:覆盖目标已有同名属性(从左到右求值,后展开者胜)。 + let target = unsafe { &mut *target_val.as_js_object_ptr() }; + for (si, val) in assignments { + let promoted = self.promote_if_needed_for_write_ptr(target, val); + self.set_or_create_prop_value(target, si, promoted); + } + Ok(()) + } } diff --git a/crates/oxide_vm/tests/object_tests.rs b/crates/oxide_vm/tests/object_tests.rs index b40a0dff..7822b0cb 100644 --- a/crates/oxide_vm/tests/object_tests.rs +++ b/crates/oxide_vm/tests/object_tests.rs @@ -152,3 +152,37 @@ fn object_literal_setter_updates_receiver() { fn object_literal_getter_setter_pair() { assert_eq!(eval("var o={ get x(){ return this.y }, set x(v){ this.y=v } }; o.x=9; o.x"), "9"); } + +fn eval_string(source: &str) -> String { + let allocator = Allocator::default(); + let mut vm = Vm::new(); + let program = oxide_parser::parse(&allocator, source).expect("parse failed"); + let module = Compiler::new().compile(&program).expect("compile failed"); + let result = vm.run(&module).expect("vm run failed"); + vm.lookup_str(result).unwrap_or_default() +} + +#[test] +fn object_literal_spread_copies_own_properties() { + assert_eq!(eval_string("JSON.stringify({a:0,...{x:1,y:2}})"), r#"{"a":0,"x":1,"y":2}"#); +} + +#[test] +fn object_literal_spread_later_definitions_override() { + assert_eq!(eval("({...{a:1},a:9}).a"), "9"); + assert_eq!(eval("({a:1,...{a:9}}).a"), "9"); +} + +#[test] +fn object_literal_spread_nullish_is_empty() { + assert_eq!(eval_string("JSON.stringify({...null})"), "{}"); + assert_eq!(eval_string("JSON.stringify({...undefined})"), "{}"); + assert_eq!(eval_string("JSON.stringify({...42})"), "{}"); +} + +#[test] +fn object_literal_spread_string_indexes_and_getters() { + assert_eq!(eval_string("JSON.stringify({...'ab'})"), r#"{"0":"a","1":"b"}"#); + assert_eq!(eval("({...{get x(){return 5}}}).x"), "5"); + assert_eq!(eval_string("JSON.stringify({...[],a:1})"), r#"{"a":1}"#); +}