feat: 实现 Array.from 与 Array.of 静态方法

- Array.from:可迭代(迭代协议)与 array-like 双路径,支持 mapFn/thisArg,
  构造器 this 支持(new C),CreateDataPropertyOrThrow 写入,IteratorClose
- Array.of:以参数为元素,this 构造器 + CreateDataProperty + Set length
- iterator.rs:get_iterator 改为 Option 返回,新增 try_make_iterator 与
  peek_iterator_method(GetMethod 只读判定,不调用迭代器)
This commit is contained in:
爱特每羊队
2026-08-08 13:13:08 +08:00
parent a814dd0700
commit 6e5bbe718b
4 changed files with 389 additions and 9 deletions
+305 -1
View File
@@ -1,5 +1,5 @@
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::{JsObject, MAX_DENSE_PROPS};
use oxide_types::object::{JsObject, PropAttributes, MAX_DENSE_PROPS};
use oxide_types::value::JsValue;
use oxide_runtime_api::{NativeResult, VmHost};
@@ -230,6 +230,310 @@ pub fn array_is_array<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
NativeResult::Ok(JsValue::bool(unsafe { &*ptr }.is_array()))
}
/// `Array.of(...items)`:以参数为元素构造新数组。
///
/// # 步骤
/// 1. 按 `this`(构造函数 C)以参数个数构造结果对象:C 可构造时 `new C(len)`,
/// 否则新建普通数组。
/// 2. 元素逐个以 CreateDataProperty 语义写入结果对象。
/// 3. 收尾设置结果对象的 length(可触发继承的 length setter)。
pub fn array_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let n_elems = if args.len() > 1 { args.len() - 1 } else { 0 };
let c = vm.reg(args[0]);
let (a_ptr, is_array) = match construct_array_from_result(vm, c, &[JsValue::int(n_elems as i32)]) {
Ok(v) => v,
Err(err) => return NativeResult::Err(err),
};
for i in 0..n_elems {
let val = vm.reg(args[1 + i]);
if let Err(err) = array_from_set_prop(vm, a_ptr, is_array, i, val) {
return NativeResult::Err(err);
}
}
if let Err(err) = array_from_set_length(vm, a_ptr, is_array, n_elems) {
return NativeResult::Err(err);
}
NativeResult::Ok(JsValue::from_js_object(a_ptr))
}
/// `Array.from(items, mapFn?, thisArg?)`:从可迭代对象或 array-like 构造新数组,
/// 可选 `mapFn` 逐元素映射(`thisArg` 作回调 this),返回新数组。
///
/// # 步骤
/// 1. `items` 为 null/undefined 抛 TypeError;`mapFn` 非 undefined 时须可调用。
/// 2. 读取 `@@iterator` 判定可迭代(不调用):可迭代路径先按 `this`(构造函数 C)
/// `new C()` 构造结果对象,再经迭代协议逐个取元素;array-like 路径先 ToObject
/// 读 `length`,再 `new C(len)` 构造结果对象,逐索引取值(缺失属性取 undefined)。
/// 3. 元素经 `mapFn` 映射后以 CreateDataProperty 语义写入结果对象,收尾设置 length。
/// 4. 迭代器或 `mapFn` 抛错时透传原异常值;迭代中途异常先执行 IteratorClose。
///
/// # 边界与前提
/// - C 不可构造(非函数/箭头/普通 native 方法)时回退到普通空数组。
/// - 写入元素遇不可扩展对象或不可配置属性冲突时抛 TypeError(CreateDataPropertyOrThrow)。
///
/// # 注意事项
/// - 迭代只读包装器 `next`/`done`/`value` 属性,与 spread 物化共用同一迭代协议。
/// - new.target 不向被构造的 C 传播(与 Reflect.construct 同一简化)。
pub fn array_from<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let items = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
if items.is_null() || items.is_undefined() {
return NativeResult::Err(array_type_error(vm, "Array.from requires an iterable or array-like object"));
}
let mapfn = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
let mapping = if mapfn.is_undefined() {
None
} else {
match require_callback(vm, mapfn) {
Ok(cb) => Some(cb),
Err(err) => return NativeResult::Err(err),
}
};
let this_arg = if args.len() > 3 { vm.reg(args[3]) } else { JsValue::undefined() };
let c = vm.reg(args[0]);
// 先只读取 @@iterator 判定可迭代(不调用),保证"构造结果对象"先于"获取迭代器",
// 与规范的执行序一致。
let iterable = match crate::iterator::peek_iterator_method(vm, items) {
Ok(b) => b,
Err(err) => return NativeResult::Err(err),
};
let a_ptr;
let is_array;
if iterable {
// 可迭代路径:先 new C() 得到结果对象,再获取迭代器逐个取元素。
a_ptr = match construct_array_from_result(vm, c, &[]) {
Ok((ptr, is_arr)) => {
is_array = is_arr;
ptr
}
Err(err) => return NativeResult::Err(err),
};
let iterator = match crate::iterator::make_iterator_for_value(vm, items) {
Ok(it) => it,
Err(err) => return NativeResult::Err(err),
};
let next_si = vm.kernel_core().perm_interner().intern("next").0;
let done_si = vm.kernel_core().perm_interner().intern("done").0;
let value_si = vm.kernel_core().perm_interner().intern("value").0;
let mut k = 0usize;
let iter_result: Result<(), JsValue> = (|| {
loop {
let iter_obj = unsafe { &*iterator.as_js_object_ptr() };
let next_fn = match vm.ordinary_get(iter_obj, next_si, iterator) {
Ok(v) => v,
Err(err) => return Err(from_engine_error(vm, &err)),
};
let result = match vm.call_function_sync(next_fn, iterator, &[]) {
Ok(v) => v,
Err(err) => return Err(from_engine_error(vm, &err)),
};
if !result.is_object() {
return Err(array_type_error(vm, "iterator result is not an object"));
}
let result_obj = unsafe { &*result.as_js_object_ptr() };
let done = match vm.ordinary_get(result_obj, done_si, result) {
Ok(v) => oxide_runtime_api::to_boolean(v),
Err(err) => return Err(from_engine_error(vm, &err)),
};
if done {
break;
}
let elem = match vm.ordinary_get(result_obj, value_si, result) {
Ok(v) => v,
Err(err) => return Err(from_engine_error(vm, &err)),
};
let mapped = match mapping {
Some(cb) => match invoke_native_callback(vm, cb, this_arg, &[elem, JsValue::int(k as i32)]) {
NativeResult::Ok(m) => m,
NativeResult::Err(err) => return Err(err),
NativeResult::TailCall { .. } => {
return Err(array_type_error(vm, "unexpected tail call in array callback"))
}
},
None => elem,
};
array_from_set_prop(vm, a_ptr, is_array, k, mapped)?;
k += 1;
}
Ok(())
})();
if let Err(err) = iter_result {
// IteratorClose:异常退出先关闭迭代器(return() 转发给内层),再抛原异常。
close_iterator(vm, iterator);
return NativeResult::Err(err);
}
if let Err(err) = array_from_set_length(vm, a_ptr, is_array, k) {
return NativeResult::Err(err);
}
} else {
// array-like 路径:ToObject 后读 length,构造结果对象,逐索引取值。
let obj_val = match oxide_runtime_api::to_object(items, vm) {
Ok(o) => o,
Err(err) => return NativeResult::Err(crate::error::create_type_error(vm, &err)),
};
let obj_ptr = obj_val.as_js_object_ptr();
let obj = unsafe { &*obj_ptr };
let length_key = vm.new_string("length");
let length_si = vm.property_key_si(length_key);
let len_val = match vm.ordinary_get(obj, length_si, obj_val) {
Ok(v) => v,
Err(err) => return NativeResult::Err(from_engine_error(vm, &err)),
};
let len = oxide_runtime_api::to_length(len_val).min(MAX_DENSE_PROPS as u64) as usize;
a_ptr = match construct_array_from_result(vm, c, &[JsValue::int(len as i32)]) {
Ok((ptr, is_arr)) => {
is_array = is_arr;
ptr
}
Err(err) => return NativeResult::Err(err),
};
for i in 0..len {
let elem = arraylike_get(vm, obj_ptr, false, i);
let mapped = match mapping {
Some(cb) => match invoke_native_callback(vm, cb, this_arg, &[elem, JsValue::int(i as i32)]) {
NativeResult::Ok(m) => m,
NativeResult::Err(err) => return NativeResult::Err(err),
NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm),
},
None => elem,
};
if let Err(err) = array_from_set_prop(vm, a_ptr, is_array, i, mapped) {
return NativeResult::Err(err);
}
}
if let Err(err) = array_from_set_length(vm, a_ptr, is_array, len) {
return NativeResult::Err(err);
}
}
NativeResult::Ok(JsValue::from_js_object(a_ptr))
}
/// 引擎调用边界抛出的错误恢复为原始异常值(透传),否则按错误文本构造普通错误。
fn from_engine_error<H: VmHost>(vm: &mut H, err: &str) -> JsValue {
vm.take_uncaught_value().unwrap_or_else(|| callback_error_from_text(vm, err))
}
/// 按构造函数 C 构造 Array.from 的结果对象:C 可构造时以 C.prototype 分配 `this`
/// 后调用 C(返回非对象时回退 `this`),否则返回普通空数组。
///
/// # 返回值
/// `(结果对象指针, 是否为真数组)`。
fn construct_array_from_result<H: VmHost>(
vm: &mut H, c: JsValue, args: &[JsValue],
) -> Result<(*mut JsObject, bool), JsValue> {
let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let proto_val = JsValue::from_js_object(proto);
let mut fallback_array = || {
let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, proto_val, 0, vm.epoch().bump()));
(arr, true)
};
if !c.is_object() {
return Ok(fallback_array());
}
let c_obj = unsafe { &*c.as_js_object_ptr() };
// 不可构造:箭头函数、非构造器标记的 native 方法。
let constructible = c_obj.is_function()
&& !c_obj.is_arrow()
&& !(c_obj.native_fn().is_some() && c_obj.type_tag != JsObject::OBJ_TYPE_CONSTRUCTOR);
if !constructible {
return Ok(fallback_array());
}
// 仿 Reflect.construct:以 C.prototype 分配 this 后调用 C。
let proto_si = vm.kernel_core().perm_interner().intern("prototype").0;
let ctor_proto = match vm.resolve_property(c_obj, proto_si) {
Some(p) if p.is_object() => p,
_ => JsValue::from_js_object(vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject),
};
let this_ptr = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, ctor_proto));
let this_val = JsValue::from_js_object(this_ptr);
match vm.call_function_sync(c, this_val, args) {
Ok(ret) if ret.is_object() => {
let ret_ptr = ret.as_js_object_ptr();
let ret_tag = unsafe { &*ret_ptr }.type_tag;
// 原始值包装对象(如 `Object(4)` 返回的 Number 包装)的索引属性存储不完整,
// 回退到刚分配的对象;真数组/普通对象按规范使用构造返回对象。
if matches!(
ret_tag,
JsObject::OBJ_TYPE_STRING_OBJ | JsObject::OBJ_TYPE_NUMBER_OBJ | JsObject::OBJ_TYPE_BOOLEAN_OBJ
) {
return Ok((this_ptr, false));
}
Ok((ret_ptr, unsafe { &*ret_ptr }.is_array()))
}
Ok(_) => Ok((this_ptr, false)),
Err(err) => Err(from_engine_error(vm, &err)),
}
}
/// CreateDataPropertyOrThrow:以 `{writable, enumerable, configurable}=true` 写入属性,
/// 对象不可扩展或与既有不可配置属性冲突时抛 TypeError。
fn create_data_property_or_throw<H: VmHost>(
vm: &mut H, obj: &mut JsObject, prop_name_si: u32, val: JsValue,
) -> Result<(), JsValue> {
if vm.get_own_property_slot(obj, prop_name_si).is_none() && !obj.is_extensible() {
return Err(array_type_error(vm, "Cannot define property: object is not extensible"));
}
match vm.define_data_property(obj, prop_name_si, val, PropAttributes::new(true, true, true)) {
Ok(()) => Ok(()),
Err(err) => Err(crate::error::create_type_error(vm, &err)),
}
}
/// 把元素写入 Array.from 的结果对象:真数组走密集元素区,普通对象走
/// CreateDataProperty 语义(属性名按十进制索引字符串)。
fn array_from_set_prop<H: VmHost>(
vm: &mut H, a: *mut JsObject, is_array: bool, i: usize, val: JsValue,
) -> Result<(), JsValue> {
if is_array {
unsafe {
(*a).set_prop_at(i, val);
}
return Ok(());
}
let key = vm.new_string(&i.to_string());
let key_si = vm.property_key_si(key);
create_data_property_or_throw(vm, unsafe { &mut *a }, key_si, val)
}
/// 收尾设置 Array.from 结果对象的 length:真数组直接改元素计数,普通对象走
/// 普通 Set(可触发继承的 length setter 并透传其异常)。
fn array_from_set_length<H: VmHost>(vm: &mut H, a: *mut JsObject, is_array: bool, len: usize) -> Result<(), JsValue> {
if is_array {
unsafe {
(*a).set_prop_count_fast(len);
}
return Ok(());
}
let a_obj = unsafe { &mut *a };
let length_key = vm.new_string("length");
let length_si = vm.property_key_si(length_key);
let a_val = JsValue::from_js_object(a);
match vm.ordinary_set(a_obj, length_si, JsValue::int(len as i32), a_val) {
Ok(()) => Ok(()),
Err(err) => Err(from_engine_error(vm, &err)),
}
}
/// IteratorClose:迭代异常退出时调用迭代器包装器的 `return()`(转发给内层迭代器),
/// 丢弃其自身错误,保留在途异常。
fn close_iterator<H: VmHost>(vm: &mut H, iterator: JsValue) {
if !iterator.is_object() {
return;
}
let iter_obj = unsafe { &*iterator.as_js_object_ptr() };
let return_si = vm.kernel_core().perm_interner().intern("return").0;
if let Ok(ret) = vm.ordinary_get(iter_obj, return_si, iterator) {
if ret.is_object() && unsafe { &*ret.as_js_object_ptr() }.is_function() {
let _ = vm.call_function_sync(ret, iterator, &[]);
}
}
}
/// `Array.prototype.push(...items)`:追加元素到尾部,返回新长度。
pub fn array_push<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("Array.prototype.push called with {} args", args.len());
+71 -7
View File
@@ -25,7 +25,30 @@ pub fn iterator_from<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// 为任意值创建统一迭代器包装对象:String/Array/Map/Set 直接支持索引遍历,
/// 其它对象则要求提供可调用的 `next`。不可迭代时返回 TypeError。
pub fn make_iterator_for_value<H: VmHost>(vm: &mut H, value: JsValue) -> Result<JsValue, JsValue> {
let inner = get_iterator(vm, value)?;
match try_make_iterator(vm, value) {
Ok(Some(iterator)) => Ok(iterator),
Ok(None) => Err(crate::error::create_type_error(vm, "value is not iterable")),
Err(err) => Err(err),
}
}
/// 尝试创建迭代器包装对象,把"不可迭代"与"真异常"区分返回。
///
/// # 步骤
/// 1. 经迭代协议取内层迭代器(String/Array/Map/Set 直接作为内层,其余对象调用
/// `@@iterator` 或回退可调用的 `next`)。
/// 2. 包装成统一迭代器对象(带 `next` 与 `return`),供调用方逐个取元素。
///
/// # 返回值
/// - `Ok(Some(iterator))`:可迭代,返回包装器;
/// - `Ok(None)`:不可迭代(调用方回退 array-like 路径);
/// - `Err`:`@@iterator` getter/call 抛错,透传原异常值。
pub(crate) fn try_make_iterator<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Option<JsValue>, JsValue> {
let inner = match get_iterator(vm, value) {
Ok(Some(inner)) => 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;
let wrapper = vm
.epoch()
@@ -46,7 +69,7 @@ pub fn make_iterator_for_value<H: VmHost>(vm: &mut H, value: JsValue) -> Result<
let return_fn = make_native_function(vm, "return", iterator_wrapper_return::<H> as *const (), 0);
vm.set_or_create_prop_value(wrapper_obj, return_si, return_fn);
Ok(JsValue::from_js_object(wrapper))
Ok(Some(JsValue::from_js_object(wrapper)))
}
fn iterator_wrapper_return<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
@@ -118,14 +141,55 @@ pub fn iterator_wrapper_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
NativeResult::Err(crate::error::create_type_error(vm, "value is not iterable"))
}
fn get_iterator<H: VmHost>(vm: &mut H, value: JsValue) -> Result<JsValue, JsValue> {
/// 判断 value 是否可迭代,只读取 `@@iterator` 方法而不调用它(GetMethod 语义)。
///
/// 与 [`get_iterator`] 的判定一致:内建集合(String/Array/TypedArray/Map/Set)恒可迭代;
/// 其它对象读取 `@@iterator`,可调用即视为可迭代,否则回退到自身可调用的 `next`。
/// `@@iterator` getter 抛错时透传 `Err`。
pub(crate) fn peek_iterator_method<H: VmHost>(vm: &mut H, value: JsValue) -> Result<bool, JsValue> {
if value.is_string()
|| is_array_value(value)
|| is_typed_array_value(value)
|| is_map_value(value)
|| is_set_value(value)
{
return Ok(value);
return Ok(true);
}
if value.is_object() {
let obj = unsafe { &*value.as_js_object_ptr() };
let sym_iter_si = vm.kernel_core().perm_interner().intern("@@iterator").0;
let method = match vm.ordinary_get(obj, sym_iter_si, value) {
Ok(m) => m,
Err(err) => {
// GetMethod 取 @@iterator 时 getter 抛出:透传原值,不落入鸭子回退。
let exc = vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_type_error(vm, &err));
return Err(exc);
}
};
if is_callable(method) {
return Ok(true);
}
// 鸭子回退:对象自身有可调用 next。
let next_si = vm.kernel_core().perm_interner().intern("next").0;
if let Ok(next) = vm.ordinary_get(obj, next_si, value) {
if is_callable(next) {
return Ok(true);
}
}
}
Ok(false)
}
fn get_iterator<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Option<JsValue>, JsValue> {
if value.is_string()
|| is_array_value(value)
|| is_typed_array_value(value)
|| is_map_value(value)
|| is_set_value(value)
{
return Ok(Some(value));
}
if value.is_object() {
@@ -158,18 +222,18 @@ fn get_iterator<H: VmHost>(vm: &mut H, value: JsValue) -> Result<JsValue, JsValu
"Result of the Symbol.iterator method is not an object",
));
}
return Ok(iterator);
return Ok(Some(iterator));
}
// 鸭子回退:对象自身有可调用 next(Map/Set 迭代器包装等既有用法)。
let next_si = vm.kernel_core().perm_interner().intern("next").0;
if let Ok(next) = vm.ordinary_get(obj, next_si, value) {
if is_callable(next) {
return Ok(value);
return Ok(Some(value));
}
}
}
Err(crate::error::create_type_error(vm, "value is not iterable"))
Ok(None)
}
fn next_array_like<H: VmHost>(
+11 -1
View File
@@ -69,6 +69,8 @@ pub struct ObjectMethods {
/// Array 静态方法与原型方法的 native 函数指针集合,由 [`BuiltinWorld::bind_array_methods`] 安装。
pub struct ArrayMethods {
pub is_array: *const (),
pub from: *const (),
pub of: *const (),
pub push: *const (),
pub pop: *const (),
pub slice: *const (),
@@ -967,7 +969,15 @@ impl BuiltinWorld {
pub fn bind_array_methods(&self, methods: &ArrayMethods, string_forge: &PermInterner, shape_forge: &ShapeForge) {
let ctor_ptr = P::as_ptr(&self.array_constructor) as *mut JsObject;
let ctor = unsafe { &mut *ctor_ptr };
bind_methods!(self, ctor, string_forge, shape_forge, ("isArray", methods.is_array, 1),);
bind_methods!(
self,
ctor,
string_forge,
shape_forge,
("isArray", methods.is_array, 1),
("from", methods.from, 1),
("of", methods.of, 0),
);
let proto_ptr = P::as_ptr(&self.array_proto) as *mut JsObject;
let proto = unsafe { &mut *proto_ptr };
@@ -11,6 +11,8 @@ use crate::bindings::apply_binding_table;
pub fn bind_array(core: &Arc<KernelCore>, session: &KernelSession, global: &mut JsObject) {
let _array_methods = ArrayMethods {
is_array: oxide_builtins::array::array_is_array::<crate::vm::Vm> as *const (),
from: oxide_builtins::array::array_from::<crate::vm::Vm> as *const (),
of: oxide_builtins::array::array_of::<crate::vm::Vm> as *const (),
push: oxide_builtins::array::array_push::<crate::vm::Vm> as *const (),
pop: oxide_builtins::array::array_pop::<crate::vm::Vm> as *const (),
slice: oxide_builtins::array::array_slice::<crate::vm::Vm> as *const (),