feat: 对象字面量 spread({...obj} 展开可枚举自有属性)
This commit is contained in:
@@ -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) => {}
|
||||
|
||||
@@ -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<u16> = 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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}"#);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user