Files
OxideJS-New/crates/oxide_builtins/src/array/from.rs
T

431 lines
19 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Array 构造器与静态方法(constructor / isArray / of / from)及 species 构造辅助。
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::{JsObject, PropAttributes, MAX_DENSE_PROPS};
use oxide_types::private_key::make_well_known_symbol_key;
use oxide_types::value::JsValue;
use oxide_runtime_api::{NativeResult, VmHost};
use super::common::{
array_length_arg, array_type_error, arraylike_get, arraylike_get_or_err, create_new_array, invoke_native_callback,
is_constructor_value, js_array_index, require_callback, string_arraylike_units, unexpected_tail_call_error,
unit_string_value,
};
/// JS `Array()` 构造逻辑:单个数字参数创建指定长度空数组,其余情况把参数作为元素。
pub fn array_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let proto_val = JsValue::from_js_object(proto);
if args.len() == 2 {
let val = vm.reg(args[1]);
if val.is_int() || val.is_double() {
let n = match array_length_arg(vm, val) {
Ok(n) => n,
Err(err) => return NativeResult::Err(err),
};
let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, proto_val, n, vm.epoch().bump()));
// 数值长度分支建 n 个空洞而非 present-undefined:`in`、for-in、
// Object.keys 按缺失处理,元素 Get 落原型链。
for i in 0..n {
unsafe {
(*arr).mark_hole_at(i);
}
}
return NativeResult::Ok(JsValue::from_js_object(arr));
}
}
let n_elems = if args.len() > 1 { args.len() - 1 } else { 0 };
let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, proto_val, n_elems, vm.epoch().bump()));
for i in 0..n_elems {
unsafe {
(*arr).set_prop_at(i, vm.reg(args[1 + i]));
}
}
unsafe {
(*arr).set_prop_count(n_elems);
}
NativeResult::Ok(JsValue::from_js_object(arr))
}
/// `Array.isArray(value)`:参数是否为真正的 Array 对象。
pub fn array_is_array<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return NativeResult::Ok(JsValue::bool(false));
}
let val = vm.reg(args[1]);
if !val.is_object() {
return NativeResult::Ok(JsValue::bool(false));
}
let ptr = val.as_js_object_ptr();
if ptr.is_null() {
return NativeResult::Ok(JsValue::bool(false));
}
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,构造结果对象,逐索引取值。
// 字符串源(原始串/装箱串)的 length 与索引未物化,直接按 UTF-16 单元读。
let string_units = string_arraylike_units(vm, items);
let obj_val = match &string_units {
Some(_) => JsValue::undefined(),
None => match oxide_runtime_api::to_object(items, vm) {
Ok(o) => o,
Err(err) => return NativeResult::Err(crate::error::create_type_error(vm, &err)),
},
};
let len = match &string_units {
Some(units) => units.len().min(MAX_DENSE_PROPS),
None => {
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_u64 = oxide_runtime_api::to_length(len_val);
if len_u64 > u32::MAX as u64 {
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid array length"));
}
len_u64.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 = match &string_units {
Some(units) => unit_string_value(vm, units[i]),
None => arraylike_get_or_err!(vm, obj_val.as_js_object_ptr(), i),
};
let mapped = match mapping {
Some(cb) => match invoke_native_callback(vm, cb, this_arg, &[elem, js_array_index(i)]) {
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))
}
/// 引擎调用边界抛出的错误恢复为原始异常值(透传),否则按错误文本构造对应 kind 的错误。
pub(crate) fn from_engine_error<H: VmHost>(vm: &mut H, err: &str) -> JsValue {
vm.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_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)),
}
}
/// ArraySpeciesCreate(originalArray, length):真数组读取 constructor / @@species
/// 并构造目标对象;array-like(非数组)直接 ArrayCreate(length)。
pub(crate) fn array_species_create<H: VmHost>(
vm: &mut H, o_val: JsValue, is_array: bool, count: usize,
) -> Result<*mut JsObject, JsValue> {
if !is_array {
return Ok(create_new_array(vm, count));
}
let o_ptr = o_val.as_js_object_ptr();
let o_obj = unsafe { &*o_ptr };
let ctor_key = vm.kernel_core().perm_interner().intern("constructor").0;
let mut c = match vm.ordinary_get(o_obj, ctor_key, o_val) {
Ok(v) => v,
Err(msg) => return Err(from_engine_error(vm, &msg)),
};
if !is_constructor_value(c) {
if !c.is_object() {
return Err(crate::error::create_type_error(vm, "Species constructor not a constructor"));
}
let species_key = make_well_known_symbol_key(10);
let s = match vm.ordinary_get(unsafe { &*c.as_js_object_ptr() }, species_key, c) {
Ok(v) => v,
Err(msg) => return Err(from_engine_error(vm, &msg)),
};
c = if s.is_null() { JsValue::undefined() } else { s };
}
if c.is_undefined() {
return Ok(create_new_array(vm, count));
}
if !is_constructor_value(c) {
return Err(crate::error::create_type_error(vm, "Species constructor not a constructor"));
}
match construct_array_from_result(vm, c, &[js_array_index(count)]) {
Ok((ptr, _)) => Ok(ptr),
Err(err) => Err(err),
}
}
/// CreateDataPropertyOrThrow:以 `{writable, enumerable, configurable}=true` 写入属性,
/// 对象不可扩展或与既有不可配置属性冲突时抛 TypeError。
pub(crate) 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, true) {
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() {
// return() 的抛错被忽略,其值不得外泄进槽覆盖在途异常。
let saved_uncaught = vm.take_uncaught_value();
let _ = vm.call_function_sync(ret, iterator, &[]);
vm.restore_uncaught_value(saved_uncaught);
}
}
}