feat: 实现 Promise.allKeyed 与 keyed 共享核

This commit is contained in:
2026-09-24 08:46:04 +00:00
parent d0b47a6610
commit 821e24cd0b
4 changed files with 311 additions and 7 deletions
+300 -4
View File
@@ -1,21 +1,27 @@
//! Promise 静态聚合方法(all/race/allSettled/any/withResolvers)与 //! Promise 静态聚合方法(all/race/allSettled/any/allKeyed 族/withResolvers)与
//! AggregateError 内建初始化。 //! AggregateError 内建初始化。
//! //!
//! 元素处理器共享结算计数记录(剩余计数 / 结果数组 / 能力闭包); //! 元素处理器共享结算计数记录(剩余计数 / 结果数组 / 能力闭包);
//! 计数从 1 起步,尾部哨兵计入整趟迭代。 //! 计数从 1 起步,尾部哨兵计入整趟迭代。
//! keyed 族(allKeyed / allSettledKeyed)与 all 族共用能力 / 元素函数 /
//! 结算路径,差异仅在元素来源(own keys 枚举替代迭代器)与结果承载
//! (null 原型键控对象替代数组)。
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_runtime_api::NativeResult; use oxide_runtime_api::NativeResult;
use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes}; use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes};
use oxide_types::private_key::make_int_key; use oxide_types::private_key::{
is_symbol_key, make_int_key, make_symbol_key, make_well_known_symbol_key, symbol_index_from_key,
well_known_symbol_id_from_key, WELL_KNOWN_SYMBOL_COUNT,
};
use oxide_types::value::JsValue; use oxide_types::value::JsValue;
use crate::native::NativeFn; use crate::native::NativeFn;
use crate::vm::Vm; use crate::vm::Vm;
use super::{ use super::{
AggregateKind, AGG_ALREADY_PROP, AGG_INDEX_PROP, AGG_RECORD_PROP, AGG_REJECT_PROP, AGG_REMAINING_PROP, AggregateKind, AGG_ALREADY_PROP, AGG_INDEX_PROP, AGG_KEYS_PROP, AGG_RECORD_PROP, AGG_REJECT_PROP,
AGG_RESOLVE_PROP, AGG_VALUES_PROP, TRY_ARGS_PROP, TRY_EXECUTOR_PROP, AGG_REMAINING_PROP, AGG_RESOLVE_PROP, AGG_VALUES_PROP, TRY_ARGS_PROP, TRY_EXECUTOR_PROP,
}; };
impl Vm { impl Vm {
@@ -37,6 +43,26 @@ impl Vm {
JsValue::from_js_object(ptr) JsValue::from_js_object(ptr)
} }
/// 创建 keyed 聚合静态方法的共享记录对象:剩余计数(初始 1)、结果平行数组、
/// 键平行数组与能力 resolve/reject 闭包存为自身属性。
fn make_agg_record_keyed(&mut self, values: JsValue, keys: JsValue, resolve: JsValue, reject: JsValue) -> JsValue {
let proto_val = JsValue::from_js_object(self.session.builtin_world().object_proto.as_ptr() as *mut JsObject);
let ptr = self.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val));
// SAFETY: ptr 由 alloc_object 新建,返回非空 arena 指针;本次 native 调用内不搬移,借出期间无别名。
let obj = unsafe { &mut *ptr };
let rem_si = self.kernel_core.perm_interner().intern(AGG_REMAINING_PROP).0;
self.set_or_create_prop_value(obj, rem_si, JsValue::int(1));
let val_si = self.kernel_core.perm_interner().intern(AGG_VALUES_PROP).0;
self.set_or_create_prop_value(obj, val_si, values);
let key_si = self.kernel_core.perm_interner().intern(AGG_KEYS_PROP).0;
self.set_or_create_prop_value(obj, key_si, keys);
let res_si = self.kernel_core.perm_interner().intern(AGG_RESOLVE_PROP).0;
self.set_or_create_prop_value(obj, res_si, resolve);
let rej_si = self.kernel_core.perm_interner().intern(AGG_REJECT_PROP).0;
self.set_or_create_prop_value(obj, rej_si, reject);
JsValue::from_js_object(ptr)
}
/// 创建聚合静态方法的元素处理器闭包:携带共享记录与元素下标,`already` 标志 /// 创建聚合静态方法的元素处理器闭包:携带共享记录与元素下标,`already` 标志
/// 初始 false。内部状态属性非枚举且追加在 length/name 之后,保持内建函数 /// 初始 false。内部状态属性非枚举且追加在 length/name 之后,保持内建函数
/// "length 先于 name" 的属性序。 /// "length 先于 name" 的属性序。
@@ -370,6 +396,17 @@ pub(super) fn promise_static_any(vm: &mut Vm, args: &[u8]) -> NativeResult {
} }
} }
/// `Promise.allKeyed(object)`:输入自身可枚举键的元素全部结算后以 null 原型
/// 键控结果对象完成,任一元素拒绝则拒绝。
pub(super) fn promise_static_all_keyed(vm: &mut Vm, args: &[u8]) -> NativeResult {
let ctor = vm.reg(if args.is_empty() { 0 } else { args[0] });
let promises = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
match perform_promise_combine_keyed(vm, ctor, promises, KeyedVariant::All) {
Ok(promise) => NativeResult::Ok(promise),
Err(err) => NativeResult::Err(err),
}
}
/// 聚合静态方法的共享核心:迭代可迭代输入,逐元素调 `C.resolve` 建 promise 并注册反应, /// 聚合静态方法的共享核心:迭代可迭代输入,逐元素调 `C.resolve` 建 promise 并注册反应,
/// 按模式在全部/任一结算后交付能力 promise。 /// 按模式在全部/任一结算后交付能力 promise。
/// ///
@@ -536,6 +573,200 @@ fn perform_promise_combine(
Ok(promise) Ok(promise)
} }
/// keyed 聚合族的语义模式:决定元素处理器形态(all 拒绝侧直拒 /
/// allSettled 双侧记录)。
#[derive(Clone, Copy, PartialEq, Eq)]
enum KeyedVariant {
All,
// allSettledKeyed 入口尚未挂入构造器静态表,核心实现先行备齐。
#[expect(dead_code)]
AllSettled,
}
/// keyed 聚合静态方法(allKeyed / allSettledKeyed)共享核心:按 own keys 序
/// 枚举输入对象的可枚举自身键,逐元素包装 promise 并注册反应,全部结算后
/// 以 null 原型键控结果对象交付能力 promise。
///
/// # 步骤
/// 1. 建能力(NewPromiseCapability(C) 抛错同步上抛);取 C.resolve
/// (读错 / 不可调用 → 拒绝)。
/// 2. 非对象输入 → 异步以新 TypeError 拒绝(键枚举之前)。
/// 3. 按 OwnKeys 序枚举(整数键升序、字符串键与符号键插入序):可枚举键
/// 经完整 Get 取值,`Call(C.resolve, C, «value»)` 包装,注册元素处理器
/// (剩余计数先 +1 再注册)。
/// 4. 循环尾部哨兵计数递减,归 0 时建结果对象并结算。
///
/// # 边界与前提
/// - Get / resolve 调用 / then 注册抛错均异步拒绝能力 promise
/// (IfAbruptRejectPromise),不同步抛出;仅 NewPromiseCapability 同步传播。
/// - ownKeys 枚举在非 Proxy 对象上无抛点(本引擎无 Proxy);描述符读取与
/// Get 同走现件路径。
fn perform_promise_combine_keyed(
vm: &mut Vm, ctor: JsValue, promises: JsValue, variant: KeyedVariant,
) -> Result<JsValue, JsValue> {
// NewPromiseCapability(C):本函数唯一同步传播臂。
let (promise, resolve, reject) = vm.new_promise_capability_with_ctor(ctor)?;
// GetPromiseResolve(C):读取抛错或不可调用 → 以原抛出值拒绝。
let resolve_si = vm.kernel_core.perm_interner().intern("resolve").0;
let promise_resolve = if ctor.is_object() {
// SAFETY: ctor 是存活对象。
let ctor_obj = unsafe { &*ctor.as_js_object_ptr() };
match vm.ordinary_get(ctor_obj, resolve_si, ctor) {
Ok(v) => v,
Err(e) => {
let exc = agg_engine_error(vm, &e);
let _ = vm.call_function_sync(reject, JsValue::undefined(), &[exc]);
return Ok(promise);
}
}
} else {
JsValue::undefined()
};
if !oxide_builtins::iterator::is_callable(promise_resolve) {
let exc = oxide_builtins::error::create_type_error(vm, "resolve is not a function");
let _ = vm.call_function_sync(reject, JsValue::undefined(), &[exc]);
return Ok(promise);
}
// 非对象输入:能力已建、键枚举之前,异步以新 TypeError 拒绝。
if !promises.is_object() {
let exc = oxide_builtins::error::create_type_error(vm, "promises argument is not an object");
let _ = vm.call_function_sync(reject, JsValue::undefined(), &[exc]);
return Ok(promise);
}
// 平行承载:values 结果数组与 keys 键值数组同下标对齐,记录对象持有两者。
// SAFETY: promises 是存活对象。
let promises_obj = unsafe { &*promises.as_js_object_ptr() };
let values = vm.make_plain_array(Vec::new());
let keys = vm.make_plain_array(Vec::new());
let record = vm.make_agg_record_keyed(values, keys, resolve, reject);
// OwnKeys 序:整数键升序、字符串键插入序;符号键按插入序追加(shape 位置
// 换算绝对存储下标,与 walk_own_keys 口径一致)。
let mut all_keys = oxide_builtins::object::walk_own_keys(vm, promises_obj);
for sym in oxide_builtins::object::own_symbol_key_values(vm, promises_obj) {
let idx = sym.as_symbol_index();
let si = if idx < WELL_KNOWN_SYMBOL_COUNT {
make_well_known_symbol_key(idx)
} else {
make_symbol_key(idx)
};
if let Some(pos) = vm.kernel_core.shape_forge().lookup_position(promises_obj.shape_id(), si) {
let store = if promises_obj.is_array() { promises_obj.array_prop_count + pos } else { pos };
all_keys.push((si, store));
}
}
let mut index: i32 = 0;
for (si, store) in all_keys {
// 可枚举性:TA 元素键(哨兵存储下标)恒在场且可枚举;其余查元数据区
// (无元数据 = 默认数据描述符,可枚举)。
let enumerable = if store == u32::MAX {
true
} else {
promises_obj
.prop_meta_at(store)
.map_or(true, |m| !m.is_hole() && m.attributes.enumerable())
};
if !enumerable {
continue;
}
// Get:完整读(走原型链,访问器触发);抛错异步拒绝。
let value = match vm.ordinary_get(promises_obj, si, promises) {
Ok(v) => v,
Err(e) => {
let exc = agg_engine_error(vm, &e);
let _ = vm.call_function_sync(reject, JsValue::undefined(), &[exc]);
return Ok(promise);
}
};
// 键值物化进 keys 数组(与 values 下标平行)。
let key_value = key_si_to_key_value(vm, si);
// SAFETY: keys 是本函数新建的存活数组对象;元素区直写不触发数组 setter。
vm.set_or_create_prop_value(unsafe { &mut *keys.as_js_object_ptr() }, make_int_key(index as u32), key_value);
// nextPromise = Call(C.resolve, C, «value»);抛错异步拒绝。
let next_promise = match vm.call_function_sync(promise_resolve, ctor, &[value]) {
Ok(p) => p,
Err(e) => {
let exc = agg_engine_error(vm, &e);
let _ = vm.call_function_sync(reject, JsValue::undefined(), &[exc]);
return Ok(promise);
}
};
// 剩余计数先 +1 再注册:thenable 同步结算时 handler 依赖计数已含自身。
let cur = agg_read_remaining(vm, record);
agg_write_remaining(vm, record, cur + 1);
// 元素处理器按模式注册:all 变体拒绝侧直挂能力 reject,
// allSettled 变体双侧用记录元素函数。
let outcome = match variant {
KeyedVariant::All => {
let re = vm.make_agg_element_fn(promise_keyed_resolve_element, record, index);
vm.invoke_then(next_promise, &[re, reject])
}
KeyedVariant::AllSettled => {
let re = vm.make_agg_element_fn(promise_keyed_settled_resolve_element, record, index);
let rj = vm.make_agg_element_fn(promise_keyed_settled_reject_element, record, index);
vm.invoke_then(next_promise, &[re, rj])
}
};
if let Err(exc) = outcome {
let _ = vm.call_function_sync(reject, JsValue::undefined(), &[exc]);
return Ok(promise);
}
index += 1;
}
// 尾部哨兵递减;归 0 时结算(键非空时 thenable 同步结算亦可发生)。
let remaining = agg_read_remaining(vm, record) - 1;
agg_write_remaining(vm, record, remaining);
if remaining == 0 {
settle_keyed_aggregate(vm, record);
}
Ok(promise)
}
/// 键 si 物化为 JS 可见键值:字符串 / 数字串经文本还原,符号键还原为符号值。
fn key_si_to_key_value(vm: &mut Vm, si: u32) -> JsValue {
if is_symbol_key(si) {
if let Some(id) = well_known_symbol_id_from_key(si) {
return JsValue::symbol(id);
}
return JsValue::symbol(symbol_index_from_key(si));
}
oxide_builtins::object::key_si_to_js_value(vm, si)
}
/// 结算 keyed 聚合:按 keys/values 平行数组建 null 原型结果对象(键序 =
/// 输入 OwnKeys 序),经能力 resolve 交付;结算抛错降级为拒绝。
fn settle_keyed_aggregate(vm: &mut Vm, record: JsValue) {
let keys = agg_record_val(vm, record, AGG_KEYS_PROP);
let values = agg_record_val(vm, record, AGG_VALUES_PROP);
let ptr = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
// SAFETY: ptr 由 alloc_object 新建,返回非空 arena 指针;结算循环内属性写入不搬移对象,借出期间无别名。
let obj = unsafe { &mut *ptr };
if keys.is_object() && values.is_object() {
// SAFETY: keys/values 是本次调用新建的存活平行数组;元素区读写不搬移对象。
let keys_obj = unsafe { &*keys.as_js_object_ptr() };
let values_obj = unsafe { &*values.as_js_object_ptr() };
for i in 0..keys_obj.prop_count() {
let key_val = keys_obj.get_prop_at(i);
let si = match vm.property_key_si(key_val) {
Ok(si) => si,
Err(_) => continue,
};
vm.set_or_create_prop_value(obj, si, values_obj.get_prop_at(i));
}
}
agg_call_resolve(vm, record, JsValue::from_js_object(ptr));
}
/// 聚合元素处理器的公共 prologue:取共享记录与下标;`already` 已置位返回 None /// 聚合元素处理器的公共 prologue:取共享记录与下标;`already` 已置位返回 None
/// (元素函数只生效一次),否则置位后返回记录。 /// (元素函数只生效一次),否则置位后返回记录。
fn agg_element_state(vm: &mut Vm) -> Option<(JsValue, i32)> { fn agg_element_state(vm: &mut Vm) -> Option<(JsValue, i32)> {
@@ -719,6 +950,71 @@ fn promise_any_reject_element(vm: &mut Vm, args: &[u8]) -> NativeResult {
NativeResult::Ok(JsValue::undefined()) NativeResult::Ok(JsValue::undefined())
} }
/// `Promise.allKeyed` resolve 元素:写 `values[index]`,剩余计数归零时建
/// null 原型结果对象并完成。
fn promise_keyed_resolve_element(vm: &mut Vm, args: &[u8]) -> NativeResult {
let Some((record, index)) = agg_element_state(vm) else {
return NativeResult::Ok(JsValue::undefined());
};
let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let values = agg_record_val(vm, record, AGG_VALUES_PROP);
if values.is_object() {
let key_si = make_int_key(index as u32);
// SAFETY: values 是存活数组对象;直写元素区不触发数组 setter。
vm.set_or_create_prop_value(unsafe { &mut *values.as_js_object_ptr() }, key_si, value);
}
let remaining = agg_read_remaining(vm, record) - 1;
agg_write_remaining(vm, record, remaining);
if remaining == 0 {
settle_keyed_aggregate(vm, record);
}
NativeResult::Ok(JsValue::undefined())
}
/// `Promise.allSettledKeyed` resolve 元素:写 `{status:'fulfilled', value}`
/// 记录,剩余计数归零时建结果对象并完成。
fn promise_keyed_settled_resolve_element(vm: &mut Vm, args: &[u8]) -> NativeResult {
let Some((record, index)) = agg_element_state(vm) else {
return NativeResult::Ok(JsValue::undefined());
};
let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let values = agg_record_val(vm, record, AGG_VALUES_PROP);
if values.is_object() {
let settled = vm.make_settled_record("fulfilled", "value", value);
let key_si = make_int_key(index as u32);
// SAFETY: values 是存活数组对象。
vm.set_or_create_prop_value(unsafe { &mut *values.as_js_object_ptr() }, key_si, settled);
}
let remaining = agg_read_remaining(vm, record) - 1;
agg_write_remaining(vm, record, remaining);
if remaining == 0 {
settle_keyed_aggregate(vm, record);
}
NativeResult::Ok(JsValue::undefined())
}
/// `Promise.allSettledKeyed` reject 元素:写 `{status:'rejected', reason}`
/// 记录,剩余计数归零时建结果对象并完成。
fn promise_keyed_settled_reject_element(vm: &mut Vm, args: &[u8]) -> NativeResult {
let Some((record, index)) = agg_element_state(vm) else {
return NativeResult::Ok(JsValue::undefined());
};
let reason = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let values = agg_record_val(vm, record, AGG_VALUES_PROP);
if values.is_object() {
let settled = vm.make_settled_record("rejected", "reason", reason);
let key_si = make_int_key(index as u32);
// SAFETY: values 是存活数组对象。
vm.set_or_create_prop_value(unsafe { &mut *values.as_js_object_ptr() }, key_si, settled);
}
let remaining = agg_read_remaining(vm, record) - 1;
agg_write_remaining(vm, record, remaining);
if remaining == 0 {
settle_keyed_aggregate(vm, record);
}
NativeResult::Ok(JsValue::undefined())
}
/// `AggregateError(errors, message)` 构造器:message 非 undefined 时先 ToString 建 /// `AggregateError(errors, message)` 构造器:message 非 undefined 时先 ToString 建
/// 自身属性,再把 errors 可迭代收集为 `errors` 数据属性。 /// 自身属性,再把 errors 可迭代收集为 `errors` 数据属性。
fn aggregate_error_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { fn aggregate_error_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
+3 -2
View File
@@ -12,8 +12,8 @@ use oxide_types::value::JsValue;
use crate::vm::{FrameArgs, FrameContinuation, Vm}; use crate::vm::{FrameArgs, FrameContinuation, Vm};
use super::aggregate::{ use super::aggregate::{
promise_static_all, promise_static_all_settled, promise_static_any, promise_static_race, promise_static_reject, promise_static_all, promise_static_all_keyed, promise_static_all_settled, promise_static_any, promise_static_race,
promise_static_resolve, promise_static_try, promise_static_with_resolvers, promise_static_reject, promise_static_resolve, promise_static_try, promise_static_with_resolvers,
}; };
use super::reactions::{promise_catch, promise_finally, promise_then}; use super::reactions::{promise_catch, promise_finally, promise_then};
use super::{is_constructor_value, PromiseState, PromiseStateKind}; use super::{is_constructor_value, PromiseState, PromiseStateKind};
@@ -285,6 +285,7 @@ impl Vm {
("all", promise_static_all as *const (), 1), ("all", promise_static_all as *const (), 1),
("race", promise_static_race as *const (), 1), ("race", promise_static_race as *const (), 1),
("allSettled", promise_static_all_settled as *const (), 1), ("allSettled", promise_static_all_settled as *const (), 1),
("allKeyed", promise_static_all_keyed as *const (), 1),
("any", promise_static_any as *const (), 1), ("any", promise_static_any as *const (), 1),
("withResolvers", promise_static_with_resolvers as *const (), 0), ("withResolvers", promise_static_with_resolvers as *const (), 0),
("try", promise_static_try as *const (), 1), ("try", promise_static_try as *const (), 1),
+2
View File
@@ -106,6 +106,8 @@ const AGG_ALREADY_PROP: &str = "__oxide_agg_already__";
const AGG_REMAINING_PROP: &str = "__oxide_agg_remaining__"; const AGG_REMAINING_PROP: &str = "__oxide_agg_remaining__";
/// 记录对象上存结果数组(all 的 values,any 的 errors)。 /// 记录对象上存结果数组(all 的 values,any 的 errors)。
const AGG_VALUES_PROP: &str = "__oxide_agg_values__"; const AGG_VALUES_PROP: &str = "__oxide_agg_values__";
/// 记录对象上存结果键平行数组(keyed 族:JS 键值序列,与 values 按下标对齐)。
const AGG_KEYS_PROP: &str = "__oxide_agg_keys__";
/// 记录对象上存能力 resolve 闭包。 /// 记录对象上存能力 resolve 闭包。
const AGG_RESOLVE_PROP: &str = "__oxide_agg_resolve__"; const AGG_RESOLVE_PROP: &str = "__oxide_agg_resolve__";
/// 记录对象上存能力 reject 闭包。 /// 记录对象上存能力 reject 闭包。
+6 -1
View File
@@ -813,7 +813,10 @@ impl Vm {
prototype.bump_generation(); prototype.bump_generation();
} }
// 函数自身 `prototype` 属性:生成器 writable:true / enumerable:false / configurable:false。 // 函数自身 `prototype` 属性:生成器 writable:true / enumerable:false /
// configurable:false;普通函数与构造器 writable:true / enumerable:false /
// configurable:true。显式落元数据——缺元数据按默认描述符(可枚举)
// 判定,`prototype` 会泄漏进 keys / for-in / 聚合枚举。
let prototype_si = self.kernel_core.perm_interner().intern("prototype").0; let prototype_si = self.kernel_core.perm_interner().intern("prototype").0;
let func = unsafe { &mut *obj_ptr }; let func = unsafe { &mut *obj_ptr };
let prototype_shape = self.kernel_core.shape_forge().make_shape(func.shape_id(), prototype_si); let prototype_shape = self.kernel_core.shape_forge().make_shape(func.shape_id(), prototype_si);
@@ -821,6 +824,8 @@ impl Vm {
let prototype_pos = func.push_prop(prototype_val); let prototype_pos = func.push_prop(prototype_val);
if is_generator || is_async_generator { if is_generator || is_async_generator {
func.set_data_meta(prototype_pos, PropAttributes::new(true, false, false)); func.set_data_meta(prototype_pos, PropAttributes::new(true, false, false));
} else {
func.set_data_meta(prototype_pos, PropAttributes::new(true, false, true));
} }
func.bump_generation(); func.bump_generation();
} }