From ae1257203a6d2744274de77dd02fb39f531e68e1 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 19 Oct 2026 06:42:30 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20Iterator.zipKeyed?= =?UTF-8?q?=20=E9=9D=99=E6=80=81=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 42 +-- Cargo.toml | 2 +- crates/oxide_builtins/src/iterator.rs | 250 +++++++++++++++--- crates/oxide_vm/src/bindings/bind_iterator.rs | 5 + crates/oxide_vm/src/bindings/mod.rs | 5 + 5 files changed, 251 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a913688..af6f9b16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.21.5" +version = "0.21.6" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.21.5" +version = "0.21.6" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.21.5" +version = "0.21.6" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.21.5" +version = "0.21.6" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.21.5" +version = "0.21.6" dependencies = [ "lru", "oxide_bytecode", @@ -1237,7 +1237,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.21.5" +version = "0.21.6" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1258,7 +1258,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.21.5" +version = "0.21.6" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1272,7 +1272,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.21.5" +version = "0.21.6" dependencies = [ "num-bigint", "oxc_allocator", @@ -1288,7 +1288,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.21.5" +version = "0.21.6" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "oxide_fold" -version = "0.21.5" +version = "0.21.6" dependencies = [ "num-bigint", "oxide_bytecode", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.21.5" +version = "0.21.6" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.21.5" +version = "0.21.6" dependencies = [ "bumpalo", "dashmap", @@ -1335,7 +1335,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.21.5" +version = "0.21.6" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.21.5" +version = "0.21.6" dependencies = [ "tracing", "tracing-appender", @@ -1356,11 +1356,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.21.5" +version = "0.21.6" [[package]] name = "oxide_parser" -version = "0.21.5" +version = "0.21.6" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1375,7 +1375,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.21.5" +version = "0.21.6" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.21.5" +version = "0.21.6" dependencies = [ "num-bigint", "num-traits", @@ -1404,7 +1404,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.21.5" +version = "0.21.6" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.21.5" +version = "0.21.6" dependencies = [ "bumpalo", "dashmap", @@ -1437,7 +1437,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.21.5" +version = "0.21.6" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 68f85c2b..cad9f6af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ ] [workspace.package] -version = "0.21.5" +version = "0.21.6" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_builtins/src/iterator.rs b/crates/oxide_builtins/src/iterator.rs index ef752071..9576e7cf 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -20,6 +20,7 @@ const ZIPS_PROP: &str = "__zips__"; const OPEN_PROP: &str = "__open__"; const RESULTS_PROP: &str = "__results__"; const PADDING_PROP: &str = "__padding__"; +const KEYS_PROP: &str = "__keys__"; const ITEMS_PROP: &str = "__items__"; const SEG_PROP: &str = "__seg__"; const CURRENT_PROP: &str = "__current__"; @@ -1489,7 +1490,158 @@ pub fn iterator_zip(vm: &mut H, args: &[u8]) -> NativeResult { } else { JsValue::undefined() }; - NativeResult::Ok(make_iterator_zip_helper(vm, &zips, mode, padding_value)) + NativeResult::Ok(make_iterator_zip_helper(vm, &zips, mode, padding_value, &[])) +} + +/// `Iterator.zipKeyed(iterables, options)`:`iterables` 为对象(属性键=结果键), +/// 各可枚举属性值须为可展平迭代器;返回迭代器逐键产出 null 原型对象。 +/// +/// # 步骤 +/// 1. 校验 `iterables` 为对象(非对象 TypeError);读 options/mode/padding +/// (与 zip 同口径,mode 非法 TypeError、longest 时 padding 非对象 TypeError)。 +/// 2. 遍历 `iterables.[[OwnPropertyKeys]]`:可枚举且值非 undefined 的属性才 +/// 收键(`keys`)并 GetIteratorFlattenable 收迭代器(`iters`);骤发逆序关 +/// 已收迭代器透传原值。 +/// 3. longest 且 padding 为对象:逐键 `Get(padding, key)` 预计算 padding 数组 +/// (骤发逆序关已收迭代器透传);padding 为 undefined 时全位 undefined。 +/// 4. 建 zip wrapper(带 `__keys__` 槽),步进期逐键构造 null 原型结果对象。 +/// +/// # 边界 +/// - 静态方法,无 this 检查。 +/// - 属性值为 undefined 时跳过(不收键、不收迭代器)。 +pub fn iterator_zip_keyed(vm: &mut H, args: &[u8]) -> NativeResult { + let iterables = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let options = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; + if !iterables.is_object() { + return NativeResult::Err(crate::error::create_type_error(vm, "Iterator.zipKeyed iterables must be an object")); + } + let options_obj = match get_options_object(vm, options) { + Ok(o) => o, + Err(v) => return NativeResult::Err(v), + }; + let options_ref = unsafe { &*options_obj.as_js_object_ptr() }; + let mode_si = vm.perm_intern("mode"); + let mode_val = match vm.ordinary_get(options_ref, mode_si, options_obj) { + Ok(v) => v, + Err(err) => return NativeResult::Err(engine_error(vm, &err)), + }; + let mode = if mode_val.is_undefined() { + 0 + } else if mode_val.is_string() { + let units = vm.string_units(mode_val); + if is_ascii_str(&units, "shortest") { + 0 + } else if is_ascii_str(&units, "longest") { + 1 + } else if is_ascii_str(&units, "strict") { + 2 + } else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Iterator.zipKeyed mode must be \"shortest\", \"longest\", or \"strict\"", + )); + } + } else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Iterator.zipKeyed mode must be \"shortest\", \"longest\", or \"strict\"", + )); + }; + let padding = if mode == 1 { + let padding_si = vm.perm_intern("padding"); + let p = match vm.ordinary_get(options_ref, padding_si, options_obj) { + Ok(v) => v, + Err(err) => return NativeResult::Err(engine_error(vm, &err)), + }; + if !p.is_undefined() && !p.is_object() { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Iterator.zipKeyed padding must be undefined or an object", + )); + } + p + } else { + JsValue::undefined() + }; + let iterables_obj = unsafe { &*iterables.as_js_object_ptr() }; + let all_keys = match vm.own_keys(iterables_obj) { + Ok(k) => k, + Err(e) => return NativeResult::Err(engine_error(vm, &e)), + }; + let mut keys: Vec = Vec::new(); + let mut iters: Vec<(JsValue, JsValue)> = Vec::new(); + let enumerable_si = vm.perm_intern("enumerable"); + for key in all_keys { + let key_si = vm.property_key_si(key); + let desc = match vm.get_own_property_desc(iterables_obj, key_si) { + Ok(d) => d, + Err(e) => { + let exc = engine_error(vm, &e); + close_zip_pairs(vm, &iters, None, Some(exc)); + return NativeResult::Err(exc); + } + }; + let Some(desc_val) = desc else { + continue; + }; + if !desc_val.is_object() { + continue; + } + let desc_ref = unsafe { &*desc_val.as_js_object_ptr() }; + let enumerable = match vm.ordinary_get(desc_ref, enumerable_si, desc_val) { + Ok(v) => v, + Err(e) => { + let exc = engine_error(vm, &e); + close_zip_pairs(vm, &iters, None, Some(exc)); + return NativeResult::Err(exc); + } + }; + if !oxide_runtime_api::to_boolean(enumerable) { + continue; + } + let value = match vm.ordinary_get(iterables_obj, key_si, iterables) { + Ok(v) => v, + Err(e) => { + let exc = engine_error(vm, &e); + close_zip_pairs(vm, &iters, None, Some(exc)); + return NativeResult::Err(exc); + } + }; + if value.is_undefined() { + continue; + } + keys.push(key); + match get_iterator_flattenable(vm, value) { + Ok(pair) => iters.push(pair), + Err(v) => { + close_zip_pairs(vm, &iters, None, Some(v)); + return NativeResult::Err(v); + } + } + } + // longest 且 padding 为对象:逐键 Get(padding, key) 预计算 padding 数组(长度 + // 恒为元素数);骤发逆序关已收迭代器透传原值。padding 为 undefined 时全位 + // undefined(longest 步进期越界落点同样 undefined)。 + let padding_value = if mode == 1 && padding.is_object() { + let padding_ref = unsafe { &*padding.as_js_object_ptr() }; + let mut pad_values: Vec = Vec::with_capacity(iters.len()); + for key in &keys { + let key_si = vm.property_key_si(*key); + let v = match vm.ordinary_get(padding_ref, key_si, padding) { + Ok(v) => v, + Err(e) => { + let exc = engine_error(vm, &e); + close_zip_pairs(vm, &iters, None, Some(exc)); + return NativeResult::Err(exc); + } + }; + pad_values.push(v); + } + make_array_from_list(vm, &pad_values) + } else { + JsValue::undefined() + }; + NativeResult::Ok(make_iterator_zip_helper(vm, &iters, mode, padding_value, &keys)) } /// 建 zip wrapper:空对象挂 `%IteratorHelperPrototype%`,写 `__zips__`(元素 @@ -1499,10 +1651,13 @@ pub fn iterator_zip(vm: &mut H, args: &[u8]) -> NativeResult { /// `__padding__` 存预计算 padding 数组(longest 且 padding 为对象时,长度恒为 /// 元素数)或 undefined;步进期按 done 位下标取落点值。 /// +/// `keys` 非空时(zipKeyed)写 `__keys__` 槽(键数组,与 `__zips__` 同序): +/// 步进期据此把结果构造成 null 原型对象(键=结果值);为空时(zip)构造数组。 +/// /// 独立构造器(不复用 [`make_iterator_helper`]):槽位布局不同,zip 无 /// `__inner__`/`__next__` 槽。 fn make_iterator_zip_helper( - vm: &mut H, zips: &[(JsValue, JsValue)], mode: i32, padding: JsValue, + vm: &mut H, zips: &[(JsValue, JsValue)], mode: i32, padding: JsValue, keys: &[JsValue], ) -> JsValue { let helper_proto = vm.builtin_proto(ProtoKind::IteratorHelperProto); let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(helper_proto))); @@ -1538,6 +1693,22 @@ fn make_iterator_zip_helper( vm.set_or_create_prop_value(wrapper_obj, state_si, JsValue::int(0)); vm.set_or_create_prop_value(wrapper_obj, kind_si, JsValue::int(IteratorHelperKind::Zip as i32)); vm.set_or_create_prop_value(wrapper_obj, padding_si, padding); + if !keys.is_empty() { + let keys_arr = + vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(array_proto), keys.len())); + for (i, key) in keys.iter().enumerate() { + // SAFETY: keys_arr 是刚经统一入口分配的数组对象,元素区已就绪。 + unsafe { + (*keys_arr).set_prop_at(i, *key); + } + } + // SAFETY: 与 new_array 同 epoch,写入后按实际元素数设 prop_count。 + unsafe { + (*keys_arr).set_prop_count(keys.len()); + } + let keys_si = vm.perm_intern(KEYS_PROP); + vm.set_or_create_prop_value(wrapper_obj, keys_si, JsValue::from_js_object(keys_arr)); + } JsValue::from_js_object(wrapper) } @@ -1642,16 +1813,17 @@ fn helper_step_zip( // longest 臂落点值来自预计算 padding 数组(用户调用窗口前读取)。 let padding_si = vm.perm_intern(PADDING_PROP); let padding_val = vm.ordinary_get(helper, padding_si, this_val).unwrap_or(JsValue::undefined()); - let array_proto = vm.builtin_proto(ProtoKind::ArrayProto); - let arr = vm.alloc_object(JsObject::new_array( - EMPTY_SHAPE_ID, - JsValue::from_js_object(array_proto), - iter_count as usize, - )); - let arr_val = JsValue::from_js_object(arr); + // 结果形态:`__keys__` 槽在场(zipKeyed)时构造 null 原型对象(键=结果值), + // 否则(zip)构造数组。键数组与 `__zips__` 同序,步进期按下标取键。 + let keys_si = vm.perm_intern(KEYS_PROP); + let keys_val = vm.ordinary_get(helper, keys_si, this_val).unwrap_or(JsValue::undefined()); + let keys_obj = if keys_val.is_object() { + Some(unsafe { &*keys_val.as_js_object_ptr() }) + } else { + None + }; let results_si = vm.perm_intern(RESULTS_PROP); - vm.set_or_create_prop_value(helper, results_si, arr_val); - let mut arr_len = 0usize; + let mut values: Vec = Vec::with_capacity(iter_count as usize); for i in 0..iter_count { if !zip_bit_open(open, i) { // 已关迭代器:longest 臂填 padding 落点续行(结果数组每个输入恒有 @@ -1663,11 +1835,7 @@ fn helper_step_zip( } else { JsValue::undefined() }; - // SAFETY: arr 是刚经统一入口分配的数组对象,元素区已就绪。 - unsafe { - (*arr).set_prop_at(arr_len, pad); - } - arr_len += 1; + values.push(pad); } continue; } @@ -1681,11 +1849,7 @@ fn helper_step_zip( let next_fn = pair_obj.get_prop_at(1); match iterator_record_step(vm, next_fn, iter) { Ok(Some(value)) => { - // SAFETY: arr 是刚经统一入口分配的数组对象,元素区已就绪。 - unsafe { - (*arr).set_prop_at(arr_len, value); - } - arr_len += 1; + values.push(value); } Ok(None) => { // 直接重赋值外层掩码:本臂不返回时后续迭代器的清位须在此基础上 @@ -1758,11 +1922,7 @@ fn helper_step_zip( } else { JsValue::undefined() }; - // SAFETY: 同上一分支。 - unsafe { - (*arr).set_prop_at(arr_len, pad); - } - arr_len += 1; + values.push(pad); } Err(v) => { open = zip_bit_clear(open, i); @@ -1774,11 +1934,39 @@ fn helper_step_zip( } } vm.set_or_create_prop_value(helper, results_si, JsValue::undefined()); - // SAFETY: 与 new_array 同 epoch,写入后按实际元素数设 prop_count。 - unsafe { - (*arr).set_prop_count(arr_len); - } - Ok(Some(arr_val)) + // 收口:按 `__keys__` 形态构造结果对象。zipKeyed 建 null 原型对象逐键写 + // 结果值(CreateDataPropertyOrThrow 语义,键已按自身键序收集);zip 建数组 + // 按下标写。 + let result_val = if let Some(keys_arr) = keys_obj { + let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + let obj_ptr = obj; + for (i, value) in values.iter().enumerate() { + let key = keys_arr.get_prop_at(i); + // 键经 ToPropertyKey 归一为 si(字符串/符号统一口径),逐键写结果值。 + let key_si = vm.property_key_si(key); + vm.set_or_create_prop_value(unsafe { &mut *obj_ptr }, key_si, *value); + } + JsValue::from_js_object(obj) + } else { + let array_proto = vm.builtin_proto(ProtoKind::ArrayProto); + let arr = vm.alloc_object(JsObject::new_array( + EMPTY_SHAPE_ID, + JsValue::from_js_object(array_proto), + values.len(), + )); + for (i, value) in values.iter().enumerate() { + // SAFETY: arr 是刚经统一入口分配的数组对象,元素区已就绪。 + unsafe { + (*arr).set_prop_at(i, *value); + } + } + // SAFETY: 与 new_array 同 epoch,写入后按实际元素数设 prop_count。 + unsafe { + (*arr).set_prop_count(values.len()); + } + JsValue::from_js_object(arr) + }; + Ok(Some(result_val)) } /// `Iterator.concat(...items)`:返回按序依次产出各实参元素的迭代器。 diff --git a/crates/oxide_vm/src/bindings/bind_iterator.rs b/crates/oxide_vm/src/bindings/bind_iterator.rs index e077033a..fbc86cfd 100644 --- a/crates/oxide_vm/src/bindings/bind_iterator.rs +++ b/crates/oxide_vm/src/bindings/bind_iterator.rs @@ -31,6 +31,11 @@ pub fn bind_iterator(env: BindEnv<'_>, global: &mut JsObject) { &[ ("from", oxide_builtins::iterator::iterator_from:: as *const (), 1), ("zip", oxide_builtins::iterator::iterator_zip:: as *const (), 1), + ( + "zipKeyed", + oxide_builtins::iterator::iterator_zip_keyed:: as *const (), + 1, + ), ("concat", oxide_builtins::iterator::iterator_concat:: as *const (), 0), ], ); diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index f5b34747..76c32211 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -968,6 +968,11 @@ fn bind_iterator_global(env: BindEnv<'_>, global: &mut JsObject) { &[ ("from", oxide_builtins::iterator::iterator_from:: as *const (), 1), ("zip", oxide_builtins::iterator::iterator_zip:: as *const (), 1), + ( + "zipKeyed", + oxide_builtins::iterator::iterator_zip_keyed:: as *const (), + 1, + ), ("concat", oxide_builtins::iterator::iterator_concat:: as *const (), 0), ], );