diff --git a/crates/perry-runtime/src/array/iterator.rs b/crates/perry-runtime/src/array/iterator.rs index 2528aa94df..9af4db5010 100644 --- a/crates/perry-runtime/src/array/iterator.rs +++ b/crates/perry-runtime/src/array/iterator.rs @@ -453,8 +453,36 @@ pub(crate) fn async_from_sync_wrap_iterator(iter: f64) -> f64 { #[no_mangle] pub extern "C" fn js_get_async_iterator(value: f64) -> f64 { - if let Some(iter) = call_symbol_async_iterator(value) { - return iter; + // GetIterator(value, async) — ECMA-262 §7.4.3. + // + // Spec ordering matters (test262 yield-star-getiter-async-*): consult + // @@asyncIterator with GetMethod semantics FIRST. A method that is present + // but not callable is a TypeError; a callable method whose result is not an + // Object is a TypeError. Only an ABSENT (undefined/null) @@asyncIterator + // falls back to the sync iterator wrapped via CreateAsyncFromSyncIterator — + // so e.g. `yield* { [Symbol.asyncIterator]() { return undefined } }` throws + // instead of (wrongly) reaching the object's `[Symbol.iterator]`. + let sym = crate::symbol::well_known_symbol("asyncIterator"); + if !sym.is_null() { + let sym_f64 = f64::from_bits(crate::value::JSValue::pointer(sym as *const u8).bits()); + let method = unsafe { crate::symbol::js_object_get_symbol_property(value, sym_f64) }; + let mb = method.to_bits(); + if mb != crate::value::TAG_UNDEFINED && mb != crate::value::TAG_NULL { + // @@asyncIterator is present: GetMethod requires it be callable. + if !is_callable_value(method) { + throw_iterator_method_not_callable(); + } + let prev_this = crate::object::js_implicit_this_set(value); + let iterator = + unsafe { crate::closure::js_native_call_value(method, std::ptr::null(), 0) }; + crate::object::js_implicit_this_set(prev_this); + // GetIterator step 5: the result must be an Object. + if !is_async_iterator_object(iterator) { + throw_iterator_result_not_object(); + } + return iterator; + } + // @@asyncIterator absent → fall through to the sync-iterator path. } let iter = crate::symbol::js_get_iterator(value); @@ -469,6 +497,15 @@ pub extern "C" fn js_get_async_iterator(value: f64) -> f64 { async_from_sync_wrap_iterator(iter) } +/// `Type(x) is Object` for the GetIterator(async) result check: heap +/// pointer-tagged values that are not registered Symbols (strings, numbers, +/// booleans, null/undefined, symbols are all NOT objects). +fn is_async_iterator_object(value: f64) -> bool { + let jv = crate::value::JSValue::from_bits(value.to_bits()); + jv.is_pointer() + && !crate::symbol::is_registered_symbol(crate::value::js_nanbox_get_pointer(value) as usize) +} + #[cold] fn throw_not_iterable(value: f64) -> ! { let label = if value.to_bits() == crate::value::TAG_NULL { diff --git a/crates/perry-runtime/src/object/class_registry.rs b/crates/perry-runtime/src/object/class_registry.rs index 9f72b0d6b5..ddd17719b6 100644 --- a/crates/perry-runtime/src/object/class_registry.rs +++ b/crates/perry-runtime/src/object/class_registry.rs @@ -3341,8 +3341,14 @@ pub(crate) unsafe fn call_vtable_method( has_synthetic_arguments: bool, has_rest: bool, ) -> f64 { + // A missing trailing argument is `undefined` per spec (NOT NaN): default + // parameters lower to a `param === undefined ? : param` check in + // the method prologue, so padding a hole with NaN left the default + // un-applied (`async method(a, b, c = 99)` called via the dynamic vtable + // path — e.g. a detached `C.prototype.method` value — saw `c = NaN`). Pad + // with TAG_UNDEFINED so the prologue's default-check fires. #[inline(always)] - unsafe fn arg_or_nan(args_ptr: *const f64, args_len: usize, idx: usize) -> f64 { + unsafe fn arg_or_undefined(args_ptr: *const f64, args_len: usize, idx: usize) -> f64 { if idx < args_len { *args_ptr.add(idx) } else { @@ -3406,13 +3412,13 @@ pub(crate) unsafe fn call_vtable_method( crate::array::js_array_set_f64( raw_args, slot as u32, - arg_or_nan(args_ptr, args_len, i), + arg_or_undefined(args_ptr, args_len, i), ); } let raw_args_value = crate::value::js_nanbox_pointer(raw_args as i64); let mut args = Vec::with_capacity(param_count as usize); for i in 0..visible_params { - args.push(arg_or_nan(args_ptr, args_len, i)); + args.push(arg_or_undefined(args_ptr, args_len, i)); } args.push(raw_args_value); adjusted_args_storage = Some(args); @@ -3429,33 +3435,33 @@ pub(crate) unsafe fn call_vtable_method( } 1 => { let f: extern "C" fn(f64, f64) -> f64 = std::mem::transmute(func_ptr); - f(this_f64, arg_or_nan(call_args_ptr, call_args_len, 0)) + f(this_f64, arg_or_undefined(call_args_ptr, call_args_len, 0)) } 2 => { let f: extern "C" fn(f64, f64, f64) -> f64 = std::mem::transmute(func_ptr); f( this_f64, - arg_or_nan(call_args_ptr, call_args_len, 0), - arg_or_nan(call_args_ptr, call_args_len, 1), + arg_or_undefined(call_args_ptr, call_args_len, 0), + arg_or_undefined(call_args_ptr, call_args_len, 1), ) } 3 => { let f: extern "C" fn(f64, f64, f64, f64) -> f64 = std::mem::transmute(func_ptr); f( this_f64, - arg_or_nan(call_args_ptr, call_args_len, 0), - arg_or_nan(call_args_ptr, call_args_len, 1), - arg_or_nan(call_args_ptr, call_args_len, 2), + arg_or_undefined(call_args_ptr, call_args_len, 0), + arg_or_undefined(call_args_ptr, call_args_len, 1), + arg_or_undefined(call_args_ptr, call_args_len, 2), ) } 4 => { let f: extern "C" fn(f64, f64, f64, f64, f64) -> f64 = std::mem::transmute(func_ptr); f( this_f64, - arg_or_nan(call_args_ptr, call_args_len, 0), - arg_or_nan(call_args_ptr, call_args_len, 1), - arg_or_nan(call_args_ptr, call_args_len, 2), - arg_or_nan(call_args_ptr, call_args_len, 3), + arg_or_undefined(call_args_ptr, call_args_len, 0), + arg_or_undefined(call_args_ptr, call_args_len, 1), + arg_or_undefined(call_args_ptr, call_args_len, 2), + arg_or_undefined(call_args_ptr, call_args_len, 3), ) } 5 => { @@ -3463,11 +3469,11 @@ pub(crate) unsafe fn call_vtable_method( std::mem::transmute(func_ptr); f( this_f64, - arg_or_nan(call_args_ptr, call_args_len, 0), - arg_or_nan(call_args_ptr, call_args_len, 1), - arg_or_nan(call_args_ptr, call_args_len, 2), - arg_or_nan(call_args_ptr, call_args_len, 3), - arg_or_nan(call_args_ptr, call_args_len, 4), + arg_or_undefined(call_args_ptr, call_args_len, 0), + arg_or_undefined(call_args_ptr, call_args_len, 1), + arg_or_undefined(call_args_ptr, call_args_len, 2), + arg_or_undefined(call_args_ptr, call_args_len, 3), + arg_or_undefined(call_args_ptr, call_args_len, 4), ) } 6 => { @@ -3475,12 +3481,12 @@ pub(crate) unsafe fn call_vtable_method( std::mem::transmute(func_ptr); f( this_f64, - arg_or_nan(call_args_ptr, call_args_len, 0), - arg_or_nan(call_args_ptr, call_args_len, 1), - arg_or_nan(call_args_ptr, call_args_len, 2), - arg_or_nan(call_args_ptr, call_args_len, 3), - arg_or_nan(call_args_ptr, call_args_len, 4), - arg_or_nan(call_args_ptr, call_args_len, 5), + arg_or_undefined(call_args_ptr, call_args_len, 0), + arg_or_undefined(call_args_ptr, call_args_len, 1), + arg_or_undefined(call_args_ptr, call_args_len, 2), + arg_or_undefined(call_args_ptr, call_args_len, 3), + arg_or_undefined(call_args_ptr, call_args_len, 4), + arg_or_undefined(call_args_ptr, call_args_len, 5), ) } 7 => { @@ -3488,13 +3494,13 @@ pub(crate) unsafe fn call_vtable_method( std::mem::transmute(func_ptr); f( this_f64, - arg_or_nan(call_args_ptr, call_args_len, 0), - arg_or_nan(call_args_ptr, call_args_len, 1), - arg_or_nan(call_args_ptr, call_args_len, 2), - arg_or_nan(call_args_ptr, call_args_len, 3), - arg_or_nan(call_args_ptr, call_args_len, 4), - arg_or_nan(call_args_ptr, call_args_len, 5), - arg_or_nan(call_args_ptr, call_args_len, 6), + arg_or_undefined(call_args_ptr, call_args_len, 0), + arg_or_undefined(call_args_ptr, call_args_len, 1), + arg_or_undefined(call_args_ptr, call_args_len, 2), + arg_or_undefined(call_args_ptr, call_args_len, 3), + arg_or_undefined(call_args_ptr, call_args_len, 4), + arg_or_undefined(call_args_ptr, call_args_len, 5), + arg_or_undefined(call_args_ptr, call_args_len, 6), ) } 8 => { @@ -3502,14 +3508,14 @@ pub(crate) unsafe fn call_vtable_method( std::mem::transmute(func_ptr); f( this_f64, - arg_or_nan(call_args_ptr, call_args_len, 0), - arg_or_nan(call_args_ptr, call_args_len, 1), - arg_or_nan(call_args_ptr, call_args_len, 2), - arg_or_nan(call_args_ptr, call_args_len, 3), - arg_or_nan(call_args_ptr, call_args_len, 4), - arg_or_nan(call_args_ptr, call_args_len, 5), - arg_or_nan(call_args_ptr, call_args_len, 6), - arg_or_nan(call_args_ptr, call_args_len, 7), + arg_or_undefined(call_args_ptr, call_args_len, 0), + arg_or_undefined(call_args_ptr, call_args_len, 1), + arg_or_undefined(call_args_ptr, call_args_len, 2), + arg_or_undefined(call_args_ptr, call_args_len, 3), + arg_or_undefined(call_args_ptr, call_args_len, 4), + arg_or_undefined(call_args_ptr, call_args_len, 5), + arg_or_undefined(call_args_ptr, call_args_len, 6), + arg_or_undefined(call_args_ptr, call_args_len, 7), ) } 9 => { @@ -3517,15 +3523,15 @@ pub(crate) unsafe fn call_vtable_method( std::mem::transmute(func_ptr); f( this_f64, - arg_or_nan(call_args_ptr, call_args_len, 0), - arg_or_nan(call_args_ptr, call_args_len, 1), - arg_or_nan(call_args_ptr, call_args_len, 2), - arg_or_nan(call_args_ptr, call_args_len, 3), - arg_or_nan(call_args_ptr, call_args_len, 4), - arg_or_nan(call_args_ptr, call_args_len, 5), - arg_or_nan(call_args_ptr, call_args_len, 6), - arg_or_nan(call_args_ptr, call_args_len, 7), - arg_or_nan(call_args_ptr, call_args_len, 8), + arg_or_undefined(call_args_ptr, call_args_len, 0), + arg_or_undefined(call_args_ptr, call_args_len, 1), + arg_or_undefined(call_args_ptr, call_args_len, 2), + arg_or_undefined(call_args_ptr, call_args_len, 3), + arg_or_undefined(call_args_ptr, call_args_len, 4), + arg_or_undefined(call_args_ptr, call_args_len, 5), + arg_or_undefined(call_args_ptr, call_args_len, 6), + arg_or_undefined(call_args_ptr, call_args_len, 7), + arg_or_undefined(call_args_ptr, call_args_len, 8), ) } _ => { @@ -3533,16 +3539,16 @@ pub(crate) unsafe fn call_vtable_method( std::mem::transmute(func_ptr); f( this_f64, - arg_or_nan(call_args_ptr, call_args_len, 0), - arg_or_nan(call_args_ptr, call_args_len, 1), - arg_or_nan(call_args_ptr, call_args_len, 2), - arg_or_nan(call_args_ptr, call_args_len, 3), - arg_or_nan(call_args_ptr, call_args_len, 4), - arg_or_nan(call_args_ptr, call_args_len, 5), - arg_or_nan(call_args_ptr, call_args_len, 6), - arg_or_nan(call_args_ptr, call_args_len, 7), - arg_or_nan(call_args_ptr, call_args_len, 8), - arg_or_nan(call_args_ptr, call_args_len, 9), + arg_or_undefined(call_args_ptr, call_args_len, 0), + arg_or_undefined(call_args_ptr, call_args_len, 1), + arg_or_undefined(call_args_ptr, call_args_len, 2), + arg_or_undefined(call_args_ptr, call_args_len, 3), + arg_or_undefined(call_args_ptr, call_args_len, 4), + arg_or_undefined(call_args_ptr, call_args_len, 5), + arg_or_undefined(call_args_ptr, call_args_len, 6), + arg_or_undefined(call_args_ptr, call_args_len, 7), + arg_or_undefined(call_args_ptr, call_args_len, 8), + arg_or_undefined(call_args_ptr, call_args_len, 9), ) } } @@ -4348,12 +4354,14 @@ pub(crate) unsafe fn call_static_method( args_len: usize, param_count: u32, ) -> f64 { + // Missing trailing args pad with `undefined` (NOT NaN) so default + // parameters fire — see `call_vtable_method::arg_or_undefined`. #[inline(always)] unsafe fn a(args_ptr: *const f64, args_len: usize, idx: usize) -> f64 { if idx < args_len { *args_ptr.add(idx) } else { - f64::NAN + f64::from_bits(crate::value::TAG_UNDEFINED) } } match param_count { diff --git a/crates/perry-runtime/src/object/global_this.rs b/crates/perry-runtime/src/object/global_this.rs index 860e619ace..db115da9ca 100644 --- a/crates/perry-runtime/src/object/global_this.rs +++ b/crates/perry-runtime/src/object/global_this.rs @@ -2263,6 +2263,49 @@ extern "C" fn async_generator_proto_throw_thunk( generator_proto_method(b"throw", arg, true) } +/// `%AsyncGenerator.prototype%[Symbol.asyncIterator]()` returns `this` (spec +/// inherits this from `%AsyncIteratorPrototype%`). Without it, `for await` / +/// `GetIterator(obj, async)` over a generator instance can't obtain the async +/// iterator and either throws or silently produces nothing. +extern "C" fn async_generator_proto_async_iterator_thunk( + _c: *const crate::closure::ClosureHeader, + _arg: f64, +) -> f64 { + crate::object::js_implicit_this_get() +} + +/// Install a well-known-symbol-keyed method (returning `this`) on a +/// generator/async-generator prototype, with the spec descriptor shape +/// (`name`/`length` own props, non-enumerable value). +fn install_proto_symbol_self_method( + proto: *mut ObjectHeader, + symbol_name: &str, + display_name: &str, + func_ptr: *const u8, +) { + let closure = crate::closure::js_closure_alloc(func_ptr, 0); + if closure.is_null() { + return; + } + crate::closure::js_register_closure_arity(func_ptr, 0); + super::native_module::set_bound_native_closure_name(closure, display_name); + super::native_module::set_builtin_closure_length(closure as usize, 0); + let configurable = super::PropertyAttrs::new(false, false, true); + super::set_builtin_property_attrs(closure as usize, "name".to_string(), configurable); + super::set_builtin_property_attrs(closure as usize, "length".to_string(), configurable); + let sym = crate::symbol::well_known_symbol(symbol_name); + if sym.is_null() { + return; + } + unsafe { + crate::symbol::js_object_set_symbol_property( + crate::value::js_nanbox_pointer(proto as i64), + f64::from_bits(JSValue::pointer(sym as *const u8).bits()), + crate::value::js_nanbox_pointer(closure as i64), + ); + } +} + /// #4141: link a freshly-built generator/async-generator instance object into /// the spec `[[Prototype]]` chain. Perry lowers `gen()` to a `{next,return, /// throw}` object literal; this interposes a fresh intermediate object (the @@ -2449,6 +2492,28 @@ fn build_generator_tower( install_proto_method(gen_proto, "next", next_thunk, 1); install_proto_method(gen_proto, "return", return_thunk, 1); install_proto_method(gen_proto, "throw", throw_thunk, 1); + // Spec: `%AsyncGenerator.prototype%` inherits `[Symbol.asyncIterator]` from + // `%AsyncIteratorPrototype%` (returning `this`). Without it, `for await (x of + // gen())` over an async-generator *method instance* can't resolve the async + // iterator and hangs/yields nothing (the instance carries no own iterator + // symbol). The async-iterator-acquisition path (`js_get_async_iterator`) + // sets the implicit-this before invoking this thunk, so it returns the + // generator instance. + // + // Note: the SYNC `%Generator.prototype%` deliberately gets NO own + // `[Symbol.iterator]` here — the sync `for-of` iterator-acquisition path + // (`js_get_iterator`) does NOT bind implicit-this before invoking a + // `[Symbol.iterator]` method, so a `this`-returning thunk would resolve to + // `undefined` and break `for (x of gen())`. Sync generators already iterate + // through their own `next` via the builtin-iterator recognizers. + if is_async { + install_proto_symbol_self_method( + gen_proto, + "asyncIterator", + "[Symbol.asyncIterator]", + async_generator_proto_async_iterator_thunk as *const u8, + ); + } set_intrinsic_to_string_tag(gen_proto, inst_tag); ctor_slot.store(ctor as i64, Ordering::Release); diff --git a/crates/perry-runtime/src/object/object_ops_frozen.rs b/crates/perry-runtime/src/object/object_ops_frozen.rs index 40644e663b..64bcaaad55 100644 --- a/crates/perry-runtime/src/object/object_ops_frozen.rs +++ b/crates/perry-runtime/src/object/object_ops_frozen.rs @@ -274,6 +274,22 @@ pub extern "C" fn js_object_is_extensible(obj_value: f64) -> f64 { if obj.is_null() || (obj as usize) <= 0x10000 { return f64::from_bits(TAG_FALSE); // non-objects are not extensible } + // Typed arrays and ArrayBuffers use a non-standard allocation that does + // not carry the 8-byte object `GcHeader` the freeze/seal/extend flags + // live in (small typed arrays are raw-`alloc`'d with the + // `TypedArrayHeader` at offset 0 — only the large-object path interposes + // a `GcHeader`). Reading `_reserved` for them dereferences whatever + // precedes the allocation, so `isExtensible` would non-deterministically + // report `false` depending on heap layout. Integer-indexed exotic + // objects are extensible by default; report that instead of reading a + // header that may not exist. + let raw = crate::value::js_nanbox_get_pointer(obj_value) as usize; + if raw > 0x10000 + && (crate::typedarray::lookup_typed_array_kind(raw).is_some() + || crate::buffer::is_registered_buffer(raw)) + { + return f64::from_bits(TAG_TRUE); + } let gc = gc_header_for(obj); if (*gc)._reserved & crate::gc::OBJ_FLAG_NO_EXTEND != 0 { f64::from_bits(TAG_FALSE) diff --git a/crates/perry-transform/src/generator/linearize.rs b/crates/perry-transform/src/generator/linearize.rs index 5243be6028..0b826fa735 100644 --- a/crates/perry-transform/src/generator/linearize.rs +++ b/crates/perry-transform/src/generator/linearize.rs @@ -2,6 +2,48 @@ use super::*; +thread_local! { + /// Whether the generator currently being linearized is an `async function*`. + /// Set by `transform_generator_function_with_extra_captures` right before it + /// calls `linearize_body` (linearization is fully synchronous, so a + /// thread-local avoids threading a bool through ~14 recursive call sites). + /// Read by the `yield*` arms to pick the async vs sync delegation protocol. + static LINEARIZE_IS_ASYNC_GEN: std::cell::Cell = const { std::cell::Cell::new(false) }; +} + +pub(crate) fn set_linearize_async_generator(v: bool) { + LINEARIZE_IS_ASYNC_GEN.with(|c| c.set(v)); +} + +fn linearize_async_generator() -> bool { + LINEARIZE_IS_ASYNC_GEN.with(|c| c.get()) +} + +/// Resolve a `yield*` operand into its iterator. An async generator delegates +/// through the async-iterator protocol (`GetAsyncIterator`, which honours +/// `[Symbol.asyncIterator]` and wraps a sync iterable via +/// CreateAsyncFromSyncIterator); a sync generator uses `GetIterator`. +fn delegate_get_iterator(inner: Expr) -> Expr { + if linearize_async_generator() { + Expr::GetAsyncIterator(Box::new(inner)) + } else { + Expr::GetIterator(Box::new(inner)) + } +} + +/// Wrap a delegated-iterator `next()`/`return()`/`throw()` call. In an async +/// generator the delegated iterator's methods return a promise of the +/// iter-result, which must be awaited before `.value`/`.done` are read (`await` +/// is a synchronous promise-drain in codegen). Sync generators read the +/// iter-result directly. +fn delegate_await(call: Expr) -> Expr { + if linearize_async_generator() { + Expr::Await(Box::new(call)) + } else { + call + } +} + pub struct State { pub num: u32, pub body: Vec, @@ -136,11 +178,11 @@ pub fn linearize_body( // well-known-symbol method to obtain one. current.push(Stmt::Expr(Expr::LocalSet( del_iter_id, - Box::new(Expr::GetIterator(Box::new(*inner.clone()))), + Box::new(delegate_get_iterator(*inner.clone())), ))); current.push(Stmt::Expr(Expr::LocalSet( del_result_id, - Box::new(next_call), + Box::new(delegate_await(next_call)), ))); // Build the while loop with yield @@ -152,7 +194,10 @@ pub fn linearize_body( })), delegate: false, }), - Stmt::Expr(Expr::LocalSet(del_result_id, Box::new(next_call_resumed))), + Stmt::Expr(Expr::LocalSet( + del_result_id, + Box::new(delegate_await(next_call_resumed)), + )), ]; let while_stmt = Stmt::While { @@ -243,14 +288,15 @@ pub fn linearize_body( // #1831: resolve the iterator (effect / custom `[Symbol.iterator]` // operands need `js_get_iterator` to invoke the well-known-symbol - // method; a generator call already is its iterator). + // method; a generator call already is its iterator). Async + // generators delegate through the async-iterator protocol. current.push(Stmt::Expr(Expr::LocalSet( del_iter_id, - Box::new(Expr::GetIterator(Box::new(*inner.clone()))), + Box::new(delegate_get_iterator(*inner.clone())), ))); current.push(Stmt::Expr(Expr::LocalSet( del_result_id, - Box::new(next_call), + Box::new(delegate_await(next_call)), ))); let while_body = vec![ @@ -261,7 +307,10 @@ pub fn linearize_body( })), delegate: false, }), - Stmt::Expr(Expr::LocalSet(del_result_id, Box::new(next_call_resumed))), + Stmt::Expr(Expr::LocalSet( + del_result_id, + Box::new(delegate_await(next_call_resumed)), + )), ]; let while_stmt = Stmt::While { @@ -998,14 +1047,15 @@ pub fn linearize_body( // #1831: resolve the iterator. For a generator *call* the result // already is its iterator; for an arbitrary iterable (effect, // custom `[Symbol.iterator]`) `js_get_iterator` invokes the - // well-known-symbol method to obtain one. + // well-known-symbol method to obtain one. Async generators + // delegate through the async-iterator protocol. current.push(Stmt::Expr(Expr::LocalSet( del_iter_id, - Box::new(Expr::GetIterator(Box::new(*inner.clone()))), + Box::new(delegate_get_iterator(*inner.clone())), ))); current.push(Stmt::Expr(Expr::LocalSet( del_result_id, - Box::new(next_call), + Box::new(delegate_await(next_call)), ))); let while_body = vec![ @@ -1016,7 +1066,10 @@ pub fn linearize_body( })), delegate: false, }), - Stmt::Expr(Expr::LocalSet(del_result_id, Box::new(next_call_resumed))), + Stmt::Expr(Expr::LocalSet( + del_result_id, + Box::new(delegate_await(next_call_resumed)), + )), ]; let while_stmt = Stmt::While { diff --git a/crates/perry-transform/src/generator/lower.rs b/crates/perry-transform/src/generator/lower.rs index d222f9fec2..9d5be33aca 100644 --- a/crates/perry-transform/src/generator/lower.rs +++ b/crates/perry-transform/src/generator/lower.rs @@ -110,6 +110,10 @@ pub fn transform_generator_function_with_extra_captures( // completion. Innermost finallys are pushed first (the recursion into a // try body collects nested finallys before the enclosing one). let mut finallys: Vec = Vec::new(); + // Tell the linearizer whether `yield*` should delegate through the + // async-iterator protocol (await each delegated `next()`); see the `yield*` + // arms in `linearize.rs`. + super::linearize::set_linearize_async_generator(is_async_generator); linearize_body( &func.body, &mut states,