From 0bc8c847b59cdd8ad473ec88a256466464b256c1 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 8 Aug 2026 13:13:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20Array.from=20?= =?UTF-8?q?=E4=B8=8E=20Array.of=20=E9=9D=99=E6=80=81=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 只读判定,不调用迭代器) --- crates/oxide_builtins/src/array.rs | 306 ++++++++++++++++++++- crates/oxide_builtins/src/iterator.rs | 78 +++++- crates/oxide_kernel/src/builtin.rs | 12 +- crates/oxide_vm/src/bindings/bind_array.rs | 2 + 4 files changed, 389 insertions(+), 9 deletions(-) diff --git a/crates/oxide_builtins/src/array.rs b/crates/oxide_builtins/src/array.rs index 5e45b3db..80bbbc4e 100644 --- a/crates/oxide_builtins/src/array.rs +++ b/crates/oxide_builtins/src/array.rs @@ -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(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(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(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(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( + 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( + 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( + 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(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(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(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.push called with {} args", args.len()); diff --git a/crates/oxide_builtins/src/iterator.rs b/crates/oxide_builtins/src/iterator.rs index 7e5f8bac..6107573f 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -25,7 +25,30 @@ pub fn iterator_from(vm: &mut H, args: &[u8]) -> NativeResult { /// 为任意值创建统一迭代器包装对象:String/Array/Map/Set 直接支持索引遍历, /// 其它对象则要求提供可调用的 `next`。不可迭代时返回 TypeError。 pub fn make_iterator_for_value(vm: &mut H, value: JsValue) -> Result { - 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(vm: &mut H, value: JsValue) -> Result, 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(vm: &mut H, value: JsValue) -> Result< let return_fn = make_native_function(vm, "return", iterator_wrapper_return:: 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(vm: &mut H, args: &[u8]) -> NativeResult { @@ -118,14 +141,55 @@ pub fn iterator_wrapper_next(vm: &mut H, args: &[u8]) -> NativeResult NativeResult::Err(crate::error::create_type_error(vm, "value is not iterable")) } -fn get_iterator(vm: &mut H, value: JsValue) -> Result { +/// 判断 value 是否可迭代,只读取 `@@iterator` 方法而不调用它(GetMethod 语义)。 +/// +/// 与 [`get_iterator`] 的判定一致:内建集合(String/Array/TypedArray/Map/Set)恒可迭代; +/// 其它对象读取 `@@iterator`,可调用即视为可迭代,否则回退到自身可调用的 `next`。 +/// `@@iterator` getter 抛错时透传 `Err`。 +pub(crate) fn peek_iterator_method(vm: &mut H, value: JsValue) -> Result { 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(vm: &mut H, value: JsValue) -> Result, 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(vm: &mut H, value: JsValue) -> Result( diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index e1b58715..c362c0f1 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -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 }; diff --git a/crates/oxide_vm/src/bindings/bind_array.rs b/crates/oxide_vm/src/bindings/bind_array.rs index 7316c53a..e2accbb4 100644 --- a/crates/oxide_vm/src/bindings/bind_array.rs +++ b/crates/oxide_vm/src/bindings/bind_array.rs @@ -11,6 +11,8 @@ use crate::bindings::apply_binding_table; pub fn bind_array(core: &Arc, session: &KernelSession, global: &mut JsObject) { let _array_methods = ArrayMethods { is_array: oxide_builtins::array::array_is_array:: as *const (), + from: oxide_builtins::array::array_from:: as *const (), + of: oxide_builtins::array::array_of:: as *const (), push: oxide_builtins::array::array_push:: as *const (), pop: oxide_builtins::array::array_pop:: as *const (), slice: oxide_builtins::array::array_slice:: as *const (),