From b6580036da3a6af214abda77342d7c3c2f64fbb3 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 27 Sep 2026 20:53:13 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20for-await-of=20=E5=A4=9A=E5=B1=82?= =?UTF-8?q?=E9=80=83=E5=87=BA=E9=80=90=E5=B1=82=20LIFO=20=E5=85=B3?= =?UTF-8?q?=E9=97=AD=E5=B9=B6=E6=8E=A5=E9=80=9A=E5=BC=82=E6=AD=A5=E7=94=9F?= =?UTF-8?q?=E6=88=90=E5=99=A8=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/async_func.rs | 41 +++- crates/oxide_vm/src/async_generator.rs | 196 +++++++++++++++++- crates/oxide_vm/src/vm_dispatch_ctrl.rs | 66 +++--- .../tests/async_gen_delegation_tests.rs | 26 +++ crates/oxide_vm/tests/async_tests.rs | 98 +++++++++ 5 files changed, 388 insertions(+), 39 deletions(-) diff --git a/crates/oxide_vm/src/async_func.rs b/crates/oxide_vm/src/async_func.rs index 86220c59..f46b054e 100644 --- a/crates/oxide_vm/src/async_func.rs +++ b/crates/oxide_vm/src/async_func.rs @@ -65,7 +65,7 @@ pub(crate) const ASYNC_CTX_PROP: &str = "__oxide_async_ctx__"; /// 恢复闭包上区分 reject 角色的属性名。 const ASYNC_REJECT_PROP: &str = "__oxide_async_reject__"; /// 逃出关闭结算闭包上存 return() promise 的属性名(陈旧防御校验用)。 -const ASYNC_ESCAPE_PROMISE_PROP: &str = "__oxide_async_escape_promise__"; +pub(crate) const ASYNC_ESCAPE_PROMISE_PROP: &str = "__oxide_async_escape_promise__"; impl Vm { /// 调用异步函数返回的 capability promise:创建上下文对象 + 状态盒,立即压帧 @@ -389,14 +389,16 @@ impl Vm { Ok(()) } - /// 恢复逃出 for-await-of 的异步函数:return() 的 promise 结算后执行完成。 + /// 恢复逃出 for-await-of 的异步函数:return() 的 promise 结算后继续关闭剩余 + /// 条目并执行完成。 /// /// # 步骤 /// 1. 陈旧防御:状态盒 pending_async_escape.close_promise 须与本闭包 promise /// 匹配,不匹配则不处理(防同一 promise 被双结算)。 - /// 2. 把挂起快照灌回 VM,取出完成并清除 pending_async_escape。 - /// 3. 执行完成:Return 走 do_return,Break/Continue 跳转 target_pc 续 dispatch。 - /// 4. 再挂起则快照新状态;完成/异常则结算 capability。恢复调用方状态。 + /// 2. 把挂起快照灌回 VM,取出完成与剩余待关条目并清除 pending_async_escape。 + /// 3. 多层逃出:remaining 非空时逐层关闭(每层一轮结算),空了才执行完成。 + /// 4. 执行完成:Return 走 do_return,Break/Continue 跳转 target_pc 续 dispatch。 + /// 5. 再挂起则快照新状态;完成/异常则结算 capability。恢复调用方状态。 /// /// # 边界与前提 /// - 关闭 promise 拒绝时(reject 角色),拒绝值替代在途完成:抛拒绝原因, @@ -406,6 +408,8 @@ impl Vm { ) -> Result<(), String> { let state_ptr = self.async_state_ptr(ctx); let saved = self.save_inline_state(256); + // 清上一轮多层关闭残留的挂起信号(多层逐层结算每轮清零,防陈旧信号误消费)。 + self.async_suspended = false; let prev_ctx = self.async_context.take(); let prev_dispatch = self.async_dispatch; let prev_gen_ctx = self.async_gen_context.take(); @@ -438,8 +442,29 @@ impl Vm { state.phase = AsyncPhase::Running; } - // 取出完成并清除 pending_async_escape。 - let completion = self.pending_async_escape.take().unwrap().completion; + // 取出完成与剩余待关条目并清除 pending_async_escape。 + let (completion, mut remaining) = match self.pending_async_escape.take() { + Some(p) => (p.completion, p.remaining), + // 陈旧防御已早退,正常路径此处必为 Some;防御性兜底。 + None => return Ok(()), + }; + + // 多层逃出:remaining 非空时逐层关闭(每层一轮结算),空了才执行完成 + // (或抛拒绝原因)。挂起信号由 register 置位,快照后返回续下一轮结算。 + while let Some(next_iter) = remaining.first().cloned() { + remaining.remove(0); + match self.register_async_escape_close(next_iter, completion, remaining.clone()) { + Ok(true) => { + self.snapshot_async(unsafe { &mut *state_ptr })?; + self.restore_async_flags(prev_ctx, prev_dispatch, prev_gen_ctx, prev_gen_dispatch); + self.native_call_depth -= 1; + self.restore_inline_state(saved); + return Ok(()); + } + Ok(false) => continue, + Err(e) => return Err(e), + } + } // 关闭拒绝:拒绝值替代在途完成。抛拒绝原因,外围 catch/finally 接管; // 无处理器时异步函数以拒绝原因拒绝。 @@ -479,7 +504,7 @@ impl Vm { return Ok(()); } - // 单层路径:remaining 恒空,直接执行完成。 + // remaining 已空,执行完成。 match completion { Completion::Return { value: ret_val, .. } => { let result = self.do_return(ret_val)?; diff --git a/crates/oxide_vm/src/async_generator.rs b/crates/oxide_vm/src/async_generator.rs index a8cfce85..a33aa82e 100644 --- a/crates/oxide_vm/src/async_generator.rs +++ b/crates/oxide_vm/src/async_generator.rs @@ -21,7 +21,7 @@ use oxide_types::object::{Cell, JsObject, NativeFnPtr, PropAttributes}; use oxide_types::value::JsValue; use crate::generator::{DelegateOutcome, GeneratorResumeMode}; -use crate::vm::Vm; +use crate::vm::{Completion, Vm}; /// 异步生成器执行阶段。 #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -429,13 +429,20 @@ impl Vm { } let result = self.dispatch(); - self.async_gen_dispatch = prev_agd; - self.async_gen_context = prev_gen_ctx; - self.generator_dispatch = prev_gd; - self.async_dispatch = prev_ad; - self.async_context = prev_ctx; - self.native_call_depth -= 1; + self.restore_async_gen_flags(prev_ctx, prev_gen_ctx, prev_gd, prev_ad, prev_agd); + self.post_dispatch_async_gen(state_ptr, saved, gen_val, result) + } + /// 异步生成器恢复的 dispatch 后处理:YIELD 让出(快照 + 让出值 unwrap 结算)、 + /// AWAIT 挂起(快照)、完成/异常(结算当前请求)。 + /// + /// # 前提 + /// 调度标志已恢复(调用方先恢复再调本函数);本函数内部按结果快照新状态并 + /// 恢复调用方内联状态。 + fn post_dispatch_async_gen( + &mut self, state_ptr: *mut AsyncGeneratorState, saved: Box, gen_val: JsValue, + result: Result, + ) -> Result<(), String> { // YIELD 让出:快照挂起状态,让出值经 promise unwrap 后结算当前请求。 if let Some(value) = self.generator_suspended.take() { // AsyncGeneratorYield:让出值 PromiseResolve 包装后 await 展开 @@ -758,6 +765,154 @@ impl Vm { self.add_fn_name_length(obj, "", 1); JsValue::from_js_object(ptr) } + + /// 构造逃出关闭结算闭包(异步生成器):携带目标异步生成器上下文对象与 + /// return() promise,区分 fulfill/reject 角色。 + pub(crate) fn make_async_gen_escape_close_fn( + &mut self, ctx: JsValue, promise: JsValue, reject_role: bool, + ) -> JsValue { + let fn_proto = self.session.builtin_world().fn_proto_val(); + let mut func = JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto); + func.set_function(true); + // SAFETY: async_gen_escape_closure 是 NativeFn 函数项。 + func.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(async_gen_escape_closure as *const ()) })); + func.set_native_arg_count(1); + let ptr = self.alloc_object(func); + let obj = unsafe { &mut *ptr }; + let ctx_si = self.kernel_core.perm_interner().intern(crate::async_func::ASYNC_CTX_PROP).0; + self.set_or_create_prop_value(obj, ctx_si, ctx); + let role_si = self.kernel_core.perm_interner().intern(AG_REJECT_PROP).0; + self.set_or_create_prop_value(obj, role_si, JsValue::bool(reject_role)); + let promise_si = self + .kernel_core + .perm_interner() + .intern(crate::async_func::ASYNC_ESCAPE_PROMISE_PROP) + .0; + self.set_or_create_prop_value(obj, promise_si, promise); + self.add_fn_name_length(obj, "", 1); + JsValue::from_js_object(ptr) + } + + /// 恢复逃出 for-await-of 的异步生成器:return() 的 promise 结算后继续关闭剩余 + /// 条目并执行完成。 + /// + /// # 步骤 + /// 1. 陈旧防御:状态盒 pending_async_escape.close_promise 须与本闭包 promise + /// 匹配,不匹配则不处理(防同一 promise 被双结算)。 + /// 2. 把挂起快照灌回 VM,取出完成与剩余待关条目并清除 pending_async_escape。 + /// 3. 多层逃出:remaining 非空时逐层关闭(每层一轮结算),空了才执行完成。 + /// 4. 执行完成:Return 走 do_return 完成生成器,Break/Continue 跳转 target_pc + /// 续 dispatch。 + /// 5. 再挂起则快照新状态;完成/异常则结算当前请求。恢复调用方状态。 + /// + /// # 边界与前提 + /// - 关闭 promise 拒绝时(reject 角色),拒绝值替代在途完成:抛拒绝原因, + /// 外围 catch/finally 接管;无处理器时当前请求以拒绝原因拒绝。 + pub(crate) fn resume_async_gen_escape( + &mut self, ctx: JsValue, promise: JsValue, is_reject: bool, value: JsValue, + ) -> Result<(), String> { + let state_ptr = self.async_gen_state_ptr(ctx); + let saved = self.save_inline_state(256); + // 清上一轮多层关闭残留的挂起信号(多层逐层结算每轮清零,防陈旧信号误消费)。 + self.generator_suspended = None; + self.async_gen_suspended = false; + let prev_ctx = self.async_context.take(); + let prev_gd = self.generator_dispatch; + let prev_ad = self.async_dispatch; + let prev_gen_ctx = self.async_gen_context.take(); + let prev_agd = self.async_gen_dispatch; + self.generator_dispatch = true; + self.async_dispatch = true; + self.async_gen_dispatch = true; + self.async_context = Some(ctx); + self.async_gen_context = Some(ctx); + self.native_call_depth += 1; + + { + let state = unsafe { &mut *state_ptr }; + // 陈旧防御:close_promise 须匹配,陈旧闭包不处理。 + let is_stale = !matches!( + state.suspended.pending_async_escape.as_ref(), + Some(p) if p.close_promise == promise + ); + if is_stale { + self.restore_async_gen_flags(prev_ctx, prev_gen_ctx, prev_gd, prev_ad, prev_agd); + self.restore_inline_state(saved); + return Ok(()); + } + let restore_res = state.suspended.restore_into(self, state.callee); + if restore_res.is_err() { + self.restore_async_gen_flags(prev_ctx, prev_gen_ctx, prev_gd, prev_ad, prev_agd); + self.restore_inline_state(saved); + return Err("async generator suspended across runs is no longer valid".into()); + } + state.phase = AsyncGenPhase::Running; + } + + // 取出完成与剩余待关条目并清除 pending_async_escape。 + let (completion, mut remaining) = match self.pending_async_escape.take() { + Some(p) => (p.completion, p.remaining), + // 陈旧防御已早退,正常路径此处必为 Some;防御性兜底。 + None => { + self.restore_async_gen_flags(prev_ctx, prev_gen_ctx, prev_gd, prev_ad, prev_agd); + self.restore_inline_state(saved); + return Ok(()); + } + }; + + // 多层逃出:remaining 非空时逐层关闭(每层一轮结算),空了才执行完成 + // (或抛拒绝原因)。挂起信号由 register 置位,快照后返回续下一轮结算。 + while let Some(next_iter) = remaining.first().cloned() { + remaining.remove(0); + match self.register_async_escape_close(next_iter, completion, remaining.clone()) { + Ok(true) => { + unsafe { (*state_ptr).phase = AsyncGenPhase::AwaitSuspended }; + self.snapshot_async_generator(state_ptr)?; + self.restore_async_gen_flags(prev_ctx, prev_gen_ctx, prev_gd, prev_ad, prev_agd); + self.restore_inline_state(saved); + return Ok(()); + } + Ok(false) => continue, + Err(e) => return Err(e), + } + } + + // 关闭拒绝:拒绝值替代在途完成。抛拒绝原因,外围 catch/finally 接管; + // 无处理器时当前请求以拒绝原因拒绝。 + if is_reject { + self.exception_value = Some(value); + self.pending_error_kind = Some(self.thrown_error_kind(value)); + if self.unwind().is_err() { + let exc = self.last_uncaught_value.take().unwrap_or(value); + self.restore_async_gen_flags(prev_ctx, prev_gen_ctx, prev_gd, prev_ad, prev_agd); + return self.finish_async_gen_exc(state_ptr, saved, ctx, exc); + } + let result = self.dispatch(); + self.restore_async_gen_flags(prev_ctx, prev_gen_ctx, prev_gd, prev_ad, prev_agd); + return self.post_dispatch_async_gen(state_ptr, saved, ctx, result); + } + + // remaining 已空,执行完成。 + match completion { + Completion::Return { value: ret_val, .. } => { + let result = self.do_return(ret_val)?; + if let Some(result) = result { + self.restore_async_gen_flags(prev_ctx, prev_gen_ctx, prev_gd, prev_ad, prev_agd); + return self.finish_async_gen_ok(state_ptr, saved, ctx, result); + } + // 嵌套帧未弹空:续 dispatch。 + let result = self.dispatch(); + self.restore_async_gen_flags(prev_ctx, prev_gen_ctx, prev_gd, prev_ad, prev_agd); + self.post_dispatch_async_gen(state_ptr, saved, ctx, result) + } + Completion::Break { target_pc, .. } | Completion::Continue { target_pc, .. } => { + self.pc = target_pc; + let result = self.dispatch(); + self.restore_async_gen_flags(prev_ctx, prev_gen_ctx, prev_gd, prev_ad, prev_agd); + self.post_dispatch_async_gen(state_ptr, saved, ctx, result) + } + } + } } /// await 恢复闭包:读自身 prop 的异步生成器上下文与角色,恢复异步生成器继续执行。 @@ -1058,6 +1213,33 @@ fn async_gen_delegate_return_await_closure(vm: &mut Vm, args: &[u8]) -> NativeRe } } +/// 逃出关闭结算闭包(异步生成器):读自身 prop 的异步生成器上下文、return() +/// promise 与角色,恢复挂起帧并继续关闭剩余条目、执行完成。 +fn async_gen_escape_closure(vm: &mut Vm, args: &[u8]) -> NativeResult { + let callee = vm.reg(254); + if !callee.is_object() { + return NativeResult::Err(oxide_builtins::error::create_type_error(vm, "escape close handler is invalid")); + } + let callee_obj = unsafe { &*callee.as_js_object_ptr() }; + let ctx_si = vm.kernel_core.perm_interner().intern(crate::async_func::ASYNC_CTX_PROP).0; + let ctx = vm.resolve_property(callee_obj, ctx_si).unwrap_or(JsValue::undefined()); + let role_si = vm.kernel_core.perm_interner().intern(AG_REJECT_PROP).0; + let is_reject = vm + .resolve_property(callee_obj, role_si) + .is_some_and(oxide_runtime_api::to_boolean); + let promise_si = vm + .kernel_core + .perm_interner() + .intern(crate::async_func::ASYNC_ESCAPE_PROMISE_PROP) + .0; + let promise = vm.resolve_property(callee_obj, promise_si).unwrap_or(JsValue::undefined()); + let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + match vm.resume_async_gen_escape(ctx, promise, is_reject, value) { + Ok(()) => NativeResult::Ok(JsValue::undefined()), + Err(e) => NativeResult::Err(oxide_builtins::error::create_from_text(vm, &e)), + } +} + /// 委托结算值读取:done 与 value 的 getter 抛错经 `last_uncaught_value` 取原值。 enum DelegateResult { /// 结算值读取成功(done 标志与委托值)。 diff --git a/crates/oxide_vm/src/vm_dispatch_ctrl.rs b/crates/oxide_vm/src/vm_dispatch_ctrl.rs index c2b5c785..8976980a 100644 --- a/crates/oxide_vm/src/vm_dispatch_ctrl.rs +++ b/crates/oxide_vm/src/vm_dispatch_ctrl.rs @@ -945,9 +945,9 @@ impl Vm { /// 逃出关闭:按 LIFO 弹出逃出的 for-in 迭代器(无 return() 语义),再关闭逃出的 /// for-of 迭代器。同步迭代器同步关闭;异步迭代器(for-await-of)同步调 return() - /// 得 promise,登记结算闭包后挂起。逃出路径非 suppress:return() 抛错经 - /// raise_call_error 展开,剩余迭代器由 unwind 的 close_for_of_above 以 suppress - /// 继续关闭——新错误替代原完成值向外传播。 + /// 得 promise,登记结算闭包后挂起,剩余异步条目入 remaining 逐层续关。逃出路径 + /// 非 suppress:return() 抛错经 raise_call_error 展开,剩余迭代器由 unwind 的 + /// close_for_of_above 以 suppress 继续关闭——新错误替代原完成值向外传播。 /// /// # 返回值 /// `Done` = 全部关闭完成,调用方继续跳转/返回动作;`Handled` = return() 抛错且 @@ -960,16 +960,14 @@ impl Vm { for _ in 0..for_in_count { self.iters.pop_for_in(); } - // 单层路径:首个异步条目登记关闭,其余异步条目丢弃(多层路径填 remaining)。 - let mut first_async: Option = None; + // 多层路径:异步条目按 LIFO 收集(前元素先关),同步条目当场关闭。 + let mut async_iters: Vec = Vec::new(); for _ in 0..for_of_count { let Some(entry) = self.iters.pop_for_of() else { break; }; if entry.is_async { - if first_async.is_none() { - first_async = Some(entry.iterator); - } + async_iters.push(entry.iterator); continue; } let pc_before = self.pc; @@ -984,13 +982,14 @@ impl Vm { return Ok(CloseEscapeOutcome::Handled); } } - if let Some(iterator) = first_async { - match self.register_async_escape_close(iterator, completion, Vec::new()) { - Ok(true) => { - self.async_suspended = true; - return Ok(CloseEscapeOutcome::Suspended); - } - Ok(false) => {} + // 异步条目逐层关闭:首个有 return 方法的登记挂起,其余入 remaining;无 + // return 方法时继续下一层。 + let mut remaining = async_iters; + while let Some(iterator) = remaining.first().cloned() { + remaining.remove(0); + match self.register_async_escape_close(iterator, completion, remaining.clone()) { + Ok(true) => return Ok(CloseEscapeOutcome::Suspended), + Ok(false) => continue, Err(e) => return Err(e), } } @@ -998,19 +997,20 @@ impl Vm { } /// 登记逃出 for-await-of 的异步关闭:同步调 return() 得 promise,登记结算闭包, - /// 置 pending_async_escape。迭代器无 return 方法时不登记(完成直接继续)。 + /// 置 pending_async_escape 与挂起信号。迭代器无 return 方法时不登记(完成直接 + /// 继续)。 + /// + /// # 边界与前提 + /// - 异步生成器派发(`async_gen_dispatch`)走异步生成器状态盒与结算闭包,置 + /// `async_gen_suspended`;普通异步函数走异步上下文,置 `async_suspended`。 + /// - 同步生成器无异步上下文:不登记挂起(完成直接继续)。 /// /// # 返回值 /// `Ok(true)` = 已登记挂起;`Ok(false)` = 无 return 方法,无需挂起;`Err` = 关闭 /// 过程出错。 - fn register_async_escape_close( + pub(crate) fn register_async_escape_close( &mut self, iterator: JsValue, completion: Completion, remaining: Vec, ) -> Result { - // 异步生成器走独立状态盒(AsyncGeneratorState),本路径的结算闭包按 - // AsyncState 恢复——不登记挂起(异步迭代器保持既有跳过行为,完成直接继续)。 - if self.async_gen_dispatch { - return Ok(false); - } if !iterator.is_object() { return Ok(false); } @@ -1037,8 +1037,25 @@ impl Vm { } } }; - // 同步生成器无异步上下文:不登记挂起(异步迭代器保持既有跳过行为,完成直接继续)。 - let Some(ctx) = self.async_context.or(self.async_gen_context) else { + // 异步生成器派发:异步生成器状态盒与结算闭包,置 async_gen_suspended。 + if self.async_gen_dispatch { + let Some(ctx) = self.async_gen_context else { + return Ok(false); + }; + let fulfill_fn = self.make_async_gen_escape_close_fn(ctx, promise, false); + let reject_fn = self.make_async_gen_escape_close_fn(ctx, promise, true); + let _ = self.perform_promise_then(promise, fulfill_fn, reject_fn); + self.pending_async_escape = Some(PendingAsyncEscape { + close_promise: promise, + completion, + remaining, + }); + self.async_gen_suspended = true; + return Ok(true); + } + // 普通异步函数:异步上下文与结算闭包,置 async_suspended。同步生成器无 + // 异步上下文:不登记挂起(完成直接继续)。 + let Some(ctx) = self.async_context else { return Ok(false); }; let fulfill_fn = self.make_async_escape_close_fn(ctx, promise, false); @@ -1049,6 +1066,7 @@ impl Vm { completion, remaining, }); + self.async_suspended = true; Ok(true) } diff --git a/crates/oxide_vm/tests/async_gen_delegation_tests.rs b/crates/oxide_vm/tests/async_gen_delegation_tests.rs index a704b53c..d8513048 100644 --- a/crates/oxide_vm/tests/async_gen_delegation_tests.rs +++ b/crates/oxide_vm/tests/async_gen_delegation_tests.rs @@ -360,3 +360,29 @@ fn t18_throw_close_non_object_result_typeerror() { "\"caught:IteratorResult is not an object:true\"" ); } + +// T19:异步生成器体内 for-await-of 多层 return 逃出——LIFO 顺序关闭(内层先), +// 完成值经 next() 的 promise 交付({value:42, done:true})。 +#[test] +fn t19_async_gen_multi_layer_return_escape_lifo() { + let source = "let log=[];\ + const mk=(n)=>({[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:n,done:false})},return(){log.push('c'+n);return Promise.resolve({done:true})}}}});\ + async function* g() { for await (const a of mk(1)) { for await (const b of mk(2)) { log.push('body'); return 42; } } }\ + (async function run() { const it = g(); const r = await it.next(); \ + return log.join(',') + '|' + r.done + ':' + r.value; })()" + .to_string(); + assert_eq!(eval(&source), "\"body,c2,c1|true:42\""); +} + +// T20:异步生成器体内 for-await-of break 逃出——关闭迭代器后 body 继续, +// 后续 yield 正常交付(验证 break 跳转 target_pc 正确、生成器未误完成)。 +#[test] +fn t20_async_gen_break_escape_continues() { + let source = "let log=[];\ + const it={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:false})},return(){log.push('close');return Promise.resolve({done:true})}}}};\ + async function* g() { for await (const x of it) { log.push('body'); break; } yield 'after'; }\ + (async function run() { const it = g(); const r = await it.next(); \ + return log.join(',') + '|' + r.value + ':' + r.done; })()" + .to_string(); + assert_eq!(eval(&source), "\"body,close|after:false\""); +} diff --git a/crates/oxide_vm/tests/async_tests.rs b/crates/oxide_vm/tests/async_tests.rs index 56ae8b0f..449097e9 100644 --- a/crates/oxide_vm/tests/async_tests.rs +++ b/crates/oxide_vm/tests/async_tests.rs @@ -281,6 +281,104 @@ fn for_await_of_close_throw_object_identity_preserved() { ); } +// ── 逃出关闭测试矩阵(E1-E10):多层 LIFO 关闭与 Completion 边角 ── +// +// for-await-of 的 next() 经 await 结算,循环体在 N 层嵌套时深 N 个微任务 tick; +// 读取 log 的 then 回调须比最深的结算闭包更晚入队,故用足够长的 then 链延迟读取。 + +/// 延迟 N 个微任务 tick 后读取 log(N 层逃出需 N+2 个 tick 让全部结算闭包跑完)。 +fn delay_read(n: usize) -> String { + let mut chain = "Promise.resolve()".to_string(); + for _ in 0..n { + chain.push_str(".then(()=>{})"); + } + chain.push_str(".then(()=>log.join(','))"); + chain +} + +#[test] +fn for_await_of_break_escape_middle_layer() { + // E2b:break 逃出中间 for-await-of 层——内层迭代器关闭,外层继续迭代。 + let source = "let log=[];\ + const mk=(n)=>({[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:n,done:false})},return(){log.push('c'+n);return Promise.resolve({done:true})}}}});\ + (async()=>{ for await (const a of mk(1)) { for await (const b of mk(2)) { log.push('body'); break; } log.push('after'); break; } })();\ + ".to_string() + + &delay_read(4); + assert_eq!(eval(&source), "\"body,c2,after,c1\""); +} + +#[test] +fn for_await_of_labeled_continue_closes_inner() { + // E3:labeled continue 逃出内层 for-await-of——内层迭代器每轮关闭,外层继续。 + // 外层迭代器有限(两轮后 done),验证 continue 不关闭外层、外层继续迭代。 + let source = "let log=[];\ + let outerN=0;\ + const outer={[Symbol.asyncIterator](){return{next(){outerN++;if(outerN>2)return Promise.resolve({value:undefined,done:true});return Promise.resolve({value:outerN,done:false});},return(){log.push('cO');return Promise.resolve({done:true})}}}};\ + const inner={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:false})},return(){log.push('cI');return Promise.resolve({done:true})}}}};\ + (async()=>{ outer: for await (const a of outer) { for await (const b of inner) { log.push('body'); continue outer; } log.push('after-inner'); } })();\ + ".to_string() + + &delay_read(6); + assert_eq!(eval(&source), "\"body,cI,body,cI\""); +} + +#[test] +fn for_await_of_multi_layer_return_escape_lifo() { + // E4:多层 return 逃出(两层 for-await-of)——LIFO 顺序关闭(内层先)。 + // 内层 return() 先被调(c2),外层后(c1),关闭顺序验证 LIFO。 + let source = "let log=[];\ + const mk=(n)=>({[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:n,done:false})},return(){log.push('c'+n);return Promise.resolve({done:true})}}}});\ + (async()=>{ for await (const a of mk(1)) { for await (const b of mk(2)) { log.push('body'); return 42; } } })();\ + ".to_string() + + &delay_read(4); + assert_eq!(eval(&source), "\"body,c2,c1\""); +} + +#[test] +fn for_await_of_multi_layer_labeled_break_escape_lifo() { + // E5:多层 labeled break 逃出——LIFO 顺序关闭(内层先),跳转目标正确。 + let source = "let log=[];\ + const mk=(n)=>({[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:n,done:false})},return(){log.push('c'+n);return Promise.resolve({done:true})}}}});\ + (async()=>{ outer: for await (const a of mk(1)) { for await (const b of mk(2)) { log.push('body'); break outer; } } log.push('after'); })();\ + ".to_string() + + &delay_read(4); + assert_eq!(eval(&source), "\"body,c2,c1,after\""); +} + +#[test] +fn for_await_of_escape_close_reject_outer_catch() { + // E6:逃出时 return() 拒绝——拒绝原因替代完成值,外围 catch 捕获。 + let source = "let log=[];\ + const it={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:false})},return(){return Promise.reject('rej');}}}};\ + (async()=>{ try { for await (const x of it) { log.push('body'); return 1; } } catch (e) { log.push('caught:'+e); } log.push('done'); })();\ + ".to_string() + + &delay_read(3); + assert_eq!(eval(&source), "\"body,caught:rej,done\""); +} + +#[test] +fn for_await_of_escape_no_return_method() { + // E8:迭代器无 return 方法——不登记挂起,完成直接继续。 + let source = "let log=[];\ + const it={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:false})}}}};\ + (async()=>{ for await (const x of it) { log.push('body'); return 1; } })();\ + " + .to_string() + + &delay_read(2); + assert_eq!(eval(&source), "\"body\""); +} + +#[test] +fn for_await_of_escape_settlement_precedes_then() { + // E10:微任务时序——结算闭包(执行完成)先于后续 then 回调。 + // break 逃出后,完成跳转(log.push('after'))在结算闭包内执行,须先于 then 回调。 + let source = "let log=[];\ + const it={[Symbol.asyncIterator](){return{next(){return Promise.resolve({value:1,done:false})},return(){return Promise.resolve({done:true})}}}};\ + (async()=>{ for await (const x of it) { log.push('body'); break; } log.push('after'); })();\ + ".to_string() + + &delay_read(3); + assert_eq!(eval(&source), "\"body,after\""); +} + // ── class/object 生成器方法(异步) ── // class async 生成器方法:yield 顺序与 done 收敛。顶层 then 链驱动