From 5d7c78cdea9d748cb37eff75da9c60a4af0625ae Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 16 Oct 2026 11:32:29 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Iterator.zip=20=E6=94=AF=E6=8C=81=20lon?= =?UTF-8?q?gest=20=E4=B8=8E=20strict=20=E6=A8=A1=E5=BC=8F=E5=8F=8A=20paddi?= =?UTF-8?q?ng=20=E5=A1=AB=E5=85=85?= 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 | 173 ++++++++- .../oxide_vm/tests/builtins_iterator_tests.rs | 337 ++++++++++++++++++ 4 files changed, 519 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 93ad4dc0..e4afbe4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.19.26" +version = "0.19.27" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.19.26" +version = "0.19.27" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.19.26" +version = "0.19.27" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.19.26" +version = "0.19.27" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.19.26" +version = "0.19.27" dependencies = [ "lru", "oxide_bytecode", @@ -1237,7 +1237,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.19.26" +version = "0.19.27" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1258,7 +1258,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.19.26" +version = "0.19.27" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1272,7 +1272,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.19.26" +version = "0.19.27" dependencies = [ "num-bigint", "oxc_allocator", @@ -1288,7 +1288,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.19.26" +version = "0.19.27" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "oxide_fold" -version = "0.19.26" +version = "0.19.27" dependencies = [ "num-bigint", "oxide_bytecode", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.19.26" +version = "0.19.27" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.19.26" +version = "0.19.27" dependencies = [ "bumpalo", "dashmap", @@ -1335,7 +1335,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.19.26" +version = "0.19.27" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.19.26" +version = "0.19.27" dependencies = [ "tracing", "tracing-appender", @@ -1356,11 +1356,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.19.26" +version = "0.19.27" [[package]] name = "oxide_parser" -version = "0.19.26" +version = "0.19.27" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1375,7 +1375,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.19.26" +version = "0.19.27" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.19.26" +version = "0.19.27" dependencies = [ "num-bigint", "num-traits", @@ -1404,7 +1404,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.19.26" +version = "0.19.27" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.19.26" +version = "0.19.27" dependencies = [ "bumpalo", "dashmap", @@ -1437,7 +1437,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.19.26" +version = "0.19.27" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 2c0e12a8..0689d63d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ ] [workspace.package] -version = "0.19.26" +version = "0.19.27" 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 c1b20693..49ca84f7 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -234,6 +234,26 @@ pub(crate) fn iterator_record_step( Ok(Some(value)) } +/// 调用缓存的 next 取一步,只读结果对象的 `done`(对应规范 IteratorStep, +/// 不读 value getter)。 +/// +/// # 返回值 +/// - `Ok(true)`:迭代完成(done=true); +/// - `Ok(false)`:未耗尽(done=false,不读 value); +/// - `Err`:next 抛错 / 结果非对象 / done getter 抛错(透传原值,不关底层)。 +pub(crate) fn iterator_step_done_only( + vm: &mut H, next: JsValue, iterated: JsValue, +) -> Result { + let result = vm.call_function_sync(next, iterated, &[]).map_err(|e| engine_error(vm, &e))?; + if !result.is_object() { + return Err(crate::error::create_type_error(vm, "iterator result is not an object")); + } + let result_obj = unsafe { &*result.as_js_object_ptr() }; + let done_si = vm.perm_intern("done"); + let done = vm.ordinary_get(result_obj, done_si, result).map_err(|e| engine_error(vm, &e))?; + Ok(oxide_runtime_api::to_boolean(done)) +} + /// IteratorClose:延迟 GetMethod(iterator, "return") 并调用,按 completion 形态决定 /// 错误胜者(throw → 原错误胜出;正常 → return 相关错误胜出)。 /// @@ -443,7 +463,9 @@ fn get_iterator_flattenable(vm: &mut H, value: JsValue) -> Result<(Js /// /// # 返回 /// - `Ok((iter, next))`:迭代器 + GetIteratorDirect 缓存的 next。 -fn get_iterator_strict(vm: &mut H, value: JsValue) -> Result<(JsValue, JsValue), JsValue> { +fn get_iterator_strict( + vm: &mut H, what: &str, value: JsValue, +) -> Result<(JsValue, JsValue), JsValue> { let obj = unsafe { &*value.as_js_object_ptr() }; let sym_iter_si = encode_symbol_key(vm.realm_id(), 0); let method = match vm.ordinary_get(obj, sym_iter_si, value) { @@ -451,13 +473,16 @@ fn get_iterator_strict(vm: &mut H, value: JsValue) -> Result<(JsValue Err(err) => return Err(engine_error(vm, &err)), }; if !is_callable(method) { - return Err(crate::error::create_type_error(vm, "Iterator.zip iterables is not iterable")); + return Err(crate::error::create_type_error( + vm, + &format!("Iterator.zip {what} is not iterable"), + )); } let iter = vm.call_function_sync(method, value, &[]).map_err(|e| engine_error(vm, &e))?; if !iter.is_object() { return Err(crate::error::create_type_error( vm, - "Iterator.zip iterables @@iterator returned a non-object", + &format!("Iterator.zip {what} @@iterator returned a non-object"), )); } get_iterator_direct(vm, iter) @@ -1261,7 +1286,8 @@ fn helper_step_flat_map( /// `iterables` 对象、读 options(mode 严格字符串校验,longest 时读 padding), /// 再全 GetIterator 取 `inputIter`,元素收集循环(IteratorStepValue 骤发逆序关 /// 已收集元素迭代器;元素经 GetIteratorFlattenable 骤发逆序关含 inputIter), -/// 最后建 zip wrapper。 +/// longest 且 padding 为对象时预计算 padding 数组(骤发逆序关元素迭代器、 +/// padding 迭代器不关;未耗尽收口正常关),最后建 zip wrapper。 /// /// # 步骤 /// 1. `iterables` 非对象 → TypeError(options 不读)。 @@ -1269,7 +1295,9 @@ fn helper_step_flat_map( /// String 包装对象拒绝、不触发 toPrimitive,无效 mode 不读 padding)。 /// 3. longest 模式读 `padding`(undefined 或对象,其余 TypeError)。 /// 4. GetIterator(无鸭子回退)取 inputIter;元素收集循环骤发逆序关后透传。 -/// 5. 建 zip wrapper(`__kind__`=5)。 +/// 5. longest 且 padding 为对象:GetIterator(padding) 加升序步进至多元素数次 +/// 预计算 padding 数组(耗尽余位填 undefined,未耗尽正常关)。 +/// 6. 建 zip wrapper(`__kind__`=5)。 pub fn iterator_zip(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() }; @@ -1324,7 +1352,7 @@ pub fn iterator_zip(vm: &mut H, args: &[u8]) -> NativeResult { } else { JsValue::undefined() }; - let (input_iter, input_next) = match get_iterator_strict(vm, iterables) { + let (input_iter, input_next) = match get_iterator_strict(vm, "iterables", iterables) { Ok(pair) => pair, Err(v) => return NativeResult::Err(v), }; @@ -1347,13 +1375,57 @@ pub fn iterator_zip(vm: &mut H, args: &[u8]) -> NativeResult { } } } - NativeResult::Ok(make_iterator_zip_helper(vm, &zips, mode, padding)) + // longest 且 padding 为对象:预计算 padding 数组(长度恒为元素数,耗尽后余位 + // 填 undefined)。骤发(GetIterator 与 next/done/value getter)逆序关已收集 + // 元素迭代器而 padding 迭代器不关,透传原值;全部步进后未耗尽则正常关, + // 关闭骤发同样逆序关元素迭代器透传。 + let padding_value = if mode == 1 && padding.is_object() { + let (padding_iter, padding_next) = match get_iterator_strict(vm, "padding", padding) { + Ok(pair) => pair, + Err(v) => { + close_zip_pairs(vm, &zips, None, Some(v)); + return NativeResult::Err(v); + } + }; + let mut pad_values: Vec = Vec::new(); + let mut using_iterator = true; + for _ in 0..zips.len() { + if using_iterator { + match iterator_record_step(vm, padding_next, padding_iter) { + Ok(Some(value)) => pad_values.push(value), + Ok(None) => { + using_iterator = false; + pad_values.push(JsValue::undefined()); + } + Err(v) => { + close_zip_pairs(vm, &zips, None, Some(v)); + return NativeResult::Err(v); + } + } + } else { + pad_values.push(JsValue::undefined()); + } + } + if using_iterator { + if let Err(v) = iterator_close_record(vm, padding_iter, None) { + close_zip_pairs(vm, &zips, None, Some(v)); + return NativeResult::Err(v); + } + } + make_array_from_list(vm, &pad_values) + } else { + JsValue::undefined() + }; + NativeResult::Ok(make_iterator_zip_helper(vm, &zips, mode, padding_value)) } /// 建 zip wrapper:空对象挂 `%IteratorHelperPrototype%`,写 `__zips__`(元素 /// 迭代器对数组,每元素为 [iter, next] 对)、`__open__`(位掩码,初始全 1)、 /// `__mode__`(0/1/2)、`__state__`(0)、`__kind__`(5)、`__padding__` 槽。 /// +/// `__padding__` 存预计算 padding 数组(longest 且 padding 为对象时,长度恒为 +/// 元素数)或 undefined;步进期按 done 位下标取落点值。 +/// /// 独立构造器(不复用 [`make_iterator_helper`]):槽位布局不同,zip 无 /// `__inner__`/`__next__` 槽。 fn make_iterator_zip_helper( @@ -1467,10 +1539,13 @@ fn close_zip_pairs( } } -/// zip 推进(shortest 臂加 mode 分派骨架):升序步进开放迭代器,收集到钉在 -/// `__results__` 槽的新数组;done 清位后 shortest 逆序关其余开放迭代器(正常 -/// 完成)收口;abrupt 清位后逆序关其余(原值胜出)透传;longest/strict 臂 -/// 未实现,此处按 longest 的 undefined 填充语义降级保 wrapper 健壮。 +/// zip 推进:升序步进开放迭代器,收集到钉在 `__results__` 槽的新数组。三 mode +/// 臂:shortest done 位逆序关其余开放迭代器(正常完成)收口;longest done 位填 +/// 预计算 `padding[i]`(越界 undefined)续行、全耗尽收口;strict done 位 i 非 0 +/// 逆序关全部开放迭代器(throw 完成,新 TypeError 胜出)后抛新 TypeError,i 为 0 +/// 升序余者扫描(只读 done,已关位视 done 不重调 next),首个骤发逆序关全部开放 +/// 迭代器透传原值、首个未耗尽逆序关全部开放迭代器抛新 TypeError、全耗尽正常收口; +/// 步进骤发清位后逆序关其余(原值胜出)透传。 fn helper_step_zip( vm: &mut H, this_val: JsValue, helper: &mut JsObject, ) -> Result, JsValue> { @@ -1491,6 +1566,9 @@ fn helper_step_zip( } let mode_si = vm.perm_intern(MODE_PROP); let mode = read_slot_int(vm, helper, mode_si, 0); + // 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, @@ -1503,6 +1581,21 @@ fn helper_step_zip( let mut arr_len = 0usize; for i in 0..iter_count { if !zip_bit_open(open, i) { + // 已关迭代器:longest 臂填 padding 落点续行(结果数组每个输入恒有 + // 一槽);shortest/strict 首 done 即返回,本臂不会到达已关位。 + if mode == 1 { + let pad = if padding_val.is_object() { + let pad_obj = unsafe { &*padding_val.as_js_object_ptr() }; + pad_obj.get_prop_at(i) + } else { + JsValue::undefined() + }; + // SAFETY: arr 是刚经统一入口分配的数组对象,元素区已就绪。 + unsafe { + (*arr).set_prop_at(arr_len, pad); + } + arr_len += 1; + } continue; } // 每次用户调用窗口后重读槽位(回调可改写 wrapper 槽)。 @@ -1534,13 +1627,67 @@ fn helper_step_zip( Err(v) => Err(v), }; } - // longest/strict 臂未实现:按 longest 的 undefined 填充语义降级。 + if mode == 2 { + // strict:i 非 0 done → 逆序关全部开放迭代器(throw 完成,新 + // TypeError 胜出)后抛新 TypeError。 + if i != 0 { + let type_err = crate::error::create_type_error( + vm, + "Iterator.zip strict mode: iterators are not all the same length", + ); + let _ = close_remaining_open(vm, this_val, helper, open, iter_count, Some(type_err)); + return Err(type_err); + } + // i 为 0:升序余者扫描(只读 done,已关位视 done 不重调 next)。 + for k in 1..iter_count { + if !zip_bit_open(open, k) { + continue; + } + let pair_val = zips_obj.get_prop_at(k); + if !pair_val.is_object() { + continue; + } + let pair_obj = unsafe { &*pair_val.as_js_object_ptr() }; + let iter_k = pair_obj.get_prop_at(0); + let next_k = pair_obj.get_prop_at(1); + match iterator_step_done_only(vm, next_k, iter_k) { + Err(v) => { + open = zip_bit_clear(open, k); + vm.set_or_create_prop_value(helper, open_si, JsValue::int(open)); + let _ = close_remaining_open(vm, this_val, helper, open, iter_count, Some(v)); + return Err(v); + } + Ok(true) => { + // done 位移出开放集(不重调 next,不参与关全部)。 + open = zip_bit_clear(open, k); + vm.set_or_create_prop_value(helper, open_si, JsValue::int(open)); + } + Ok(false) => { + let type_err = crate::error::create_type_error( + vm, + "Iterator.zip strict mode: iterators are not all the same length", + ); + let _ = close_remaining_open(vm, this_val, helper, open, iter_count, Some(type_err)); + return Err(type_err); + } + } + } + // 全耗尽:正常收口。 + return Ok(None); + } + // longest:done 位填 padding[i](越界 undefined)续行。 if !zip_bit_open(open, 0) && (0..iter_count).all(|k| !zip_bit_open(open, k)) { return Ok(None); } + let pad = if padding_val.is_object() { + let pad_obj = unsafe { &*padding_val.as_js_object_ptr() }; + pad_obj.get_prop_at(i) + } else { + JsValue::undefined() + }; // SAFETY: 同上一分支。 unsafe { - (*arr).set_prop_at(arr_len, JsValue::undefined()); + (*arr).set_prop_at(arr_len, pad); } arr_len += 1; } diff --git a/crates/oxide_vm/tests/builtins_iterator_tests.rs b/crates/oxide_vm/tests/builtins_iterator_tests.rs index d40dcfb3..5ddb7944 100644 --- a/crates/oxide_vm/tests/builtins_iterator_tests.rs +++ b/crates/oxide_vm/tests/builtins_iterator_tests.rs @@ -2703,3 +2703,340 @@ fn iterator_zip_reentrancy_guard() { .unwrap(); assert!(r.as_bool(), "executing reentry on return throws TypeError"); } + +#[test] +fn iterator_zip_longest_basic_values_and_padding() { + // longest 基本值与 padding 落点:无 padding 填 undefined、padding 数组、 + // padding 迭代器(耗尽余位 undefined、无限迭代器步进至多 iterCount 次)。 + let mut vm = Vm::new(); + let bool_cases = [ + ( + "var it = Iterator.zip([[1,2,3],[4,5]], {mode: 'longest'}); \ + var r1 = it.next().value; var r2 = it.next().value; var r3 = it.next(); \ + r1[0] === 1 && r1[1] === 4 && r2[0] === 2 && r2[1] === 5 \ + && r3.value[0] === 3 && r3.value[1] === undefined && r3.done === false \ + && it.next().done === true", + true, + ), + ( + "var it = Iterator.zip([[1,2],[3,4,5]], {mode: 'longest', padding: ['p','q']}); \ + var r1 = it.next().value; var r2 = it.next().value; var r3 = it.next(); \ + r1[0] === 1 && r1[1] === 3 && r2[0] === 2 && r2[1] === 4 \ + && r3.value[0] === 'p' && r3.value[1] === 5 && it.next().done === true", + true, + ), + ( + "var it = Iterator.zip([[1,2],[3,4,5]], {mode: 'longest', padding: []}); \ + it.next(); it.next(); var r3 = it.next().value; r3[0] === undefined && r3[1] === 5 \ + && it.next().done === true", + true, + ), + ( + "var pads = { [Symbol.iterator]() { return {v: 0, next() { this.v++; return {value: this.v, done: false}; }}; } }; \ + var it = Iterator.zip([[1,2],[3,4,5]], {mode: 'longest', padding: pads}); \ + it.next(); it.next(); var r3 = it.next().value; r3[0] === 1 && r3[1] === 5", + true, + ), + ( + "var steps = 0; var rets = 0; \ + var infinite = { [Symbol.iterator]() { var self = this; return { next() { steps++; return {value: steps, done: false}; }, \ + return() { rets++; return {done: true}; } }; } }; \ + var it = Iterator.zip([[1,2],[3]], {mode: 'longest', padding: infinite}); \ + it.next(); it.next(); it.next(); \ + steps === 2 && rets === 1", + true, + ), + ]; + for (src, expected) in bool_cases { + let result = eval(&mut vm, src).unwrap(); + assert_eq!(result.as_bool(), expected, "for {}", src); + } +} + +#[test] +fn iterator_zip_strict_basic_values() { + // strict 基本值:等长正常收口,不等长 done 位抛 TypeError。 + let mut vm = Vm::new(); + let bool_cases = [ + ( + "var it = Iterator.zip([[1,2,3],[4,5,6]], {mode: 'strict'}); \ + it.next().value[0] === 1 && it.next().value[1] === 5 && it.next().value[0] === 3 \ + && it.next().done === true", + true, + ), + ( + "var ok = false; try { \ + var it = Iterator.zip([[1,2,3],[4,5]], {mode: 'strict'}); \ + it.next(); it.next(); it.next(); \ + } catch (e) { ok = e instanceof TypeError; } ok", + true, + ), + ( + "var ok = false; try { \ + var it = Iterator.zip([[1,2],[3,4,5]], {mode: 'strict'}); \ + it.next(); it.next(); it.next(); \ + } catch (e) { ok = e instanceof TypeError; } ok", + true, + ), + ]; + for (src, expected) in bool_cases { + let result = eval(&mut vm, src).unwrap(); + assert_eq!(result.as_bool(), expected, "for {}", src); + } +} + +#[test] +fn iterator_zip_strict_done_close_order() { + // strict done 位 i 非 0:逆序关全部开放迭代器(throw 完成,return 错误被吞), + // 抛新 TypeError;done 位迭代器不关。 + let mut vm = Vm::new(); + let r = eval( + &mut vm, + "(() => { + var log = []; + var first = { next() { log.push('first next'); return {done: false}; }, + return() { log.push('first return'); throw new RangeError('x'); } }; + var second = { next() { log.push('second next'); return {done: true}; }, + return() { log.push('second return'); } }; + var third = { next() { log.push('third next'); }, + return() { log.push('third return'); throw new ReferenceError('boom'); } }; + var it = Iterator.zip([first, second, third], {mode: 'strict'}); + var threw = false; + try { it.next(); } catch (e) { threw = e instanceof TypeError; } + return threw + '|' + JSON.stringify(log); + })()", + ) + .unwrap(); + assert_eq!( + to_str(&vm, r), + "true|[\"first next\",\"second next\",\"third return\",\"first return\"]", + "strict i not zero: close all open in reverse, new TypeError wins" + ); +} + +#[test] +fn iterator_zip_strict_i_is_zero_scan() { + // strict done 位 i 为 0:升序余者扫描(只读 done),三臂——全耗尽正常收口、 + // 首个未耗尽逆序关全部开放抛新 TypeError、骤发逆序关全部透传原值。 + let mut vm = Vm::new(); + // 全耗尽:正常收口,无 return 调用。 + let r = eval( + &mut vm, + "(() => { + var log = []; + function makeIter(name) { + return { next() { log.push(name + ' next'); return {done: true}; }, + return() { log.push(name + ' return'); } }; + } + var it = Iterator.zip([makeIter('a'), makeIter('b'), makeIter('c')], {mode: 'strict'}); + var result = it.next(); + return (result.done === true) + '|' + JSON.stringify(log); + })()", + ) + .unwrap(); + assert_eq!( + to_str(&vm, r), + "true|[\"a next\",\"b next\",\"c next\"]", + "strict i zero all exhausted: normal completion, no return calls" + ); + // 首个未耗尽:逆序关全部开放(含已关位不重调 next),抛新 TypeError。 + let r = eval( + &mut vm, + "(() => { + var log = []; + var first = { next() { log.push('first next'); return {done: true}; }, + return() { log.push('first return'); } }; + var second = { next() { log.push('second next'); return {done: true}; }, + return() { log.push('second return'); } }; + var third = { next() { log.push('third next'); return {done: false}; }, + return() { log.push('third return'); throw new RangeError('x'); } }; + var fourth = { next() { log.push('fourth next'); }, + return() { log.push('fourth return'); throw new ReferenceError('boom'); } }; + var it = Iterator.zip([first, second, third, fourth], {mode: 'strict'}); + var threw = false; + try { it.next(); } catch (e) { threw = e instanceof TypeError; } + return threw + '|' + JSON.stringify(log); + })()", + ) + .unwrap(); + assert_eq!( + to_str(&vm, r), + "true|[\"first next\",\"second next\",\"third next\",\"fourth return\",\"third return\"]", + "strict i zero not exhausted: close all open in reverse, new TypeError wins" + ); + // 骤发:首个骤发逆序关全部开放(原值胜出),骤发位不关。 + let r = eval( + &mut vm, + "(() => { + var log = []; + var first = { next() { log.push('first next'); return {done: true}; }, + return() { log.push('first return'); } }; + var second = { next() { log.push('second next'); throw new RangeError('boom'); }, + return() { log.push('second return'); } }; + var third = { next() { log.push('third next'); }, + return() { log.push('third return'); throw new ReferenceError('x'); } }; + var fourth = { next() { log.push('fourth next'); }, + return() { log.push('fourth return'); throw new ReferenceError('y'); } }; + var it = Iterator.zip([first, second, third, fourth], {mode: 'strict'}); + var threw = false; + try { it.next(); } catch (e) { threw = e instanceof RangeError; } + return threw + '|' + JSON.stringify(log); + })()", + ) + .unwrap(); + assert_eq!( + to_str(&vm, r), + "true|[\"first next\",\"second next\",\"fourth return\",\"third return\"]", + "strict i zero scan abrupt: close all open in reverse, original value wins" + ); +} + +#[test] +fn iterator_zip_longest_abrupt_close_order() { + // longest 步进骤发:清位后逆序关其余开放迭代器(原值胜出),done 位不关。 + let mut vm = Vm::new(); + let r = eval( + &mut vm, + "(() => { + var log = []; + var empty = { next() { log.push('empty next'); return {done: true}; }, + return() { log.push('empty return'); } }; + var first = { next() { log.push('first next'); throw new RangeError('boom'); }, + return() { log.push('first return'); } }; + var second = { next() { log.push('second next'); }, + return() { log.push('second return'); throw new ReferenceError('x'); } }; + var third = { next() { log.push('third next'); }, + return() { log.push('third return'); throw new ReferenceError('y'); } }; + var it = Iterator.zip([empty, first, second, third], {mode: 'longest'}); + var threw = false; + try { it.next(); } catch (e) { threw = e instanceof RangeError; } + return threw + '|' + JSON.stringify(log); + })()", + ) + .unwrap(); + assert_eq!( + to_str(&vm, r), + "true|[\"empty next\",\"first next\",\"third return\",\"second return\"]", + "longest abrupt: done slot not closed, rest closed in reverse, original wins" + ); +} + +#[test] +fn iterator_zip_padding_precompute_abrupt() { + // padding 预计算骤发(GetIterator 与 next/done/value getter):逆序关元素 + // 迭代器,padding 迭代器不关,透传原值。 + let mut vm = Vm::new(); + // GetIterator 骤发(Symbol.iterator 抛错)。 + let r = eval( + &mut vm, + "(() => { + var log = []; + var first = { next() { log.push('unexpected next'); }, + return() { log.push('first return'); throw new ReferenceError('x'); } }; + var second = { next() { log.push('unexpected next'); }, + return() { log.push('second return'); throw new ReferenceError('y'); } }; + var third = { next() { log.push('unexpected next'); }, + get return() { log.push('third return'); throw new ReferenceError('z'); } }; + var padding = { [Symbol.iterator]() { throw new RangeError('boom'); } }; + var threw = false; + try { Iterator.zip([first, second, third], {mode: 'longest', padding}); } + catch (e) { threw = e instanceof RangeError; } + return threw + '|' + JSON.stringify(log); + })()", + ) + .unwrap(); + assert_eq!( + to_str(&vm, r), + "true|[\"third return\",\"second return\",\"first return\"]", + "padding GetIterator abrupt: close element iterators in reverse, padding not closed" + ); + // 步进骤发(next 抛错 / done getter 抛错 / value getter 抛错)。 + let r = eval( + &mut vm, + "(() => { + var log = []; + function makeElements() { + return [ + { next() { log.push('unexpected next'); }, + return() { log.push('first return'); throw new ReferenceError('x'); } }, + { next() { log.push('unexpected next'); }, + return() { log.push('second return'); throw new ReferenceError('y'); } }, + { next() { log.push('unexpected next'); }, + get return() { log.push('third return'); throw new ReferenceError('z'); } } + ]; + } + var padding = { [Symbol.iterator]() { return this; }, + next() { throw new RangeError('boom'); } }; + var threw = false; + try { Iterator.zip(makeElements(), {mode: 'longest', padding}); } + catch (e) { threw = e instanceof RangeError; } + var l1 = JSON.stringify(log); + log.length = 0; + var padding2 = { [Symbol.iterator]() { return this; }, + next() { return { get done() { throw new RangeError('boom'); } }; } }; + try { Iterator.zip(makeElements(), {mode: 'longest', padding: padding2}); } + catch (e) { threw = threw && e instanceof RangeError; } + var l2 = JSON.stringify(log); + log.length = 0; + var padding3 = { [Symbol.iterator]() { return this; }, + next() { return { done: false, get value() { throw new RangeError('boom'); } }; } }; + try { Iterator.zip(makeElements(), {mode: 'longest', padding: padding3}); } + catch (e) { threw = threw && e instanceof RangeError; } + var l3 = JSON.stringify(log); + return threw + '|' + l1 + '|' + l2 + '|' + l3; + })()", + ) + .unwrap(); + assert_eq!( + to_str(&vm, r), + "true|[\"third return\",\"second return\",\"first return\"]|[\"third return\",\"second return\",\"first return\"]|[\"third return\",\"second return\",\"first return\"]", + "padding step abrupt (next/done/value): close element iterators in reverse, original wins" + ); +} + +#[test] +fn iterator_zip_padding_not_exhausted_close() { + // padding 未耗尽收口:正常关 padding 迭代器,关闭骤发逆序关元素迭代器透传。 + let mut vm = Vm::new(); + let r = eval( + &mut vm, + "(() => { + var log = []; + var first = { next() { log.push('unexpected next'); }, + return() { log.push('first return'); throw new ReferenceError('x'); } }; + var second = { next() { log.push('unexpected next'); }, + return() { log.push('second return'); throw new ReferenceError('y'); } }; + var third = { next() { log.push('unexpected next'); }, + get return() { log.push('third return'); throw new ReferenceError('z'); } }; + var padding = { [Symbol.iterator]() { return this; }, + next() { return {done: false}; }, + return() { log.push('padding return'); throw new RangeError('boom'); } }; + var threw = false; + try { Iterator.zip([first, second, third], {mode: 'longest', padding}); } + catch (e) { threw = e instanceof RangeError; } + return threw + '|' + JSON.stringify(log); + })()", + ) + .unwrap(); + assert_eq!( + to_str(&vm, r), + "true|[\"padding return\",\"third return\",\"second return\",\"first return\"]", + "padding not exhausted: normal close, abrupt close passes through element iterators" + ); + // 耗尽:不关 padding 迭代器。 + let r = eval( + &mut vm, + "(() => { + var rets = 0; + var padding = { [Symbol.iterator]() { return this; }, + next() { if (this.v === undefined) { this.v = 1; return {done: false, value: 'p0'}; } return {done: true}; }, + return() { rets++; return {done: true}; } }; + var it = Iterator.zip([[1,2],[3]], {mode: 'longest', padding}); + var r1 = it.next(); var r2 = it.next(); + return r1.value[0] === 1 && r1.value[1] === 3 && r2.value[0] === 2 \ + && r2.value[1] === undefined && rets === 0 && it.next().done === true; + })()", + ) + .unwrap(); + assert!(r.as_bool(), "padding exhausted exactly at iterCount: no close, done landing undefined"); +}