feat: Map/Set 支持 for-of 迭代与 entries/values/keys(内部槽方式)

This commit is contained in:
2026-06-27 17:49:19 +08:00
parent 749a1c7aec
commit 279255a821
6 changed files with 337 additions and 3 deletions
+149 -1
View File
@@ -74,7 +74,7 @@ pub fn iterator_wrapper_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
}
fn get_iterator<H: VmHost>(vm: &mut H, value: JsValue) -> Result<JsValue, JsValue> {
if value.is_string() || is_array_value(value) {
if value.is_string() || is_array_value(value) || is_map_value(value) || is_set_value(value) {
return Ok(value);
}
@@ -115,6 +115,14 @@ fn next_array_like<H: VmHost>(vm: &mut H, wrapper: &mut JsObject, inner: JsValue
return Some(make_iter_result(vm, JsValue::undefined(), true));
}
// for-of loop default iterators: Map yields [key, value] entries, Set yields values.
if is_map_value(inner) {
return Some(map_set_step(vm, wrapper, inner, index_si, MapSetMode::MapEntries));
}
if is_set_value(inner) {
return Some(map_set_step(vm, wrapper, inner, index_si, MapSetMode::SetValues));
}
None
}
@@ -169,3 +177,143 @@ fn is_callable(value: JsValue) -> bool {
let ptr = value.as_js_object_ptr();
!ptr.is_null() && unsafe { &*ptr }.is_function()
}
fn is_map_value(value: JsValue) -> bool {
if !value.is_object() {
return false;
}
let ptr = value.as_js_object_ptr();
!ptr.is_null() && unsafe { &*ptr }.is_map()
}
fn is_set_value(value: JsValue) -> bool {
if !value.is_object() {
return false;
}
let ptr = value.as_js_object_ptr();
!ptr.is_null() && unsafe { &*ptr }.is_set()
}
fn make_map_set_pair<H: VmHost>(vm: &mut H, a: JsValue, b: JsValue) -> JsValue {
let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let pair = vm.alloc_object(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
2,
vm.epoch().bump(),
));
// SAFETY: pair is a freshly allocated array JsObject in the current epoch.
unsafe {
(*pair).set_prop_at(0, a);
(*pair).set_prop_at(1, b);
(*pair).set_prop_count(2);
}
JsValue::from_js_object(pair)
}
#[derive(Clone, Copy)]
enum MapSetMode {
MapEntries,
MapValues,
MapKeys,
SetEntries,
SetValues,
}
/// Advance a Map/Set iterator wrapper one step, yielding the {value, done} result for
/// the requested mode. Entries live in the indexmap stored in the collection's
/// native-data slot; the (a, b) pair is copied out before any allocation so no borrow
/// of the native collection is held across a `vm` call.
fn map_set_step<H: VmHost>(
vm: &mut H, wrapper: &mut JsObject, inner: JsValue, index_si: u32, mode: MapSetMode,
) -> JsValue {
let index = current_index(vm, wrapper, index_si);
let is_map = matches!(mode, MapSetMode::MapEntries | MapSetMode::MapValues | MapSetMode::MapKeys);
let entry: Option<(JsValue, JsValue)> = unsafe {
let obj_ptr = inner.as_js_object_ptr();
if obj_ptr.is_null() {
None
} else if is_map {
let p = (*obj_ptr).native_data() as *const crate::map::MapInner;
if p.is_null() {
None
} else {
(*p).get_index(index).map(|(key, value)| (key.0, *value))
}
} else {
let p = (*obj_ptr).native_data() as *const crate::set::SetInner;
if p.is_null() {
None
} else {
(*p).get_index(index).map(|elem| (elem.0, elem.0))
}
}
};
match entry {
Some((a, b)) => {
vm.set_or_create_prop_value(wrapper, index_si, JsValue::int((index + 1) as i32));
let value = match mode {
MapSetMode::MapEntries | MapSetMode::SetEntries => make_map_set_pair(vm, a, b),
MapSetMode::MapValues => b,
MapSetMode::MapKeys | MapSetMode::SetValues => a,
};
make_iter_result(vm, value, false)
}
None => make_iter_result(vm, JsValue::undefined(), true),
}
}
fn map_set_next_dispatch<H: VmHost>(vm: &mut H, args: &[u8], mode: MapSetMode) -> 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 next called on non-object"));
}
let wrapper = unsafe { &mut *this_val.as_js_object_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 inner = match vm.ordinary_get(wrapper, inner_si, this_val) {
Ok(inner) if !inner.is_undefined() => inner,
_ => return NativeResult::Err(crate::error::create_type_error(vm, "iterator has no inner collection")),
};
NativeResult::Ok(map_set_step(vm, wrapper, inner, index_si, mode))
}
pub(crate) fn map_entries_iter_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
map_set_next_dispatch::<H>(vm, args, MapSetMode::MapEntries)
}
pub(crate) fn map_values_iter_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
map_set_next_dispatch::<H>(vm, args, MapSetMode::MapValues)
}
pub(crate) fn map_keys_iter_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
map_set_next_dispatch::<H>(vm, args, MapSetMode::MapKeys)
}
pub(crate) fn set_values_iter_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
map_set_next_dispatch::<H>(vm, args, MapSetMode::SetValues)
}
pub(crate) fn set_entries_iter_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
map_set_next_dispatch::<H>(vm, args, MapSetMode::SetEntries)
}
/// Build an iterator wrapper whose `next` delegates to a caller-supplied mode-specific
/// native function. Mirrors `make_iterator_for_value` but lets Map/Set prototype methods
/// pick the values/keys/entries variant.
pub(crate) fn make_mode_iterator<H: VmHost>(vm: &mut H, inner: JsValue, next_fn: *const ()) -> JsValue {
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
let wrapper = vm
.epoch()
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_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;
let next_si = vm.kernel_core().perm_interner().intern("next").0;
let wrapper_obj = unsafe { &mut *wrapper };
vm.set_or_create_prop_value(wrapper_obj, inner_si, inner);
vm.set_or_create_prop_value(wrapper_obj, index_si, JsValue::int(0));
let next = make_native_function(vm, "next", next_fn, 0);
vm.set_or_create_prop_value(wrapper_obj, next_si, next);
JsValue::from_js_object(wrapper)
}