From ec32e42ce568ad00e332106969d702f103339d1a Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 15 Aug 2026 22:57:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=BF=AD=E4=BB=A3=E5=99=A8=E5=8E=9F?= =?UTF-8?q?=E5=9E=8B=E5=9F=BA=E5=BB=BA=EF=BC=9A%IteratorPrototype%=20?= =?UTF-8?q?=E9=93=BE=20+=20=E5=90=84=E9=9B=86=E5=90=88=E8=BF=AD=E4=BB=A3?= =?UTF-8?q?=E5=99=A8=E5=8E=9F=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 %IteratorPrototype%(含 @@iterator 返回自身,经原型链被全部迭代器 继承,it[Symbol.iterator]() === it 恒等)与 %ArrayIteratorPrototype%/ %MapIteratorPrototype%/%SetIteratorPrototype%/%StringIteratorPrototype%/ %RegExpStringIteratorPrototype%(链到 %IteratorPrototype%);Array/Map/Set/ TA/String/matchAll 迭代器包装统一挂对应原型;补 Array/String 的 @@iterator 别名绑定。集合迭代器原型不含 return 方法(规范集合迭代器无 return)。 --- crates/oxide_builtins/src/array.rs | 13 +-- crates/oxide_builtins/src/iterator.rs | 26 ++++-- crates/oxide_builtins/src/map.rs | 3 + crates/oxide_builtins/src/regexp.rs | 7 +- crates/oxide_builtins/src/set.rs | 3 + crates/oxide_builtins/src/string.rs | 40 ++++++++- crates/oxide_builtins/src/typed_array.rs | 3 + crates/oxide_kernel/src/builtin.rs | 77 ++++++++++++++++++ crates/oxide_vm/src/bindings/bind_array.rs | 2 + crates/oxide_vm/src/bindings/bind_iterator.rs | 14 ++++ crates/oxide_vm/src/bindings/bind_string.rs | 10 +++ crates/oxide_vm/src/bindings/mod.rs | 13 +++ .../oxide_vm/tests/builtins_iterator_tests.rs | 81 +++++++++++++++++++ 13 files changed, 274 insertions(+), 18 deletions(-) diff --git a/crates/oxide_builtins/src/array.rs b/crates/oxide_builtins/src/array.rs index 2a5d8bf..d49bf44 100644 --- a/crates/oxide_builtins/src/array.rs +++ b/crates/oxide_builtins/src/array.rs @@ -1720,11 +1720,11 @@ pub fn array_sort(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(vm.reg(args[0])) } -/// `Array.prototype.values()`:返回迭代数组元素的迭代器。 +/// `Array.prototype.values()`:返回迭代数组元素的迭代器(%ArrayIteratorPrototype%)。 pub fn array_values(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.values called with {} args", args.len()); - let this_val = vm.reg(args[0]); - match crate::iterator::make_iterator_for_value(vm, this_val) { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + match make_array_iterator(vm, this_val, ARRAY_ITER_KIND_VALUES) { Ok(iterator) => NativeResult::Ok(iterator), Err(err) => { builtins_error!("Array.prototype.values: invalid receiver"); @@ -1742,16 +1742,17 @@ const ARRAY_ITER_TARGET_PROP: &str = "__target__"; const ARRAY_ITER_INDEX_PROP: &str = "__index__"; const ARRAY_ITER_KIND_PROP: &str = "__kind__"; -/// 创建 Array Iterator 对象:记录目标、当前下标与迭代种类。 +/// 创建 Array Iterator 对象:记录目标、当前下标与迭代种类,原型挂 +/// `%ArrayIteratorPrototype%`(链到 `%IteratorPrototype%`,与 TA 迭代器共享)。 fn make_array_iterator(vm: &mut H, this_val: JsValue, kind: i32) -> Result { let target = match oxide_runtime_api::to_object(this_val, vm) { Ok(v) => v, Err(msg) => return Err(array_type_error(vm, &msg)), }; - let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; + let array_iter_proto = vm.session().builtin_world().array_iterator_proto.as_ptr() as *mut JsObject; let iter = vm .epoch() - .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto))); + .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(array_iter_proto))); let target_si = vm.kernel_core().perm_interner().intern(ARRAY_ITER_TARGET_PROP).0; let index_si = vm.kernel_core().perm_interner().intern(ARRAY_ITER_INDEX_PROP).0; let kind_si = vm.kernel_core().perm_interner().intern(ARRAY_ITER_KIND_PROP).0; diff --git a/crates/oxide_builtins/src/iterator.rs b/crates/oxide_builtins/src/iterator.rs index 1876046..d0218e6 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -13,6 +13,15 @@ pub fn iterator_constructor(vm: &mut H, _args: &[u8]) -> NativeResult NativeResult::Err(crate::error::create_type_error(vm, "Iterator is not a constructor")) } +/// `%IteratorPrototype%[@@iterator]`:返回 this(迭代器对象自迭代)。 +/// +/// 挂在 `%IteratorPrototype%` 上,所有集合迭代器经原型链继承,保证 +/// `it[Symbol.iterator]() === it` 恒等成立;this 为任意值(含原始值)时原样返回。 +pub fn iterator_symbol_iterator(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = if args.is_empty() { JsValue::undefined() } else { vm.reg(args[0]) }; + NativeResult::Ok(this_val) +} + /// `Iterator.from(iterable)`:为任意可迭代值包装一个迭代器对象。 /// 包装器带 `next` 与 `return`(用于 for-of 提前退出时的 IteratorClose 清理)。 pub fn iterator_from(vm: &mut H, args: &[u8]) -> NativeResult { @@ -64,10 +73,11 @@ pub(crate) fn try_make_iterator_inner( Ok(None) => return Ok(None), Err(err) => return Err(err), }; - let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; + // 通用包装器挂 %IteratorPrototype%:经原型链获得 @@iterator(返回自身)。 + let iterator_proto = vm.session().builtin_world().iterator_proto.as_ptr() as *mut JsObject; let wrapper = vm .epoch() - .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto))); + .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(iterator_proto))); let inner_si = vm.kernel_core().perm_interner().intern(INNER_PROP).0; let index_si = vm.kernel_core().perm_interner().intern(INDEX_PROP).0; @@ -679,13 +689,15 @@ pub(crate) fn typed_array_entries_iter_next(vm: &mut H, args: &[u8]) } /// 构造迭代器包装器,其 `next` 委托给调用方指定的按模式分发的 native 函数。 -/// 与 `make_iterator_for_value` 一致,但允许 Map/Set 原型方法选择 -/// values/keys/entries 变体。 -pub(crate) fn make_mode_iterator(vm: &mut H, inner: JsValue, next_fn: *const ()) -> JsValue { - let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; +/// 与 `make_iterator_for_value` 一致,但允许 Map/Set/TA 原型方法选择 +/// values/keys/entries 变体,并挂到各自的集合迭代器原型上。 +pub(crate) fn make_mode_iterator( + vm: &mut H, inner: JsValue, proto_val: JsValue, next_fn: *const (), +) -> JsValue { + let proto_ptr = if proto_val.is_object() { proto_val.as_js_object_ptr() } else { std::ptr::null_mut() }; let wrapper = vm .epoch() - .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto))); + .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr))); let inner_si = vm.kernel_core().perm_interner().intern(INNER_PROP).0; let index_si = vm.kernel_core().perm_interner().intern(INDEX_PROP).0; let next_si = vm.kernel_core().perm_interner().intern("next").0; diff --git a/crates/oxide_builtins/src/map.rs b/crates/oxide_builtins/src/map.rs index e54f49d..b4fbaf1 100644 --- a/crates/oxide_builtins/src/map.rs +++ b/crates/oxide_builtins/src/map.rs @@ -326,6 +326,7 @@ pub fn map_entries(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(crate::iterator::make_mode_iterator( vm, this_val, + JsValue::from_js_object(vm.session().builtin_world().map_iterator_proto.as_ptr() as *mut JsObject), crate::iterator::map_entries_iter_next:: as *const (), )) } @@ -337,6 +338,7 @@ pub fn map_values(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(crate::iterator::make_mode_iterator( vm, this_val, + JsValue::from_js_object(vm.session().builtin_world().map_iterator_proto.as_ptr() as *mut JsObject), crate::iterator::map_values_iter_next:: as *const (), )) } @@ -348,6 +350,7 @@ pub fn map_keys(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(crate::iterator::make_mode_iterator( vm, this_val, + JsValue::from_js_object(vm.session().builtin_world().map_iterator_proto.as_ptr() as *mut JsObject), crate::iterator::map_keys_iter_next:: as *const (), )) } diff --git a/crates/oxide_builtins/src/regexp.rs b/crates/oxide_builtins/src/regexp.rs index 5787504..beac827 100644 --- a/crates/oxide_builtins/src/regexp.rs +++ b/crates/oxide_builtins/src/regexp.rs @@ -547,11 +547,12 @@ pub fn regexp_symbol_match_all(vm: &mut H, args: &[u8]) -> NativeResu JsValue::from_js_object(vm.alloc_object(stub)) }; - // 复用 String.prototype.matchAll 的迭代器包装(next 经 string_match_all_next 推进)。 - let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; + // 复用 String.prototype.matchAll 的迭代器包装(next 经 string_match_all_next 推进), + // 原型挂 %RegExpStringIteratorPrototype%(链到 %IteratorPrototype%)。 + let regexp_iter_proto = vm.session().builtin_world().regexp_string_iterator_proto.as_ptr() as *mut JsObject; let wrapper = vm .epoch() - .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto))); + .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(regexp_iter_proto))); let wrapper_obj = unsafe { &mut *wrapper }; let input_si = vm.kernel_core().perm_interner().intern(crate::string::MALL_INPUT).0; let index_si = vm.kernel_core().perm_interner().intern(crate::string::MALL_INDEX).0; diff --git a/crates/oxide_builtins/src/set.rs b/crates/oxide_builtins/src/set.rs index 86ee058..0b5ff06 100644 --- a/crates/oxide_builtins/src/set.rs +++ b/crates/oxide_builtins/src/set.rs @@ -340,6 +340,7 @@ pub fn set_entries(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(crate::iterator::make_mode_iterator( vm, this_val, + JsValue::from_js_object(vm.session().builtin_world().set_iterator_proto.as_ptr() as *mut JsObject), crate::iterator::set_entries_iter_next:: as *const (), )) } @@ -351,6 +352,7 @@ pub fn set_values(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(crate::iterator::make_mode_iterator( vm, this_val, + JsValue::from_js_object(vm.session().builtin_world().set_iterator_proto.as_ptr() as *mut JsObject), crate::iterator::set_values_iter_next:: as *const (), )) } @@ -363,6 +365,7 @@ pub fn set_keys(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(crate::iterator::make_mode_iterator( vm, this_val, + JsValue::from_js_object(vm.session().builtin_world().set_iterator_proto.as_ptr() as *mut JsObject), crate::iterator::set_values_iter_next:: as *const (), )) } diff --git a/crates/oxide_builtins/src/string.rs b/crates/oxide_builtins/src/string.rs index 9cfdfca..3b6c12c 100644 --- a/crates/oxide_builtins/src/string.rs +++ b/crates/oxide_builtins/src/string.rs @@ -1535,10 +1535,11 @@ pub fn string_match_all(vm: &mut H, args: &[u8]) -> NativeResult { } fn builder_wrapper(vm: &mut H, input: &str, re_obj: JsValue) -> NativeResult { - let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; + // matchAll 迭代器挂 %RegExpStringIteratorPrototype%(链到 %IteratorPrototype%)。 + let regexp_iter_proto = vm.session().builtin_world().regexp_string_iterator_proto.as_ptr() as *mut JsObject; let wrapper = vm .epoch() - .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto))); + .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(regexp_iter_proto))); let wrapper_obj = unsafe { &mut *wrapper }; let input_si = vm.kernel_core().perm_interner().intern(MALL_INPUT).0; @@ -1557,6 +1558,41 @@ fn builder_wrapper(vm: &mut H, input: &str, re_obj: JsValue) -> Nativ NativeResult::Ok(JsValue::from_js_object(wrapper)) } +/// `String.prototype[Symbol.iterator]()`:返回按 Unicode code point 迭代字符的迭代器。 +/// +/// # 步骤 +/// 1. null/undefined 抛 TypeError(RequireObjectCoercible) +/// 2. this 经 ToString 完整转换(对象取 toString 结果,Symbol 抛 TypeError) +/// 3. 包成统一迭代器包装(next 逐 code point 产出,耗尽后 done) +/// +/// # 边界与前提 +/// - 对象 toString 抛出的原始异常原样传播 +/// - 包装器挂 `%StringIteratorPrototype%` → `%IteratorPrototype%` 链 +pub fn string_symbol_iterator(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + if this_val.is_null() || this_val.is_undefined() { + return NativeResult::Err(crate::error::create_type_error( + vm, + "String.prototype[Symbol.iterator] called on null or undefined", + )); + } + let s = match oxide_runtime_api::to_string_full(this_val, vm) { + Ok(s) => s, + Err(_) => { + // ToString 触发对象 toString/valueOf 抛出的原生异常须原样传播。 + if let Some(exc) = vm.take_uncaught_value() { + return NativeResult::Err(exc); + } + return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string")); + } + }; + let s_val = vm.new_string_owned(s); + match crate::iterator::make_iterator_for_value(vm, s_val) { + Ok(iterator) => NativeResult::Ok(iterator), + Err(err) => NativeResult::Err(err), + } +} + pub(crate) fn make_public_native_fn(vm: &mut H, name: &str, native_fn: *const (), arg_count: u8) -> JsValue { let function_proto = vm.session().builtin_world().function_proto.as_ptr() as *mut JsObject; let mut func = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(function_proto)); diff --git a/crates/oxide_builtins/src/typed_array.rs b/crates/oxide_builtins/src/typed_array.rs index afd5dbe..2a5092a 100644 --- a/crates/oxide_builtins/src/typed_array.rs +++ b/crates/oxide_builtins/src/typed_array.rs @@ -1174,6 +1174,7 @@ pub fn typed_array_values(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(crate::iterator::make_mode_iterator( vm, this_val, + JsValue::from_js_object(vm.session().builtin_world().array_iterator_proto.as_ptr() as *mut JsObject), crate::iterator::typed_array_values_iter_next:: as *const (), )) } @@ -1185,6 +1186,7 @@ pub fn typed_array_keys(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(crate::iterator::make_mode_iterator( vm, this_val, + JsValue::from_js_object(vm.session().builtin_world().array_iterator_proto.as_ptr() as *mut JsObject), crate::iterator::typed_array_keys_iter_next:: as *const (), )) } @@ -1196,6 +1198,7 @@ pub fn typed_array_entries(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(crate::iterator::make_mode_iterator( vm, this_val, + JsValue::from_js_object(vm.session().builtin_world().array_iterator_proto.as_ptr() as *mut JsObject), crate::iterator::typed_array_entries_iter_next:: as *const (), )) } diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index 0882e88..124e101 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -270,6 +270,18 @@ pub struct BuiltinWorld { pub plain_date_time_proto: P, pub bigint_constructor: P, pub bigint_proto: P, + /// `%IteratorPrototype%`:各集合迭代器原型的公共祖先,持有 `@@iterator`(返回自身)。 + pub iterator_proto: P, + /// `%ArrayIteratorPrototype%`:Array 与 TypedArray 迭代器共享(`next` 挂其上)。 + pub array_iterator_proto: P, + /// `%MapIteratorPrototype%`:Map 的 values/keys/entries 迭代器共享。 + pub map_iterator_proto: P, + /// `%SetIteratorPrototype%`:Set 的 values/keys/entries 迭代器共享。 + pub set_iterator_proto: P, + /// `%StringIteratorPrototype%`:String.prototype[@@iterator] 返回的迭代器。 + pub string_iterator_proto: P, + /// `%RegExpStringIteratorPrototype%`:matchAll 返回的迭代器。 + pub regexp_string_iterator_proto: P, pub stub_objects: Vec>, } @@ -587,6 +599,21 @@ fn wire_builtin_world_links(world: &BuiltinWorld) { set_proto_if_changed(&world.temporal_object, obj_proto_val); set_proto_if_changed(&world.temporal_now_object, obj_proto_val); + // 迭代器原型链:%IteratorPrototype% → Object.prototype;各集合迭代器原型 + // → %IteratorPrototype%(next/@@iterator 方法由绑定层安装到对应原型)。 + set_proto_if_changed(&world.iterator_proto, obj_proto_val); + let iterator_proto_val = JsValue::from_js_object(world.iterator_proto.as_ptr() as *mut JsObject); + let iterator_protos: [&P; 5] = [ + &world.array_iterator_proto, + &world.map_iterator_proto, + &world.set_iterator_proto, + &world.string_iterator_proto, + &world.regexp_string_iterator_proto, + ]; + for proto in &iterator_protos { + set_proto_if_changed(proto, iterator_proto_val); + } + let typed_array_proto_val = JsValue::from_js_object(world.typed_array_proto.as_ptr() as *mut JsObject); let typed_array_protos: [&P; 11] = [ &world.int8array_proto, @@ -780,6 +807,15 @@ impl BuiltinWorld { let (bigint_proto, bigint_constructor) = make_named_pair(string_forge, shape_forge, labels, "BigInt"); let stub_objects = Vec::new(); + // 迭代器原型家族:链关系(→ %IteratorPrototype% → Object.prototype)在 + // wire_builtin_world_links 中建立,next/@@iterator 方法由绑定层安装。 + let iterator_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + let array_iterator_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + let map_iterator_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + let set_iterator_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + let string_iterator_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + let regexp_string_iterator_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + let world = Self { object_proto, array_proto, @@ -868,6 +904,12 @@ impl BuiltinWorld { plain_date_time_proto, bigint_constructor, bigint_proto, + iterator_proto, + array_iterator_proto, + map_iterator_proto, + set_iterator_proto, + string_iterator_proto, + regexp_string_iterator_proto, stub_objects, }; wire_builtin_world_links(&world); @@ -1127,6 +1169,35 @@ impl BuiltinWorld { }; let stub_objects = if dirty.stubs { Vec::new() } else { current.stub_objects.clone() }; + // 迭代器原型依赖 Object.prototype(链到其上):object 家族重建时一并重建, + // 否则旧原型链指向已释放的 object_proto。 + let ( + iterator_proto, + array_iterator_proto, + map_iterator_proto, + set_iterator_proto, + string_iterator_proto, + regexp_string_iterator_proto, + ) = if dirty.object { + ( + P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), + P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), + P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), + P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), + P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), + P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), + ) + } else { + ( + current.iterator_proto.clone(), + current.array_iterator_proto.clone(), + current.map_iterator_proto.clone(), + current.set_iterator_proto.clone(), + current.string_iterator_proto.clone(), + current.regexp_string_iterator_proto.clone(), + ) + }; + let world = BuiltinWorld { object_proto, array_proto, @@ -1215,6 +1286,12 @@ impl BuiltinWorld { plain_date_time_proto, bigint_constructor, bigint_proto, + iterator_proto, + array_iterator_proto, + map_iterator_proto, + set_iterator_proto, + string_iterator_proto, + regexp_string_iterator_proto, stub_objects, }; wire_builtin_world_links(&world); diff --git a/crates/oxide_vm/src/bindings/bind_array.rs b/crates/oxide_vm/src/bindings/bind_array.rs index d37cce1..0e3846a 100644 --- a/crates/oxide_vm/src/bindings/bind_array.rs +++ b/crates/oxide_vm/src/bindings/bind_array.rs @@ -70,6 +70,8 @@ pub fn bind_array(core: &Arc, session: &KernelSession, global: &mut if let Some(pos) = core.shape_forge().lookup_position(array_proto.shape_id(), si) { array_proto.set_data_meta(pos, PropAttributes::new(true, false, true)); } + // Array.prototype[@@iterator] 与 values 共享同一函数对象。 + super::bind_well_known_method_alias(core, array_proto, "values", 0); let ctor_ptr = session.builtin_world().array_constructor.as_ptr() as *mut JsObject; bind_constructor!(core, global, "Array", ctor_ptr, oxide_builtins::array::array_constructor::, 1, hash: true); diff --git a/crates/oxide_vm/src/bindings/bind_iterator.rs b/crates/oxide_vm/src/bindings/bind_iterator.rs index c069710..8609425 100644 --- a/crates/oxide_vm/src/bindings/bind_iterator.rs +++ b/crates/oxide_vm/src/bindings/bind_iterator.rs @@ -26,4 +26,18 @@ pub fn bind_iterator(core: &Arc, session: &KernelSession, global: &m ); bind_global_value(core, global, "Iterator", JsValue::from_js_object(Box::into_raw(iterator))); + + // %IteratorPrototype% 自身可迭代:@@iterator 返回 this,经原型链被所有 + // 集合迭代器继承(it[Symbol.iterator]() === it 恒等)。 + let iter_proto_ptr = session.builtin_world().iterator_proto.as_ptr() as *mut JsObject; + let iter_proto = unsafe { &mut *iter_proto_ptr }; + crate::bindings::bind_well_known_method( + session.builtin_world(), + core, + iter_proto, + 0, + "iterator", + oxide_builtins::iterator::iterator_symbol_iterator:: as *const (), + 0, + ); } diff --git a/crates/oxide_vm/src/bindings/bind_string.rs b/crates/oxide_vm/src/bindings/bind_string.rs index 0b19112..54c5f12 100644 --- a/crates/oxide_vm/src/bindings/bind_string.rs +++ b/crates/oxide_vm/src/bindings/bind_string.rs @@ -61,6 +61,16 @@ pub fn bind_string(core: &Arc, session: &KernelSession, global: &mut core, &[("toString", oxide_builtins::string::string_to_string:: as *const (), 0)], ); + // String.prototype[@@iterator]:逐 code point 迭代字符。 + super::bind_well_known_method( + session.builtin_world(), + core, + proto, + 0, + "iterator", + oxide_builtins::string::string_symbol_iterator:: as *const (), + 0, + ); let si_str = core.perm_interner().intern("String").0; let str_shape = core.shape_forge().make_shape(global.shape_id(), si_str); diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 2badc46..7452f50 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -380,6 +380,19 @@ fn bind_iterator_global(core: &Arc, session: &KernelSession, global: &[("from", oxide_builtins::iterator::iterator_from:: as *const (), 1)], ); bind_existing_global(core, global, "Iterator", JsValue::from_js_object(Box::into_raw(iterator))); + + // %IteratorPrototype% 自身可迭代(@@iterator 返回 this,经原型链被全部集合迭代器继承)。 + let iter_proto_ptr = session.builtin_world().iterator_proto.as_ptr() as *mut JsObject; + let iter_proto = unsafe { &mut *iter_proto_ptr }; + bind_well_known_method( + session.builtin_world(), + core, + iter_proto, + 0, + "iterator", + oxide_builtins::iterator::iterator_symbol_iterator:: as *const (), + 0, + ); } fn bind_stub_globals(core: &Arc, session: &KernelSession, global: &mut JsObject) { diff --git a/crates/oxide_vm/tests/builtins_iterator_tests.rs b/crates/oxide_vm/tests/builtins_iterator_tests.rs index 9ea9b5a..b9f273a 100644 --- a/crates/oxide_vm/tests/builtins_iterator_tests.rs +++ b/crates/oxide_vm/tests/builtins_iterator_tests.rs @@ -95,3 +95,84 @@ fn for_of_string_chars() { let result = eval(&mut vm, "var out = []; for (const c of 'abc') out.push(c); out.join('')").unwrap(); assert_eq!(to_str(&vm, result), "abc"); } + +#[test] +fn iterator_proto_chain_and_self_iteration() { + let mut vm = Vm::new(); + let cases = [ + // 各家族迭代器自迭代恒等(%IteratorPrototype% 的 @@iterator 返回 this)。 + ("var it = [1,2].values(); it[Symbol.iterator]() === it", true), + ("var it = new Map([['a',1]]).values(); it[Symbol.iterator]() === it", true), + ("var it = new Set([1]).values(); it[Symbol.iterator]() === it", true), + ("var it = new Uint8Array([1]).values(); it[Symbol.iterator]() === it", true), + ("var it = 'abc'[Symbol.iterator](); it[Symbol.iterator]() === it", true), + ("var it = 'ab'.matchAll(/a/g); it[Symbol.iterator]() === it", true), + // Array/TA 共享 %ArrayIteratorPrototype%;Map/Set 各自原型内部共享。 + ("Object.getPrototypeOf([].values()) === Object.getPrototypeOf([].entries())", true), + ("Object.getPrototypeOf(new Uint8Array(0).values()) === Object.getPrototypeOf([].values())", true), + ("Object.getPrototypeOf(new Map().values()) === Object.getPrototypeOf(new Map().keys())", true), + ("Object.getPrototypeOf(new Set().values()) === Object.getPrototypeOf(new Set().entries())", true), + // %IteratorPrototype% 自身可迭代:其 @@iterator 返回 this。 + ( + "var P = Object.getPrototypeOf(Object.getPrototypeOf([].values())); P[Symbol.iterator]() === P", + true, + ), + // 原型链:%ArrayIteratorPrototype% → %IteratorPrototype% → Object.prototype。 + ( + "Object.getPrototypeOf(Object.getPrototypeOf([].values())) === \ + Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf([].values())))", + false, + ), + ( + "Object.getPrototypeOf(Object.getPrototypeOf([].values()))[Symbol.iterator] === \ + Object.getPrototypeOf(new Map().values())[Symbol.iterator]", + true, + ), + ]; + for (src, expected) in cases { + let result = eval(&mut vm, src).unwrap(); + assert_eq!(result.as_bool(), expected, "for {}", src); + } +} + +#[test] +fn array_string_symbol_iterator_bound() { + let mut vm = Vm::new(); + // G4 别名:Array/String 的 @@iterator 属性存在且可迭代。 + let bool_cases = [ + ("[][Symbol.iterator] === [].values", true), + ("var out = []; for (const c of 'a\\u{1F600}b') out.push(c); out.length === 3", true), + ]; + for (src, expected) in bool_cases { + let result = eval(&mut vm, src).unwrap(); + assert_eq!(result.as_bool(), expected, "for {}", src); + } + let str_cases = [ + ("typeof 'abc'[Symbol.iterator]", "function"), + ("[...'ab'].join('')", "ab"), + ("Array.from('ab').join('')", "ab"), + ("[...[1,2,3]].join(',')", "1,2,3"), + ("Array.from([1,2]).join(',')", "1,2"), + ]; + for (src, expected) in str_cases { + let result = eval(&mut vm, src).unwrap(); + assert_eq!(to_str(&vm, result), expected, "for {}", src); + } + // String 迭代器语义:null/undefined 抛 TypeError,对象走 ToString。 + let err = eval(&mut vm, "try { String.prototype[Symbol.iterator].call(null) } catch (e) { e.name }").unwrap(); + assert_eq!(to_str(&vm, err), "TypeError"); + let result = eval(&mut vm, "[...String.prototype[Symbol.iterator].call({toString: () => 'xy'})].join('')").unwrap(); + assert_eq!(to_str(&vm, result), "xy"); +} + +#[test] +fn iterator_proto_object_proto_parent() { + // 链:values() → %ArrayIteratorPrototype% → %IteratorPrototype% → Object.prototype。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf([].values()))) === Object.prototype", + ) + .unwrap(); + assert!(result.as_bool()); +}