feat: Iterator helpers 基建——Symbol.dispose 注册与 %IteratorHelperPrototype%

This commit is contained in:
2026-08-16 14:44:47 +08:00
parent c5c4916a8f
commit 9f13d107ec
10 changed files with 567 additions and 6 deletions
+104 -1
View File
@@ -1,5 +1,5 @@
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::JsObject;
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::private_key::{make_int_key, make_well_known_symbol_key};
use oxide_types::value::JsValue;
@@ -14,6 +14,109 @@ pub fn iterator_constructor<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult
NativeResult::Err(crate::error::create_type_error(vm, "Iterator is not a constructor"))
}
/// `%IteratorPrototype%` 上 `constructor` 访问器的 getter:返回当前 global 上的
/// `Iterator` 构造器。
///
/// 动态查 global(shape 槽查找)而非缓存指针:dirty reset 重建 global 时会换新的
/// `Iterator` 函数对象,缓存旧指针会指向已释放对象。lookup 失败(极端:global 无
/// `Iterator`)返回 undefined,不 panic。
pub fn iterator_constructor_getter<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
let global = vm.session().global_object();
let si = vm.kernel_core().perm_interner().intern("Iterator").0;
let Some(pos) = vm.kernel_core().shape_forge().lookup_position(global.shape_id(), si) else {
return NativeResult::Ok(JsValue::undefined());
};
NativeResult::Ok(global.get_prop_at(pos))
}
/// `%IteratorPrototype%` 上 `Symbol.toStringTag` 访问器的 getter:返回 `"Iterator"`。
pub fn iterator_to_string_tag_getter<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
let sf = vm.kernel_core().perm_interner().as_ref();
NativeResult::Ok(JsValue::perm_string(sf.string_ptr(sf.intern("Iterator").0)))
}
/// SetterThatIgnoresPrototypeProperties 的共享实现:`%IteratorPrototype%` 的
/// `constructor` 与 `Symbol.toStringTag` 访问器共用同一语义,仅属性键不同。
///
/// # 步骤
/// 1. `this` 非对象 → TypeError(原始值直接抛,不建 own 属性)。
/// 2. `this` 为 `%IteratorPrototype%`(home 对象)→ TypeError(模拟对 home 不可写
/// 数据属性的严格模式赋值)。
/// 3. `this` 无 own 指定键属性 → CreateDataPropertyOrThrow(写全可写可枚举)。
/// 4. 有 own 属性 → 普通 Set(继承访问器时在此被调用的场景)。
fn iterator_setter_ignore_proto_props<H: VmHost>(vm: &mut H, args: &[u8], key: u32) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
if !this_val.is_object() {
return NativeResult::Err(crate::error::create_type_error(vm, "Iterator property setter called on non-object"));
}
let home = vm.session().builtin_world().iterator_proto.as_ptr() as *mut JsObject;
if std::ptr::eq(this_val.as_js_object_ptr(), home) {
return NativeResult::Err(crate::error::create_type_error(
vm,
"Cannot assign to read only property of Iterator prototype",
));
}
let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let this_obj = unsafe { &mut *this_val.as_js_object_ptr() };
if vm
.kernel_core()
.shape_forge()
.lookup_position(this_obj.shape_id(), key)
.is_none()
{
match vm.define_data_property(this_obj, key, val, PropAttributes::new(true, true, true)) {
Ok(()) => NativeResult::Ok(JsValue::undefined()),
Err(err) => NativeResult::Err(crate::error::create_type_error(vm, &err)),
}
} else {
match vm.ordinary_set(this_obj, key, val, this_val) {
Ok(()) => NativeResult::Ok(JsValue::undefined()),
Err(err) => NativeResult::Err(crate::error::create_type_error(vm, &err)),
}
}
}
/// `constructor` 访问器的 setter(键 = "constructor")。
pub fn iterator_constructor_setter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let key = vm.kernel_core().perm_interner().intern("constructor").0;
iterator_setter_ignore_proto_props(vm, args, key)
}
/// `Symbol.toStringTag` 访问器的 setter(键 = well-known symbol 9)。
pub fn iterator_to_string_tag_setter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
iterator_setter_ignore_proto_props(vm, args, make_well_known_symbol_key(9))
}
/// `%IteratorPrototype%[@@dispose]`:GetMethod(this, "return"),有则调用并返回
/// undefined。
///
/// # 步骤
/// 1. `this` 非对象 → TypeError(GetMethod 的 GetV 语义要求对象)。
/// 2. 读 `return` 方法:不可调用(undefined/null)则跳过;getter 抛错透传原值。
/// 3. 调用 `return()` 成功/抛错均最终返回 undefined 或透传原异常。
pub fn iterator_dispose<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
if !this_val.is_object() {
return NativeResult::Err(crate::error::create_type_error(
vm,
"%IteratorPrototype%[@@dispose] called on non-object",
));
}
let obj = unsafe { &*this_val.as_js_object_ptr() };
let return_si = vm.kernel_core().perm_interner().intern("return").0;
let return_fn = match vm.ordinary_get(obj, return_si, this_val) {
Ok(f) if is_callable(f) => f,
Ok(_) => JsValue::undefined(),
Err(err) => return NativeResult::Err(engine_error(vm, &err)),
};
if !return_fn.is_undefined() {
if let Err(err) = vm.call_function_sync(return_fn, this_val, &[]) {
return NativeResult::Err(engine_error(vm, &err));
}
}
NativeResult::Ok(JsValue::undefined())
}
/// `%IteratorPrototype%[@@iterator]`:返回 this(迭代器对象自迭代)。
///
/// 挂在 `%IteratorPrototype%` 上,所有集合迭代器经原型链继承,保证
+2
View File
@@ -142,6 +142,8 @@ fn decode_symbol_key<H: VmHost>(vm: &H, key: u32) -> JsValue {
7 => world.sym_match_all.as_ptr(),
8 => world.sym_async_iterator.as_ptr(),
10 => world.sym_species.as_ptr(),
11 => world.sym_async_dispose.as_ptr(),
12 => world.sym_dispose.as_ptr(),
_ => world.sym_to_string_tag.as_ptr(),
};
return JsValue::from_js_object(ptr as *mut JsObject);
+26 -1
View File
@@ -254,6 +254,8 @@ pub struct BuiltinWorld {
pub sym_async_iterator: P<JsObject>,
pub sym_to_string_tag: P<JsObject>,
pub sym_species: P<JsObject>,
pub sym_async_dispose: P<JsObject>,
pub sym_dispose: P<JsObject>,
pub temporal_object: P<JsObject>,
pub temporal_now_object: P<JsObject>,
pub instant_constructor: P<JsObject>,
@@ -282,6 +284,8 @@ pub struct BuiltinWorld {
pub string_iterator_proto: P<JsObject>,
/// `%RegExpStringIteratorPrototype%`:matchAll 返回的迭代器。
pub regexp_string_iterator_proto: P<JsObject>,
/// `%IteratorHelperPrototype%`:Iterator helpers 结果对象的共享原型,链到 %IteratorPrototype%。
pub iterator_helper_proto: P<JsObject>,
pub stub_objects: Vec<P<JsObject>>,
}
@@ -603,12 +607,13 @@ fn wire_builtin_world_links(world: &BuiltinWorld) {
// → %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<JsObject>; 5] = [
let iterator_protos: [&P<JsObject>; 6] = [
&world.array_iterator_proto,
&world.map_iterator_proto,
&world.set_iterator_proto,
&world.string_iterator_proto,
&world.regexp_string_iterator_proto,
&world.iterator_helper_proto,
];
for proto in &iterator_protos {
set_proto_if_changed(proto, iterator_proto_val);
@@ -733,6 +738,8 @@ impl BuiltinWorld {
BuiltinId::SymAsyncIterator => &self.sym_async_iterator,
BuiltinId::SymToStringTag => &self.sym_to_string_tag,
BuiltinId::SymSpecies => &self.sym_species,
BuiltinId::SymAsyncDispose => &self.sym_async_dispose,
BuiltinId::SymDispose => &self.sym_dispose,
BuiltinId::TemporalObject => &self.temporal_object,
BuiltinId::TemporalNowObject => &self.temporal_now_object,
BuiltinId::InstantConstructor => &self.instant_constructor,
@@ -792,6 +799,8 @@ impl BuiltinWorld {
let sym_async_iterator = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let sym_to_string_tag = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let sym_species = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let sym_async_dispose = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let sym_dispose = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let temporal_object = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let temporal_now_object = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let (instant_proto, instant_constructor) = make_named_pair(string_forge, shape_forge, labels, "Instant");
@@ -815,6 +824,7 @@ impl BuiltinWorld {
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 iterator_helper_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let world = Self {
object_proto,
@@ -888,6 +898,8 @@ impl BuiltinWorld {
sym_async_iterator,
sym_to_string_tag,
sym_species,
sym_async_dispose,
sym_dispose,
temporal_object,
temporal_now_object,
instant_constructor,
@@ -910,6 +922,7 @@ impl BuiltinWorld {
set_iterator_proto,
string_iterator_proto,
regexp_string_iterator_proto,
iterator_helper_proto,
stub_objects,
};
wire_builtin_world_links(&world);
@@ -997,6 +1010,8 @@ impl BuiltinWorld {
sym_async_iterator,
sym_to_string_tag,
sym_species,
sym_async_dispose,
sym_dispose,
) = if dirty.symbol_family {
let (symbol_proto, symbol_constructor) = make_named_pair(string_forge, shape_forge, labels, "Symbol");
(
@@ -1013,6 +1028,8 @@ impl BuiltinWorld {
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 {
(
@@ -1029,6 +1046,8 @@ impl BuiltinWorld {
current.sym_async_iterator.clone(),
current.sym_to_string_tag.clone(),
current.sym_species.clone(),
current.sym_async_dispose.clone(),
current.sym_dispose.clone(),
)
};
@@ -1178,6 +1197,7 @@ impl BuiltinWorld {
set_iterator_proto,
string_iterator_proto,
regexp_string_iterator_proto,
iterator_helper_proto,
) = if dirty.object {
(
P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
@@ -1186,6 +1206,7 @@ impl BuiltinWorld {
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 {
(
@@ -1195,6 +1216,7 @@ impl BuiltinWorld {
current.set_iterator_proto.clone(),
current.string_iterator_proto.clone(),
current.regexp_string_iterator_proto.clone(),
current.iterator_helper_proto.clone(),
)
};
@@ -1270,6 +1292,8 @@ impl BuiltinWorld {
sym_async_iterator,
sym_to_string_tag,
sym_species,
sym_async_dispose,
sym_dispose,
temporal_object,
temporal_now_object,
instant_constructor,
@@ -1292,6 +1316,7 @@ impl BuiltinWorld {
set_iterator_proto,
string_iterator_proto,
regexp_string_iterator_proto,
iterator_helper_proto,
stub_objects,
};
wire_builtin_world_links(&world);
+8 -2
View File
@@ -225,7 +225,7 @@ pub struct KernelSession {
/// 维护注意:每个新增的 `BuiltinWorld` 对象字段都必须加到这里以及
/// `KernelSession::dirty_since_snapshot()`,以便选择性重置重建正确的
/// builtin 家族。
pub const NUM_BUILTINS: usize = 86;
pub const NUM_BUILTINS: usize = 88;
/// 内置对象枚举 id,与 `BuiltinWorld` 中的存储槽一一对应。
///
@@ -320,6 +320,8 @@ pub enum BuiltinId {
ZonedDateTimeProto = 82,
PlainDateTimeConstructor = 83,
PlainDateTimeProto = 84,
SymAsyncDispose = 86,
SymDispose = 87,
}
impl BuiltinId {
@@ -413,6 +415,8 @@ impl BuiltinId {
BuiltinId::PlainDateTimeConstructor,
BuiltinId::PlainDateTimeProto,
BuiltinId::SymSpecies,
BuiltinId::SymAsyncDispose,
BuiltinId::SymDispose,
];
}
@@ -616,7 +620,9 @@ impl KernelSession {
|| gen(BuiltinId::SymMatchAll) != snap(BuiltinId::SymMatchAll)
|| gen(BuiltinId::SymAsyncIterator) != snap(BuiltinId::SymAsyncIterator)
|| gen(BuiltinId::SymToStringTag) != snap(BuiltinId::SymToStringTag)
|| gen(BuiltinId::SymSpecies) != snap(BuiltinId::SymSpecies),
|| gen(BuiltinId::SymSpecies) != snap(BuiltinId::SymSpecies)
|| gen(BuiltinId::SymAsyncDispose) != snap(BuiltinId::SymAsyncDispose)
|| gen(BuiltinId::SymDispose) != snap(BuiltinId::SymDispose),
math: gen(BuiltinId::MathObject) != snap(BuiltinId::MathObject),
json: gen(BuiltinId::JsonObject) != snap(BuiltinId::JsonObject),
date: gen(BuiltinId::DateConstructor) != snap(BuiltinId::DateConstructor)
+6
View File
@@ -1044,6 +1044,10 @@ pub fn well_known_symbol_id<H: VmHost + ?Sized>(host: &H, ptr: *mut JsObject) ->
Some(9)
} else if std::ptr::eq(ptr, world.sym_species.as_ptr()) {
Some(10)
} else if std::ptr::eq(ptr, world.sym_async_dispose.as_ptr()) {
Some(11)
} else if std::ptr::eq(ptr, world.sym_dispose.as_ptr()) {
Some(12)
} else {
None
}
@@ -1063,6 +1067,8 @@ pub fn well_known_symbol_name(id: u32) -> Option<&'static str> {
8 => "Symbol.asyncIterator",
9 => "Symbol.toStringTag",
10 => "Symbol.species",
11 => "Symbol.asyncDispose",
12 => "Symbol.dispose",
_ => return None,
})
}
+4 -1
View File
@@ -51,7 +51,10 @@ pub const fn int_key_value(key: u32) -> u32 {
pub const SYMBOL_KEY_BASE: u32 = 0xA000_0000;
/// 保留给 well-known symbol 的键槽位数(位于 Symbol 键区间最低端)。
pub const WELL_KNOWN_SYMBOL_COUNT: u32 = 11;
///
/// 键序与 TC39 well-known symbol 表一致:0-10 为既有项,11/12 分别为
/// `Symbol.asyncDispose` 与 `Symbol.dispose`(显式资源管理提案)。
pub const WELL_KNOWN_SYMBOL_COUNT: u32 = 13;
/// 用户 symbol 下标的保留位掩码(低 28 位)。
const SYMBOL_INDEX_MASK: u32 = 0x0FFF_FFFF;
@@ -6,7 +6,8 @@ use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use crate::bindings::{
apply_binding_table, bind_global_value, bind_iterator_function_prototype, configure_native_constructor,
apply_binding_table, bind_global_value, bind_iterator_ctor_identity, bind_iterator_function_prototype,
configure_native_constructor,
};
/// 绑定 `Iterator` 全局构造器(`prototype` = %IteratorPrototype%、`from` 方法)。
@@ -23,6 +24,7 @@ pub fn bind_iterator(core: &Arc<KernelCore>, session: &KernelSession, global: &m
oxide_builtins::iterator::iterator_constructor::<crate::vm::Vm> as *const (),
0,
);
bind_iterator_ctor_identity(core, &mut iterator);
bind_iterator_function_prototype(core, session, &mut iterator);
apply_binding_table(
@@ -90,6 +90,14 @@ pub fn bind_symbol(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
"species",
JsValue::from_js_object(session.builtin_world().sym_species.as_ptr() as *mut JsObject),
),
(
"asyncDispose",
JsValue::from_js_object(session.builtin_world().sym_async_dispose.as_ptr() as *mut JsObject),
),
(
"dispose",
JsValue::from_js_object(session.builtin_world().sym_dispose.as_ptr() as *mut JsObject),
),
] {
bind_well_known_symbol(core, ctor, name, val);
}
+145
View File
@@ -165,6 +165,98 @@ pub(crate) fn bind_accessor_getter(
proto.bump_generation();
}
/// 在原型上绑定一个原生访问器属性(getter + setter 成对),键可为字符串 intern 键
/// 或 well-known symbol 键(shape 键直接传入,不要求字符串 intern)。
///
/// # 步骤
/// 1. 构造 getter 函数对象(name = `getter_name`,length 0)与 setter 函数对象
/// (name = `setter_name`,length 1)。
/// 2. 为键开 shape 槽位并写入访问器 meta(enumerable=false, configurable=true)。
///
/// # 注意事项
/// getter/setter 函数对象经 `Box::into_raw` 持有,与 `bind_accessor_getter` 的
/// 方法 wrapper 同一生命周期约定(内置对象在 session 生命周期内不被回收)。
#[expect(clippy::too_many_arguments)]
pub(crate) fn bind_accessor_getset(
core: &Arc<KernelCore>, session: &KernelSession, proto: &mut JsObject, key: u32, getter_name: &str,
setter_name: &str, getter_fn: *const (), setter_fn: *const (),
) {
let shape_forge = core.shape_forge().as_ref();
let string_forge = core.perm_interner().as_ref();
let fn_proto_val = JsValue::from_js_object(session.builtin_world().function_proto.as_ptr() as *mut JsObject);
let mut getter = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val));
getter.set_function(true);
// SAFETY: getter_fn 是转成 *const () 的 NativeFn 函数项指针。
getter.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(getter_fn) }));
getter.set_native_arg_count(0);
// 给函数对象开 name/length 槽位(描述符均 { writable:false, enumerable:false, configurable:true })。
let si_name = string_forge.intern("name").0;
let name_shape = shape_forge.make_shape(getter.shape_id(), si_name);
getter.set_shape_id(name_shape);
getter
.ensure_hash_props()
.push(JsValue::perm_string(string_forge.string_ptr(string_forge.intern(getter_name).0)));
let name_pos = getter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1);
getter.set_data_meta(name_pos, PropAttributes::new(false, false, true));
let si_length = string_forge.intern("length").0;
let length_shape = shape_forge.make_shape(getter.shape_id(), si_length);
getter.set_shape_id(length_shape);
getter.ensure_hash_props().push(JsValue::int(0));
let length_pos = getter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1);
getter.set_data_meta(length_pos, PropAttributes::new(false, false, true));
let getter_val = JsValue::from_js_object(Box::into_raw(getter));
let mut setter = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val));
setter.set_function(true);
// SAFETY: setter_fn 是转成 *const () 的 NativeFn 函数项指针。
setter.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(setter_fn) }));
setter.set_native_arg_count(1);
let name_shape = shape_forge.make_shape(setter.shape_id(), si_name);
setter.set_shape_id(name_shape);
setter
.ensure_hash_props()
.push(JsValue::perm_string(string_forge.string_ptr(string_forge.intern(setter_name).0)));
let name_pos = setter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1);
setter.set_data_meta(name_pos, PropAttributes::new(false, false, true));
let length_shape = shape_forge.make_shape(setter.shape_id(), si_length);
setter.set_shape_id(length_shape);
setter.ensure_hash_props().push(JsValue::int(1));
let length_pos = setter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1);
setter.set_data_meta(length_pos, PropAttributes::new(false, false, true));
let setter_val = JsValue::from_js_object(Box::into_raw(setter));
// 访问器属性槽:enumerable=false、configurable=true(get/set 函数对象已持有)。
let new_shape = shape_forge.make_shape(proto.shape_id(), key);
proto.set_shape_id(new_shape);
let pos = proto.push_prop(JsValue::undefined());
proto.set_accessor_meta(pos, getter_val, setter_val, PropAttributes::new(true, false, true));
proto.bump_generation();
}
/// 为 `Iterator` 构造器绑定 `length`=0 与 `name`="Iterator" 属性(规范描述符均
/// { writable:false, enumerable:false, configurable:true })。
///
/// `bind_iterator`(初始化路径)与 `bind_iterator_global`(dirty reset 路径)共用,
/// 防两处漂移导致 dirty reset 后 `Iterator.length`/`Iterator.name` 消失。
pub(crate) fn bind_iterator_ctor_identity(core: &Arc<KernelCore>, ctor: &mut JsObject) {
let shape_forge = core.shape_forge().as_ref();
let string_forge = core.perm_interner().as_ref();
let si_length = string_forge.intern("length").0;
let length_shape = shape_forge.make_shape(ctor.shape_id(), si_length);
ctor.set_shape_id(length_shape);
ctor.ensure_hash_props().push(JsValue::int(0));
let length_pos = ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1);
ctor.set_data_meta(length_pos, PropAttributes::new(false, false, true));
let si_name = string_forge.intern("name").0;
let name_shape = shape_forge.make_shape(ctor.shape_id(), si_name);
ctor.set_shape_id(name_shape);
ctor.ensure_hash_props()
.push(JsValue::perm_string(string_forge.string_ptr(string_forge.intern("Iterator").0)));
let name_pos = ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1);
ctor.set_data_meta(name_pos, PropAttributes::new(false, false, true));
}
/// 把原型上 `source` 属性已绑定的函数值复制到 `alias` 名下(共享同一函数对象)。
///
/// 用于规范要求的方法别名(如 Set 的 `keys`/`@@iterator` 与 `values` 同一函数对象)。
@@ -294,6 +386,58 @@ fn bind_iterator_protos(core: &Arc<KernelCore>, session: &KernelSession) {
);
}
// %IteratorPrototype% 的 constructor / @@toStringTag 访问器:getter 动态读
// global(dirty reset 后 global 重建,不能缓存指针),setter 实现
// SetterThatIgnoresPrototypeProperties(home 赋值抛 TypeError)。
let si_constructor = core.perm_interner().intern("constructor").0;
if core
.shape_forge()
.lookup_position(iter_proto.shape_id(), si_constructor)
.is_none()
{
bind_accessor_getset(
core,
session,
iter_proto,
si_constructor,
"get constructor",
"set constructor",
oxide_builtins::iterator::iterator_constructor_getter::<crate::vm::Vm> as *const (),
oxide_builtins::iterator::iterator_constructor_setter::<crate::vm::Vm> as *const (),
);
}
let sym_to_string_tag = oxide_types::private_key::make_well_known_symbol_key(9);
if core
.shape_forge()
.lookup_position(iter_proto.shape_id(), sym_to_string_tag)
.is_none()
{
bind_accessor_getset(
core,
session,
iter_proto,
sym_to_string_tag,
"get [Symbol.toStringTag]",
"set [Symbol.toStringTag]",
oxide_builtins::iterator::iterator_to_string_tag_getter::<crate::vm::Vm> as *const (),
oxide_builtins::iterator::iterator_to_string_tag_setter::<crate::vm::Vm> as *const (),
);
}
// %IteratorPrototype% 的 @@dispose:显式资源管理下用 return 关闭迭代器。
let sym_dispose = oxide_types::private_key::make_well_known_symbol_key(12);
if core.shape_forge().lookup_position(iter_proto.shape_id(), sym_dispose).is_none() {
bind_well_known_method(
world,
core,
iter_proto,
12,
"[Symbol.dispose]",
oxide_builtins::iterator::iterator_dispose::<crate::vm::Vm> as *const (),
0,
);
}
// %ArrayIteratorPrototype% 服务 Array/TA 两族;Map/Set 共用按 `__mode__`
// 分发的实现;%RegExpStringIteratorPrototype% 供 matchAll。
bind_iterator_proto_next(
@@ -488,6 +632,7 @@ fn bind_iterator_global(core: &Arc<KernelCore>, session: &KernelSession, global:
oxide_builtins::iterator::iterator_constructor::<crate::vm::Vm> as *const (),
0,
);
bind_iterator_ctor_identity(core, &mut iterator);
bind_iterator_function_prototype(core, session, &mut iterator);
apply_binding_table(
session.builtin_world(),
@@ -1,4 +1,5 @@
use oxide_compiler::compiler::Compiler;
use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
@@ -289,3 +290,263 @@ fn iterator_proto_next_consumption_unchanged() {
let done = eval(&mut vm, "var it = new Uint8Array([1]).values(); it.next(); it.next().done").unwrap();
assert!(done.as_bool());
}
#[test]
fn iterator_function_name_length() {
let mut vm = Vm::new();
let bool_cases = [
("Iterator.name === 'Iterator'", true),
("Iterator.length === 0", true),
("Object.getOwnPropertyDescriptor(Iterator, 'name').writable === false", true),
("Object.getOwnPropertyDescriptor(Iterator, 'name').enumerable === false", true),
("Object.getOwnPropertyDescriptor(Iterator, 'name').configurable === true", true),
("Object.getOwnPropertyDescriptor(Iterator, 'length').writable === false", true),
("Object.getOwnPropertyDescriptor(Iterator, 'length').enumerable === false", true),
("Object.getOwnPropertyDescriptor(Iterator, 'length').configurable === 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_prototype_constructor_accessor() {
let mut vm = Vm::new();
let bool_cases = [
// getter 动态读 global 上的 Iterator 构造器。
("Iterator.prototype.constructor === Iterator", true),
(
"typeof Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor').get === 'function'",
true,
),
(
"typeof Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor').set === 'function'",
true,
),
(
"Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor').enumerable === false",
true,
),
(
"Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor').configurable === true",
true,
),
// getter 无参调用(this=undefined)仍返回 Iterator。
(
"Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor').get.call() === Iterator",
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_prototype_constructor_setter_semantics() {
let mut vm = Vm::new();
// home 对象赋值与原始值 this 抛 TypeError(SetterThatIgnoresPrototypeProperties)。
let throws = [
(
"try { Iterator.prototype.constructor = 'x'; false } catch (e) { e instanceof TypeError }",
true,
),
(
"try { var d = Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor'); \
d.set.call(undefined, 'x'); false } catch (e) { e instanceof TypeError }",
true,
),
(
"try { var d = Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor'); \
d.set.call(null, 'x'); false } catch (e) { e instanceof TypeError }",
true,
),
(
"try { var d = Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor'); \
d.set.call(true, 'x'); false } catch (e) { e instanceof TypeError }",
true,
),
];
for (src, expected) in throws {
let result = eval(&mut vm, src).unwrap();
assert_eq!(result.as_bool(), expected, "for {}", src);
}
// 非 home 对象:无 own 属性建 own,有 own 属性走 Set 覆盖。
let ok_cases = [
(
"var o = {}; var d = Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor'); \
d.set.call(o, 42); o.constructor === 42 && o.hasOwnProperty('constructor')",
true,
),
(
"var o = { constructor: 1 }; var d = Object.getOwnPropertyDescriptor(Iterator.prototype, 'constructor'); \
d.set.call(o, 2); o.constructor === 2",
true,
),
// home 不受污染。
("Iterator.prototype.constructor === Iterator", true),
];
for (src, expected) in ok_cases {
let result = eval(&mut vm, src).unwrap();
assert_eq!(result.as_bool(), expected, "for {}", src);
}
}
#[test]
fn iterator_prototype_to_string_tag_accessor() {
let mut vm = Vm::new();
let bool_cases = [
("Iterator.prototype[Symbol.toStringTag] === 'Iterator'", true),
(
"typeof Object.getOwnPropertyDescriptor(Iterator.prototype, Symbol.toStringTag).get === 'function'",
true,
),
(
"typeof Object.getOwnPropertyDescriptor(Iterator.prototype, Symbol.toStringTag).set === 'function'",
true,
),
(
"Object.getOwnPropertyDescriptor(Iterator.prototype, Symbol.toStringTag).enumerable === false",
true,
),
(
"Object.getOwnPropertyDescriptor(Iterator.prototype, Symbol.toStringTag).configurable === true",
true,
),
(
"Object.getOwnPropertyDescriptor(Iterator.prototype, Symbol.toStringTag).get.call() === 'Iterator'",
true,
),
// home 赋值抛 TypeError。
(
"try { Iterator.prototype[Symbol.toStringTag] = 'x'; false } catch (e) { e instanceof TypeError }",
true,
),
// 普通对象 setter 建 own Symbol.toStringTag。
(
"var o = {}; var d = Object.getOwnPropertyDescriptor(Iterator.prototype, Symbol.toStringTag); \
d.set.call(o, 'tag'); o[Symbol.toStringTag] === 'tag'",
true,
),
// 原型继承场景:非 home 子对象赋值落到自身 own 属性(不污染祖先)。
(
"var p = Object.create(Iterator.prototype); p[Symbol.toStringTag] = 'sub'; \
p[Symbol.toStringTag] === 'sub' && Iterator.prototype[Symbol.toStringTag] === 'Iterator'",
true,
),
];
for (src, expected) in bool_cases {
let result = eval(&mut vm, src).unwrap();
assert_eq!(result.as_bool(), expected, "for {}", src);
}
}
#[test]
fn symbol_dispose_and_async_dispose_registered() {
let mut vm = Vm::new();
// well-known symbol 以空对象表示,typeof 为 object;两符号互不相同。
let bool_cases = [
("typeof Symbol.dispose === 'object'", true),
("typeof Symbol.asyncDispose === 'object'", true),
("Symbol.dispose !== Symbol.asyncDispose", true),
("Symbol.dispose !== Symbol.iterator", true),
// 键可作属性名使用(well-known 键映射一致)。
("({ [Symbol.dispose]: 1 })[Symbol.dispose] === 1", true),
// getOwnPropertySymbols 能反解出同一符号对象。
("Object.getOwnPropertySymbols({ [Symbol.dispose]: 1 })[0] === Symbol.dispose", true),
];
for (src, expected) in bool_cases {
let result = eval(&mut vm, src).unwrap();
assert_eq!(result.as_bool(), expected, "for {}", src);
}
// 描述性名称经 String 构造器反射(well_known_symbol_name 表)。
let desc = eval(&mut vm, "String(Symbol.dispose)").unwrap();
assert_eq!(to_str(&vm, desc), "Symbol(Symbol.dispose)");
}
#[test]
fn iterator_prototype_symbol_dispose() {
let mut vm = Vm::new();
// @@dispose 调用 this 的 return 方法并返回 undefined。
let bool_cases = [
("typeof Iterator.prototype[Symbol.dispose] === 'function'", true),
("Iterator.prototype[Symbol.dispose].length === 0", true),
("Iterator.prototype[Symbol.dispose].name === '[Symbol.dispose]'", true),
(
"var called = false; var it = { return: function() { called = true; return { done: true }; } }; \
var r = Iterator.prototype[Symbol.dispose].call(it); r === undefined && called === true",
true,
),
// 无 return 方法:GetMethod 返回 undefined,跳过调用。
("Iterator.prototype[Symbol.dispose].call({ next: function() {} }) === undefined", true),
// return 返回任意值不影响 @@dispose 的 undefined 返回值。
(
"var it = { return: function() { return 42; } }; \
Iterator.prototype[Symbol.dispose].call(it) === undefined",
true,
),
];
for (src, expected) in bool_cases {
let result = eval(&mut vm, src).unwrap();
assert_eq!(result.as_bool(), expected, "for {}", src);
}
// return 抛错经 @@dispose 透传(原值,不二次包装)。
let thrown = eval(
&mut vm,
"try { var it = { return: function() { throw new RangeError('boom'); } }; \
Iterator.prototype[Symbol.dispose].call(it); false } catch (e) { e instanceof RangeError }",
)
.unwrap();
assert!(thrown.as_bool());
}
#[test]
fn iterator_helper_prototype_links_to_iterator_proto() {
let vm = Vm::new();
// %IteratorHelperPrototype% 已建且链到 %IteratorPrototype%(链:
// helper → %IteratorPrototype% → Object.prototype)。
let helper_ptr = vm.session().builtin_world().iterator_helper_proto.as_ptr() as *mut JsObject;
let helper_proto_val = unsafe { &*helper_ptr }.proto();
let iter_ptr = vm.session().builtin_world().iterator_proto.as_ptr() as *mut JsObject;
assert!(helper_proto_val.is_object());
assert!(std::ptr::eq(helper_proto_val.as_js_object_ptr(), iter_ptr));
}
#[test]
fn iterator_prototype_rebound_after_full_reset() {
let mut vm = Vm::new();
// 用户改写 %IteratorPrototype%:object 家族世代递增(global 未动)。
let old_iter_proto = vm.session().builtin_world().iterator_proto.as_ptr();
unsafe { &mut *(old_iter_proto as *mut JsObject) }.bump_generation();
vm.full_reset();
// %IteratorPrototype% 重建后访问器 / @@dispose 重绑正确,构造器 identity 保留。
let cases = [
("Iterator.prototype.constructor === Iterator", true),
("Iterator.prototype[Symbol.toStringTag] === 'Iterator'", true),
("typeof Iterator.prototype[Symbol.dispose] === 'function'", true),
("Iterator.name === 'Iterator'", true),
("Iterator.length === 0", true),
("typeof Symbol.dispose === 'object'", true),
("typeof Symbol.asyncDispose === 'object'", true),
];
for (src, expected) in cases {
let result = eval(&mut vm, src).unwrap();
assert_eq!(result.as_bool(), expected, "for {}", src);
}
// helper 原型随 object 家族重建并链到新 %IteratorPrototype%。
let helper_ptr = vm.session().builtin_world().iterator_helper_proto.as_ptr() as *mut JsObject;
let helper_proto_val = unsafe { &*helper_ptr }.proto();
let new_iter_ptr = vm.session().builtin_world().iterator_proto.as_ptr() as *mut JsObject;
assert!(std::ptr::eq(helper_proto_val.as_js_object_ptr(), new_iter_ptr));
// 迭代器家族整体功能完好。
let r = eval(
&mut vm,
"Iterator.prototype[Symbol.dispose].call({return: function(){return {done:true}}})",
)
.unwrap();
assert!(r.is_undefined());
}