diff --git a/Cargo.lock b/Cargo.lock index 56fa8ee5..93ad4dc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.19.24" +version = "0.19.26" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.19.24" +version = "0.19.26" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.19.24" +version = "0.19.26" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.19.24" +version = "0.19.26" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.19.24" +version = "0.19.26" dependencies = [ "lru", "oxide_bytecode", @@ -1237,7 +1237,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.19.24" +version = "0.19.26" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1258,7 +1258,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.19.24" +version = "0.19.26" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1272,7 +1272,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.19.24" +version = "0.19.26" dependencies = [ "num-bigint", "oxc_allocator", @@ -1288,7 +1288,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.19.24" +version = "0.19.26" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "oxide_fold" -version = "0.19.24" +version = "0.19.26" dependencies = [ "num-bigint", "oxide_bytecode", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.19.24" +version = "0.19.26" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.19.24" +version = "0.19.26" dependencies = [ "bumpalo", "dashmap", @@ -1335,7 +1335,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.19.24" +version = "0.19.26" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.19.24" +version = "0.19.26" dependencies = [ "tracing", "tracing-appender", @@ -1356,11 +1356,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.19.24" +version = "0.19.26" [[package]] name = "oxide_parser" -version = "0.19.24" +version = "0.19.26" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1375,7 +1375,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.19.24" +version = "0.19.26" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.19.24" +version = "0.19.26" dependencies = [ "num-bigint", "num-traits", @@ -1404,7 +1404,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.19.24" +version = "0.19.26" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.19.24" +version = "0.19.26" dependencies = [ "bumpalo", "dashmap", @@ -1437,7 +1437,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.19.24" +version = "0.19.26" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index e822f499..2c0e12a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ ] [workspace.package] -version = "0.19.25" +version = "0.19.26" 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 49bf20a2..91460a15 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -16,6 +16,10 @@ const COUNTER_PROP: &str = "__counter__"; const CALLBACK_PROP: &str = "__callback__"; const INNER_ITER_PROP: &str = "__inner_iter__"; const INNER_NEXT_PROP: &str = "__inner_next__"; +const ZIPS_PROP: &str = "__zips__"; +const OPEN_PROP: &str = "__open__"; +const RESULTS_PROP: &str = "__results__"; +const PADDING_PROP: &str = "__padding__"; /// `Iterator` 构造逻辑:不是构造函数,`new Iterator()` / `Iterator()` 均抛 TypeError; /// subclass `super()` 路径放行并返回 undefined(原型由调用方按 newTarget 设置)。 @@ -306,6 +310,7 @@ pub(crate) enum IteratorHelperKind { Take = 2, Drop = 3, FlatMap = 4, + Zip = 5, } impl IteratorHelperKind { @@ -316,6 +321,7 @@ impl IteratorHelperKind { 2 => Self::Take, 3 => Self::Drop, 4 => Self::FlatMap, + 5 => Self::Zip, _ => Self::Map, } } @@ -424,6 +430,56 @@ fn get_iterator_flattenable(vm: &mut H, value: JsValue) -> Result<(Js get_iterator_direct(vm, inner) } +/// 全 GetIterator(无鸭子 next 回退):`GetMethod(obj, @@iterator)` 不可调用抛 +/// TypeError,调用结果非对象抛 TypeError,再 GetIteratorDirect。 +/// +/// 不能复用 [`get_iterator`] / [`get_iterator_flattenable`]:两者均带鸭子 next +/// 回退,而 `iterables` 实参要求带可调用 `next` 但无 `@@iterator` 的对象必须 +/// 抛 TypeError。 +/// +/// # 边界与前提 +/// - `value` 必须是对象(调用方已校验)。 +/// - getter 抛错 / 调用抛错透传原异常值。 +/// +/// # 返回 +/// - `Ok((iter, next))`:迭代器 + GetIteratorDirect 缓存的 next。 +fn get_iterator_strict(vm: &mut H, 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) { + Ok(m) => m, + 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")); + } + 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", + )); + } + get_iterator_direct(vm, iter) +} + +/// GetOptionsObject:undefined 建 null 原型空对象,对象原样,其余抛 TypeError。 +fn get_options_object(vm: &mut H, options: JsValue) -> Result { + if options.is_undefined() { + let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + return Ok(JsValue::from_js_object(obj)); + } + if options.is_object() { + return Ok(options); + } + Err(crate::error::create_type_error(vm, "Iterator.zip options must be undefined or an object")) +} + +/// 逐字比较 UTF-16 单元串与 ASCII 文本(mode 严格字符串校验不走 ToPrimitive)。 +fn is_ascii_str(units: &[u16], text: &str) -> bool { + units.len() == text.len() && units.iter().zip(text.bytes()).all(|(u, b)| *u == b as u16) +} + /// 创建 Iterator helper 结果对象:空对象挂 `%IteratorHelperPrototype%`,写 /// `__inner__`/`__next__`/`__kind__`/`__state__`/`__counter__`/`__callback__` /// 槽;flatMap 的内层槽初始为 undefined。next/return/throw 挂在共享原型上, @@ -457,22 +513,39 @@ fn make_iterator_helper( JsValue::from_js_object(wrapper) } -/// 校验 wrapper 对象形态:`this` 非对象或非 helper wrapper(无 `__inner__` 槽) -/// → TypeError,等价规范的 RequireInternalSlot 检查。 +/// 校验 wrapper 对象形态并读出 kind:`this` 非对象 → TypeError;其余 kind 以 +/// `__inner__` 槽为对象作判据,Zip 以 `__zips__` 槽为对象作判据(zip wrapper +/// 无 `__inner__` 槽)。 /// /// # 返回 -/// - `Ok(inner)`:底层迭代器值(wrapper `__inner__` 槽)。 -fn validate_helper_this(vm: &mut H, this_val: JsValue, method: &str) -> Result { +/// - `Ok((kind, inner))`:kind 与底层值(其余 kind 为 `__inner__` 槽,Zip 为 +/// `__zips__` 槽);`Err` 为透传的异常值。 +fn validate_helper_this( + vm: &mut H, this_val: JsValue, method: &str, +) -> Result<(IteratorHelperKind, JsValue), JsValue> { if !this_val.is_object() { return Err(crate::error::create_type_error(vm, &format!("{method} called on non-object"))); } let obj = unsafe { &*this_val.as_js_object_ptr() }; + let kind_si = vm.perm_intern(KIND_PROP); + let kind = IteratorHelperKind::from_i32(read_slot_int(vm, obj, kind_si, 0)); + if kind == IteratorHelperKind::Zip { + let zips_si = vm.perm_intern(ZIPS_PROP); + let zips = vm.ordinary_get(obj, zips_si, this_val).map_err(|e| engine_error(vm, &e))?; + if !zips.is_object() { + return Err(crate::error::create_type_error( + vm, + &format!("{method} called on non-iterator-helper"), + )); + } + return Ok((kind, zips)); + } let inner_si = vm.perm_intern(INNER_PROP); let inner = vm.ordinary_get(obj, inner_si, this_val).map_err(|e| engine_error(vm, &e))?; if !inner.is_object() { return Err(crate::error::create_type_error(vm, &format!("{method} called on non-iterator-helper"))); } - Ok(inner) + Ok((kind, inner)) } // ── 终端 6 方法 ── @@ -844,7 +917,7 @@ pub fn iterator_flat_map(vm: &mut H, args: &[u8]) -> NativeResult { )) } -/// `%IteratorHelperPrototype%.next`:读 wrapper `__kind__` 分发 5 种推进循环, +/// `%IteratorHelperPrototype%.next`:读 wrapper `__kind__` 分发 6 种推进循环, /// 统一完成态短路与重入守卫。 /// /// # 步骤 @@ -854,8 +927,8 @@ pub fn iterator_flat_map(vm: &mut H, args: &[u8]) -> NativeResult { /// 后返回 done / 透传异常(规范生成器 body 终止即 completed,后续调用短路)。 pub fn iterator_helper_next(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); - let inner = match validate_helper_this(vm, this_val, "Iterator helper next") { - Ok(v) => v, + let (kind, inner) = match validate_helper_this(vm, this_val, "Iterator helper next") { + Ok(pair) => pair, Err(v) => return NativeResult::Err(v), }; let helper = unsafe { &mut *this_val.as_js_object_ptr() }; @@ -867,8 +940,6 @@ pub fn iterator_helper_next(vm: &mut H, args: &[u8]) -> NativeResult if state == 1 { return NativeResult::Err(crate::error::create_type_error(vm, "Iterator helper is already executing")); } - let kind_si = vm.perm_intern(KIND_PROP); - let kind = IteratorHelperKind::from_i32(read_slot_int(vm, helper, kind_si, 0)); vm.set_or_create_prop_value(helper, state_si, JsValue::int(1)); let next_si = vm.perm_intern(NEXT_CACHE_PROP); let next = vm.ordinary_get(helper, next_si, this_val).unwrap_or(JsValue::undefined()); @@ -878,10 +949,14 @@ pub fn iterator_helper_next(vm: &mut H, args: &[u8]) -> NativeResult IteratorHelperKind::Take => helper_step_take(vm, helper, inner, next), IteratorHelperKind::Drop => helper_step_drop(vm, helper, inner, next), IteratorHelperKind::FlatMap => helper_step_flat_map(vm, this_val, helper, inner, next), + IteratorHelperKind::Zip => helper_step_zip(vm, this_val, helper), }; match step { Ok(Some(value)) => { - vm.set_or_create_prop_value(helper, state_si, JsValue::int(0)); + // zip 产出后为 suspended-yield(state 3,return/throw 关闭期重入守卫 + // 需区分 suspended-start 的完成态短路);其余 kind 恒 0。 + let new_state = if kind == IteratorHelperKind::Zip { 3 } else { 0 }; + vm.set_or_create_prop_value(helper, state_si, JsValue::int(new_state)); NativeResult::Ok(make_iter_result(vm, value, false)) } Ok(None) => { @@ -905,8 +980,8 @@ pub fn iterator_helper_next(vm: &mut H, args: &[u8]) -> NativeResult /// 关闭抛错 → 传播,此后 next/return/throw 全部短路。 pub fn iterator_helper_return(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); - let inner = match validate_helper_this(vm, this_val, "Iterator helper return") { - Ok(v) => v, + let (kind, inner) = match validate_helper_this(vm, this_val, "Iterator helper return") { + Ok(pair) => pair, Err(v) => return NativeResult::Err(v), }; let helper = unsafe { &mut *this_val.as_js_object_ptr() }; @@ -919,6 +994,27 @@ pub fn iterator_helper_return(vm: &mut H, args: &[u8]) -> NativeResul return NativeResult::Err(crate::error::create_type_error(vm, "Iterator helper is already executing")); } vm.set_or_create_prop_value(helper, state_si, JsValue::int(2)); + if kind == IteratorHelperKind::Zip { + // suspended-start 关全部前已置完成(嵌套调用短路);suspended-yield 关 + // 全部前置执行中(嵌套调用 TypeError),关闭后回落完成。 + let close_state = if state == 0 { 2 } else { 1 }; + vm.set_or_create_prop_value(helper, state_si, JsValue::int(close_state)); + let zips_si = vm.perm_intern(ZIPS_PROP); + let zips_val = vm.ordinary_get(helper, zips_si, this_val).unwrap_or(JsValue::undefined()); + let iter_count = if zips_val.is_object() { + unsafe { &*zips_val.as_js_object_ptr() }.prop_count() + } else { + 0 + }; + let open_si = vm.perm_intern(OPEN_PROP); + let open = read_slot_int(vm, helper, open_si, -1); + let result = match close_remaining_open(vm, this_val, helper, open, iter_count, None) { + Ok(()) => NativeResult::Ok(make_iter_result(vm, JsValue::undefined(), true)), + Err(v) => NativeResult::Err(v), + }; + vm.set_or_create_prop_value(helper, state_si, JsValue::int(2)); + return result; + } let kind_si = vm.perm_intern(KIND_PROP); if IteratorHelperKind::from_i32(read_slot_int(vm, helper, kind_si, 0)) == IteratorHelperKind::FlatMap { let inner_iter_si = vm.perm_intern(INNER_ITER_PROP); @@ -947,8 +1043,8 @@ pub fn iterator_helper_return(vm: &mut H, args: &[u8]) -> NativeResul pub fn iterator_helper_throw(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - let inner = match validate_helper_this(vm, this_val, "Iterator helper throw") { - Ok(v) => v, + let (kind, inner) = match validate_helper_this(vm, this_val, "Iterator helper throw") { + Ok(pair) => pair, Err(v) => return NativeResult::Err(v), }; let helper = unsafe { &mut *this_val.as_js_object_ptr() }; @@ -961,6 +1057,24 @@ pub fn iterator_helper_throw(vm: &mut H, args: &[u8]) -> NativeResult return NativeResult::Err(crate::error::create_type_error(vm, "Iterator helper is already executing")); } vm.set_or_create_prop_value(helper, state_si, JsValue::int(2)); + if kind == IteratorHelperKind::Zip { + // 同 return 臂:suspended-start 置完成后关全部,suspended-yield 置执行中 + // 后关全部,原值恒胜出。 + let close_state = if state == 0 { 2 } else { 1 }; + vm.set_or_create_prop_value(helper, state_si, JsValue::int(close_state)); + let zips_si = vm.perm_intern(ZIPS_PROP); + let zips_val = vm.ordinary_get(helper, zips_si, this_val).unwrap_or(JsValue::undefined()); + let iter_count = if zips_val.is_object() { + unsafe { &*zips_val.as_js_object_ptr() }.prop_count() + } else { + 0 + }; + let open_si = vm.perm_intern(OPEN_PROP); + let open = read_slot_int(vm, helper, open_si, -1); + let _ = close_remaining_open(vm, this_val, helper, open, iter_count, Some(value)); + vm.set_or_create_prop_value(helper, state_si, JsValue::int(2)); + return NativeResult::Err(value); + } let kind_si = vm.perm_intern(KIND_PROP); if IteratorHelperKind::from_i32(read_slot_int(vm, helper, kind_si, 0)) == IteratorHelperKind::FlatMap { let inner_iter_si = vm.perm_intern(INNER_ITER_PROP); @@ -1144,6 +1258,316 @@ fn helper_step_flat_map( } } +// ── Iterator.zip(构造期 + shortest 模式迭代期)── + +/// `Iterator.zip(iterables, options)`:构造期按规范顺序执行——先校验 +/// `iterables` 对象、读 options(mode 严格字符串校验,longest 时读 padding), +/// 再全 GetIterator 取 `inputIter`,元素收集循环(IteratorStepValue 骤发逆序关 +/// 已收集元素迭代器;元素经 GetIteratorFlattenable 骤发逆序关含 inputIter), +/// 最后建 zip wrapper。 +/// +/// # 步骤 +/// 1. `iterables` 非对象 → TypeError(options 不读)。 +/// 2. GetOptionsObject;`mode` 读后严格字符串比较(undefined 落 shortest, +/// String 包装对象拒绝、不触发 toPrimitive,无效 mode 不读 padding)。 +/// 3. longest 模式读 `padding`(undefined 或对象,其余 TypeError)。 +/// 4. GetIterator(无鸭子回退)取 inputIter;元素收集循环骤发逆序关后透传。 +/// 5. 建 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() }; + if !iterables.is_object() { + return NativeResult::Err(crate::error::create_type_error(vm, "Iterator.zip 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.zip mode must be \"shortest\", \"longest\", or \"strict\"", + )); + } + } else { + return NativeResult::Err(crate::error::create_type_error( + vm, + "Iterator.zip 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.zip padding must be undefined or an object")); + } + p + } else { + JsValue::undefined() + }; + let (input_iter, input_next) = match get_iterator_strict(vm, iterables) { + Ok(pair) => pair, + Err(v) => return NativeResult::Err(v), + }; + let mut zips: Vec<(JsValue, JsValue)> = Vec::new(); + loop { + match iterator_record_step(vm, input_next, input_iter) { + Ok(None) => break, + Ok(Some(value)) => match get_iterator_flattenable(vm, value) { + Ok(pair) => zips.push(pair), + Err(v) => { + // 元素骤发:逆序关 inputIter + 已收集(inputIter 在列表首位,最后关)。 + close_zip_pairs(vm, &zips, Some(input_iter), Some(v)); + return NativeResult::Err(v); + } + }, + Err(v) => { + // 步进骤发:只关已收集,inputIter 不关。 + close_zip_pairs(vm, &zips, None, Some(v)); + return NativeResult::Err(v); + } + } + } + NativeResult::Ok(make_iterator_zip_helper(vm, &zips, mode, padding)) +} + +/// 建 zip wrapper:空对象挂 `%IteratorHelperPrototype%`,写 `__zips__`(元素 +/// 迭代器对数组,每元素为 [iter, next] 对)、`__open__`(位掩码,初始全 1)、 +/// `__mode__`(0/1/2)、`__state__`(0)、`__kind__`(5)、`__padding__` 槽。 +/// +/// 独立构造器(不复用 [`make_iterator_helper`]):槽位布局不同,zip 无 +/// `__inner__`/`__next__` 槽。 +fn make_iterator_zip_helper( + vm: &mut H, zips: &[(JsValue, JsValue)], mode: i32, padding: 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))); + let wrapper_obj = unsafe { &mut *wrapper }; + let array_proto = vm.builtin_proto(ProtoKind::ArrayProto); + let zips_arr = vm.alloc_object(JsObject::new_array( + EMPTY_SHAPE_ID, + JsValue::from_js_object(array_proto), + zips.len(), + )); + for (i, (iter, next)) in zips.iter().enumerate() { + let pair = vm.alloc_object(JsObject::new_array( + EMPTY_SHAPE_ID, + JsValue::from_js_object(array_proto), + 2, + )); + // SAFETY: pair 是刚经统一入口分配的数组对象,元素区已就绪。 + unsafe { + (*pair).set_prop_at(0, *iter); + (*pair).set_prop_at(1, *next); + } + // SAFETY: 同 epoch,写入后按实际元素数设 prop_count。 + unsafe { + (*zips_arr).set_prop_at(i, JsValue::from_js_object(pair)); + } + } + // SAFETY: 与 new_array 同 epoch,写入后按实际元素数设 prop_count。 + unsafe { + (*zips_arr).set_prop_count(zips.len()); + } + let zips_si = vm.perm_intern(ZIPS_PROP); + let open_si = vm.perm_intern(OPEN_PROP); + let mode_si = vm.perm_intern(MODE_PROP); + let state_si = vm.perm_intern(STATE_PROP); + let kind_si = vm.perm_intern(KIND_PROP); + let padding_si = vm.perm_intern(PADDING_PROP); + vm.set_or_create_prop_value(wrapper_obj, zips_si, JsValue::from_js_object(zips_arr)); + vm.set_or_create_prop_value(wrapper_obj, open_si, JsValue::int(-1)); + vm.set_or_create_prop_value(wrapper_obj, mode_si, JsValue::int(mode)); + 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); + JsValue::from_js_object(wrapper) +} + +/// 位掩码第 i 位判定开放(i32 掩码 32 位以上恒视为开放,超 32 迭代器降级)。 +fn zip_bit_open(mask: i32, i: u32) -> bool { + if i < 32 { + ((mask >> i) & 1) == 1 + } else { + true + } +} + +/// 清位掩码第 i 位(32 位以上无位可清,原样返回)。 +fn zip_bit_clear(mask: i32, i: u32) -> i32 { + if i < 32 { + mask & !(1 << i) + } else { + mask + } +} + +/// 逆序 IteratorCloseAll:按位掩码对开放迭代器逆下标逐个关闭,关闭错误链式 +/// 升级 completion(throw 完成原值恒胜出)。 +fn close_remaining_open( + vm: &mut H, this_val: JsValue, helper: &mut JsObject, open_mask: i32, iter_count: u32, + completion_err: Option, +) -> Result<(), JsValue> { + let mut completion_err = completion_err; + for i in (0..iter_count).rev() { + if !zip_bit_open(open_mask, i) { + continue; + } + // 每次用户调用窗口后重读槽位(回调可改写 wrapper 槽)。 + let zips_si = vm.perm_intern(ZIPS_PROP); + let zips_val = vm.ordinary_get(helper, zips_si, this_val).unwrap_or(JsValue::undefined()); + if !zips_val.is_object() { + continue; + } + let zips_obj = unsafe { &*zips_val.as_js_object_ptr() }; + let pair_val = zips_obj.get_prop_at(i); + if !pair_val.is_object() { + continue; + } + let pair_obj = unsafe { &*pair_val.as_js_object_ptr() }; + let iter = pair_obj.get_prop_at(0); + if !iter.is_object() { + continue; + } + if let Err(v) = iterator_close_record(vm, iter, completion_err) { + completion_err = Some(v); + } + } + match completion_err { + Some(v) => Err(v), + None => Ok(()), + } +} + +/// zip 元素收集期骤发关闭:逆序关 `tail`(inputIter,`Some` 时)加已收集元素 +/// 迭代器(逆序),throw 完成原值恒胜出。 +fn close_zip_pairs( + vm: &mut H, zips: &[(JsValue, JsValue)], input_iter: Option, completion_err: Option, +) { + let mut completion_err = completion_err; + for (iter, _) in zips.iter().rev() { + if let Err(v) = iterator_close_record(vm, *iter, completion_err) { + completion_err = Some(v); + } + } + if let Some(input) = input_iter { + let _ = iterator_close_record(vm, input, completion_err); + } +} + +/// zip 推进(shortest 臂加 mode 分派骨架):升序步进开放迭代器,收集到钉在 +/// `__results__` 槽的新数组;done 清位后 shortest 逆序关其余开放迭代器(正常 +/// 完成)收口;abrupt 清位后逆序关其余(原值胜出)透传;longest/strict 臂 +/// 未实现,此处按 longest 的 undefined 填充语义降级保 wrapper 健壮。 +fn helper_step_zip( + vm: &mut H, this_val: JsValue, helper: &mut JsObject, +) -> Result, JsValue> { + let zips_si = vm.perm_intern(ZIPS_PROP); + let zips_val = vm.ordinary_get(helper, zips_si, this_val).unwrap_or(JsValue::undefined()); + if !zips_val.is_object() { + return Ok(None); + } + let zips_obj = unsafe { &*zips_val.as_js_object_ptr() }; + let iter_count = zips_obj.prop_count(); + if iter_count == 0 { + return Ok(None); + } + let open_si = vm.perm_intern(OPEN_PROP); + let mut open = read_slot_int(vm, helper, open_si, -1); + if !zip_bit_open(open, 0) && (0..iter_count).all(|i| !zip_bit_open(open, i)) { + return Ok(None); + } + let mode_si = vm.perm_intern(MODE_PROP); + let mode = read_slot_int(vm, helper, mode_si, 0); + 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); + let results_si = vm.perm_intern(RESULTS_PROP); + vm.set_or_create_prop_value(helper, results_si, arr_val); + let mut arr_len = 0usize; + for i in 0..iter_count { + if !zip_bit_open(open, i) { + continue; + } + // 每次用户调用窗口后重读槽位(回调可改写 wrapper 槽)。 + let pair_val = zips_obj.get_prop_at(i); + if !pair_val.is_object() { + continue; + } + let pair_obj = unsafe { &*pair_val.as_js_object_ptr() }; + let iter = pair_obj.get_prop_at(0); + 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; + } + Ok(None) => { + // 直接重赋值外层掩码:本臂不返回时后续迭代器的清位须在此基础上 + // 链式叠加,遮蔽新绑定会丢失先前清位。 + open = zip_bit_clear(open, i); + vm.set_or_create_prop_value(helper, open_si, JsValue::int(open)); + vm.set_or_create_prop_value(helper, results_si, JsValue::undefined()); + if mode == 0 { + // shortest:done 迭代器不关,逆序关其余开放迭代器(正常完成)。 + return match close_remaining_open(vm, this_val, helper, open, iter_count, None) { + Ok(()) => Ok(None), + Err(v) => Err(v), + }; + } + // longest/strict 臂未实现:按 longest 的 undefined 填充语义降级。 + if !zip_bit_open(open, 0) && (0..iter_count).all(|k| !zip_bit_open(open, k)) { + return Ok(None); + } + // SAFETY: 同上一分支。 + unsafe { + (*arr).set_prop_at(arr_len, JsValue::undefined()); + } + arr_len += 1; + } + Err(v) => { + open = zip_bit_clear(open, i); + vm.set_or_create_prop_value(helper, open_si, JsValue::int(open)); + vm.set_or_create_prop_value(helper, results_si, JsValue::undefined()); + let _ = close_remaining_open(vm, this_val, helper, open, iter_count, Some(v)); + return Err(v); + } + } + } + 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)) +} + /// `%IteratorPrototype%[@@iterator]`:返回 this(迭代器对象自迭代)。 /// /// 挂在 `%IteratorPrototype%` 上,所有集合迭代器经原型链继承,保证 diff --git a/crates/oxide_vm/src/bindings/bind_iterator.rs b/crates/oxide_vm/src/bindings/bind_iterator.rs index 724b2bca..932461ac 100644 --- a/crates/oxide_vm/src/bindings/bind_iterator.rs +++ b/crates/oxide_vm/src/bindings/bind_iterator.rs @@ -7,7 +7,7 @@ use crate::bindings::{ configure_native_constructor, BindEnv, }; -/// 绑定 `Iterator` 全局构造器(`prototype` = %IteratorPrototype%、`from` 方法)。 +/// 绑定 `Iterator` 全局构造器(`prototype` = %IteratorPrototype%、`from`/`zip` 方法)。 /// /// 迭代器原型方法(%IteratorPrototype% 的 `@@iterator` 与各集合原型 `next`)由 /// `rebind_dirty_builtins` 的 object 分支统一安装,本入口不重复绑定,避免 @@ -27,7 +27,10 @@ pub fn bind_iterator(env: BindEnv<'_>, global: &mut JsObject) { apply_binding_table( env, &mut iterator, - &[("from", oxide_builtins::iterator::iterator_from:: as *const (), 1)], + &[ + ("from", oxide_builtins::iterator::iterator_from:: as *const (), 1), + ("zip", oxide_builtins::iterator::iterator_zip:: as *const (), 1), + ], ); let iterator_ptr = Box::into_raw(iterator); diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index e1238053..23df66f1 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -963,7 +963,10 @@ fn bind_iterator_global(env: BindEnv<'_>, global: &mut JsObject) { apply_binding_table( env, &mut iterator, - &[("from", oxide_builtins::iterator::iterator_from:: as *const (), 1)], + &[ + ("from", oxide_builtins::iterator::iterator_from:: as *const (), 1), + ("zip", oxide_builtins::iterator::iterator_zip:: as *const (), 1), + ], ); let iterator_ptr = Box::into_raw(iterator); env.world().track_leaked_object(iterator_ptr); diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index 194e108c..23628efd 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -577,12 +577,21 @@ impl Vm { callee_mut.set_prop_at(slot, JsValue::undefined()); } } - // 弹帧后活动镜像切回新栈顶的表(帧空即顶层空切片)。 - self.active_upvalues = self - .frames - .last() - .map(|f| f.upvalues) - .unwrap_or(std::ptr::slice_from_raw_parts(std::ptr::null(), 0)); + // 弹帧后活动镜像切回新栈顶的表(帧空即顶层空切片)。内联执行期间弹回 + // 基线时新栈顶不是内联函数本身(它无帧),须恢复内联目标函数的 upvalue + // 表,否则其闭包捕获继续读外层帧的表,读到错 cell。 + let inline_upvalues = self.inline_callee.as_ref().map(|callee| { + let obj = unsafe { &*callee.as_js_object_ptr() }; + obj.upvalues_slice() as *const [*mut Cell] + }); + self.active_upvalues = match (inline_upvalues, self.frames.len() <= self.inline_frames_base) { + (Some(up), true) => up, + _ => self + .frames + .last() + .map(|f| f.upvalues) + .unwrap_or(std::ptr::slice_from_raw_parts(std::ptr::null(), 0)), + }; if let Some(saved_bc) = self.saved_bytecode_stack.pop() { self.bytecode = saved_bc; } diff --git a/crates/oxide_vm/tests/builtins_iterator_tests.rs b/crates/oxide_vm/tests/builtins_iterator_tests.rs index c6c46af7..9c1b9a78 100644 --- a/crates/oxide_vm/tests/builtins_iterator_tests.rs +++ b/crates/oxide_vm/tests/builtins_iterator_tests.rs @@ -2325,3 +2325,387 @@ fn typed_array_from_throws_when_iterator_not_callable() { .unwrap(); assert_eq!(to_str(&vm, r), "TypeError|TypeError"); } + +#[test] +fn iterator_zip_binding_surface() { + // 绑定面:name/length/原型/描述符/非构造/instanceof/结果原型。 + let mut vm = Vm::new(); + let bool_cases = [ + ( + "typeof Iterator.zip === 'function' && Iterator.zip.name === 'zip' \ + && Iterator.zip.length === 1", + true, + ), + ( + "Object.getPrototypeOf(Iterator.zip) === Function.prototype", + true, + ), + ( + "var d = Object.getOwnPropertyDescriptor(Iterator, 'zip'); \ + d.writable === true && d.enumerable === false && d.configurable === true", + true, + ), + ( + "var ok = false; try { new Iterator.zip([]); } catch (e) { ok = e instanceof TypeError; } ok", + true, + ), + ( + "var it = Iterator.zip([]); it instanceof Iterator", + true, + ), + ( + "var it = Iterator.zip([]); var p = Object.getPrototypeOf(it); \ + p[Symbol.toStringTag] === 'Iterator Helper' \ + && Object.getPrototypeOf(p) === Iterator.prototype", + 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_options_and_mode_validation() { + // options 原始值 TypeError;iterables 原始值 TypeError 且 options 不读; + // mode 严格字符串校验(String 包装拒绝、toPrimitive 不触发、无效 mode 不读 padding)。 + let mut vm = Vm::new(); + let bool_cases = [ + ( + "Iterator.zip([], undefined) instanceof Iterator && Iterator.zip([], {}) instanceof Iterator", + true, + ), + ( + "var ok = false; try { Iterator.zip([], null); } catch (e) { ok = e instanceof TypeError; } ok", + true, + ), + ( + "var ok = false; try { Iterator.zip(1); } catch (e) { ok = e instanceof TypeError; } ok", + true, + ), + ( + "var badOptions = { get mode() { throw new RangeError('x'); } }; \ + var ok = false; try { Iterator.zip(1, badOptions); } catch (e) { ok = e instanceof TypeError; } ok", + true, + ), + ( + "var ok = false; try { Iterator.zip([], {mode: 'short'}); } catch (e) { ok = e instanceof TypeError; } ok", + true, + ), + ( + "var ok = false; try { Iterator.zip([], {mode: new String('shortest')}); } \ + catch (e) { ok = e instanceof TypeError; } ok", + true, + ), + ( + "var ok = false; try { Iterator.zip([], {mode: {toString() { throw new RangeError('x'); }, \ + valueOf() { throw new RangeError('y'); }, [Symbol.toPrimitive]() { throw new RangeError('z'); }}}); } \ + catch (e) { ok = e instanceof TypeError; } ok", + true, + ), + ( + "var opts = { mode: 'bad', get padding() { throw new RangeError('x'); } }; \ + var ok = false; try { Iterator.zip([], opts); } catch (e) { ok = e instanceof TypeError; } ok", + true, + ), + ( + "Iterator.zip([], {mode: 'longest', padding: []}) instanceof Iterator \ + && Iterator.zip([], {mode: 'strict'}) instanceof Iterator", + true, + ), + ( + "var ok = false; try { Iterator.zip([], {mode: 'longest', padding: 42}); } \ + 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_iterables_validation() { + // iterables 全 GetIterator 无鸭子回退;字符串对象经 @@iterator 接受,字符串原始值拒绝。 + let mut vm = Vm::new(); + let bool_cases = [ + ( + "var ok = false; try { Iterator.zip(Object.create(null)); } \ + catch (e) { ok = e instanceof TypeError; } ok", + true, + ), + ( + "var ok = false; try { Iterator.zip(Object.create(null, \ + {next: {value: function(){}}, return: {value: function(){}}})); } \ + catch (e) { ok = e instanceof TypeError; } ok", + true, + ), + ( + "var r = Array.from(Iterator.zip([Object('abc'), Object('123')])); \ + r.length === 3 && r[0][0] === 'a' && r[0][1] === '1' && r[2][0] === 'c' && r[2][1] === '3'", + true, + ), + ( + "var ok = false; try { Iterator.zip([['a','b'], 'cd']); } 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_collection_abrupt_close_order() { + // 收集期骤发:步进骤发只关已收集(inputIter 不关);元素骤发逆序关含 inputIter。 + let mut vm = Vm::new(); + let r = eval( + &mut vm, + "(() => { + var log = []; + var first = { next() { log.push('unexpected next'); }, + return() { log.push('close first'); throw new RangeError('x'); } }; + var second = { next() { log.push('unexpected next'); }, + return() { log.push('close second'); throw new RangeError('y'); } }; + var elements = [first, second]; + var elementsIter = elements.values(); + var iterables = { [Symbol.iterator]() { return this; }, + next() { var r = elementsIter.next(); if (r.done) { throw new ReferenceError('boom'); } return r; }, + return() { log.push('close iterables'); throw new RangeError('z'); } }; + var threw = false; + try { Iterator.zip(iterables); } catch (e) { threw = e instanceof ReferenceError; } + return threw + '|' + JSON.stringify(log); + })()", + ) + .unwrap(); + assert_eq!( + to_str(&vm, r), + "true|[\"close second\",\"close first\"]", + "step abrupt closes collected only, inputIter untouched" + ); + let r = eval( + &mut vm, + "(() => { + var log = []; + var first = { next() {}, return() { log.push('close first'); throw new RangeError('x'); } }; + var second = { next() {}, return() { log.push('close second'); throw new RangeError('y'); } }; + var bad = { [Symbol.iterator]() { throw new ReferenceError('boom'); } }; + var elements = [first, second, bad]; + var elementsIter = elements.values(); + var iterables = { [Symbol.iterator]() { return this; }, + next() { return elementsIter.next(); }, + return() { log.push('close iterables'); throw new RangeError('z'); } }; + var threw = false; + try { Iterator.zip(iterables); } catch (e) { threw = e instanceof ReferenceError; } + return threw + '|' + JSON.stringify(log); + })()", + ) + .unwrap(); + assert_eq!( + to_str(&vm, r), + "true|[\"close second\",\"close first\",\"close iterables\"]", + "flattenable abrupt closes inputIter last in reverse order" + ); +} + +#[test] +fn iterator_zip_shortest_basic_values() { + // shortest 基本值、done、空迭代器、单迭代器、短输入收口、结果数组逐次新对象。 + let mut vm = Vm::new(); + let bool_cases = [ + ( + "var it = Iterator.zip([[1,2,3],[4,5,6]]); \ + var r1 = it.next(); var r2 = it.next(); var r3 = it.next(); var r4 = it.next(); \ + r1.value[0] === 1 && r1.value[1] === 4 && r2.value[0] === 2 && r2.value[1] === 5 \ + && r3.value[0] === 3 && r3.value[1] === 6 && r4.done === true", + true, + ), + ( + "var it = Iterator.zip([[1,2,3],[4,5,6]]); \ + var a = it.next().value; var b = it.next().value; a !== b", + true, + ), + ("Iterator.zip([]).next().done === true", true), + ( + "var it = Iterator.zip([[1,2]]); var r = it.next(); \ + r.value.length === 1 && r.value[0] === 1 && it.next().value[0] === 2 && it.next().done === true", + true, + ), + ( + "var it = Iterator.zip([[1,2],[3,4,5]]); \ + it.next().value[0] === 1 && it.next().value[0] === 2 && it.next().done === true", + 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_shortest_done_close_order() { + // shortest done: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'); return {}; } }; + 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 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: 'shortest'}); + var threw = false; + try { it.next(); } catch (e) { threw = e instanceof ReferenceError; } + return threw + '|' + JSON.stringify(log); + })()", + ) + .unwrap(); + assert_eq!( + to_str(&vm, r), + "true|[\"first next\",\"second next\",\"fourth return\",\"third return\",\"first return\"]", + "done iterator stays open, others closed in reverse index order" + ); +} + +#[test] +fn iterator_zip_abrupt_close_all_original_wins() { + // 步进骤发与 return 关闭:逆序关全部开放迭代器,原值恒胜出。 + 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: false}; }, + return() { log.push('second return'); throw new ReferenceError('boom'); } }; + var third = { next() { log.push('third next'); return {done: false}; }, + return() { log.push('third return'); return {}; } }; + var it = Iterator.zip([first, second, third]); + it.next(); + var threw = false; + try { it.return(); } catch (e) { threw = e instanceof ReferenceError; } + return threw + '|' + JSON.stringify(log); + })()", + ) + .unwrap(); + assert_eq!( + to_str(&vm, r), + "true|[\"first next\",\"second next\",\"third next\",\"third return\",\"second return\",\"first return\"]", + "return closes all open iterators in reverse order, original value wins" + ); + let r = eval( + &mut vm, + "(() => { + var closed = 0; + var underlying = { next() { return {value: 1, done: false}; }, + return() { closed++; return {}; } }; + var it = Iterator.zip([underlying]); + it.next(); + var ok = false; + try { it.throw(42); } catch (e) { ok = e === 42; } + return ok && closed === 1 && it.next().done === true; + })()", + ) + .unwrap(); + assert!(r.as_bool(), "throw closes all open iterators then rethrows value"); +} + +#[test] +fn iterator_zip_suspended_start_return() { + // suspended-start return:逆序关全部底层,next 不转发,嵌套调用短路。 + let mut vm = Vm::new(); + let r = eval( + &mut vm, + "(() => { + var returnCallCount = 0; + var underlying = { next() { throw new RangeError('unexpected next'); }, + return() { + returnCallCount += 1; + var result = it.next(); + if (result.value !== undefined || result.done !== true) { throw new RangeError('bad nested next'); } + return {}; + } }; + var it = Iterator.zip([underlying]); + var result = it.return(); + return result.value === undefined && result.done === true && returnCallCount === 1; + })()", + ) + .unwrap(); + assert!(r.as_bool(), "suspended-start return closes all underlying iterators"); + let r = eval( + &mut vm, + "(() => { + var returnCallCount = 0; + var underlying = { next() { throw new RangeError('unexpected next'); }, + return() { + returnCallCount += 1; + var result = it.return(); + if (result.value !== undefined || result.done !== true) { throw new RangeError('bad nested return'); } + return {}; + } }; + var it = Iterator.zip([underlying]); + var result = it.return(); + return result.value === undefined && result.done === true && returnCallCount === 1; + })()", + ) + .unwrap(); + assert!(r.as_bool(), "suspended-start return with nested return short-circuits to done"); +} + +#[test] +fn iterator_zip_reentrancy_guard() { + // executing 重入守卫:底层 return 回调内再调 next/return → TypeError。 + let mut vm = Vm::new(); + let r = eval( + &mut vm, + "(() => { + var returnCallCount = 0; + var underlying = { next() { return {value: 123, done: false}; }, + return() { + returnCallCount += 1; + var ok = false; + try { it.next(); } catch (e) { ok = e instanceof TypeError; } + if (!ok) { throw new RangeError('no TypeError'); } + return {}; + } }; + var it = Iterator.zip([underlying]); + var r = it.next(); + if (r.value[0] !== 123) { throw new RangeError('bad value'); } + var result = it.return(); + return result.done === true && returnCallCount === 1; + })()", + ) + .unwrap(); + assert!(r.as_bool(), "executing reentry on next throws TypeError"); + let r = eval( + &mut vm, + "(() => { + var returnCallCount = 0; + var underlying = { next() { return {value: 123, done: false}; }, + return() { + returnCallCount += 1; + var ok = false; + try { it.return(); } catch (e) { ok = e instanceof TypeError; } + if (!ok) { throw new RangeError('no TypeError'); } + return {}; + } }; + var it = Iterator.zip([underlying]); + var r = it.next(); + if (r.value[0] !== 123) { throw new RangeError('bad value'); } + var result = it.return(); + return result.done === true && returnCallCount === 1; + })()", + ) + .unwrap(); + assert!(r.as_bool(), "executing reentry on return throws TypeError"); +}