Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/perry-codegen-js/src/emit/exprs_more.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,11 @@ impl JsEmitter {
self.emit_expr(val);
self.output.push(')');
}
Expr::ForAwaitToArray(val) => {
self.output.push_str("Array.fromAsync(");
self.emit_expr(val);
self.output.push(')');
}
Expr::ArrayFromMapped {
iterable,
map_fn,
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/collectors/escape_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ pub fn check_escapes_in_expr(
| Expr::IteratorToArray(operand)
| Expr::GetIterator(operand)
| Expr::ForOfToArray(operand)
| Expr::ForAwaitToArray(operand)
| Expr::WeakRefNew(operand)
| Expr::WeakRefDeref(operand)
| Expr::FinalizationRegistryNew(operand)
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/collectors/escape_news.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ fn collect_used_new_fields_in_expr(
| Expr::IteratorToArray(operand)
| Expr::GetIterator(operand)
| Expr::ForOfToArray(operand)
| Expr::ForAwaitToArray(operand)
| Expr::WeakRefNew(operand)
| Expr::WeakRefDeref(operand)
| Expr::FinalizationRegistryNew(operand)
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/collectors/i32_locals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1113,6 +1113,7 @@ pub fn collect_localset_ids_in_expr_filtered(
| Expr::IteratorToArray(operand)
| Expr::GetIterator(operand)
| Expr::ForOfToArray(operand)
| Expr::ForAwaitToArray(operand)
| Expr::WeakRefNew(operand)
| Expr::WeakRefDeref(operand)
| Expr::QueueMicrotask(operand)
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/collectors/refs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ pub fn collect_ref_ids_in_expr(e: &perry_hir::Expr, out: &mut HashSet<u32>) {
| Expr::IteratorToArray(operand)
| Expr::GetIterator(operand)
| Expr::ForOfToArray(operand)
| Expr::ForAwaitToArray(operand)
| Expr::WeakRefNew(operand)
| Expr::WeakRefDeref(operand)
| Expr::QueueMicrotask(operand)
Expand Down
9 changes: 9 additions & 0 deletions crates/perry-codegen/src/expr/arrays_finds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,15 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
.block()
.call(DOUBLE, "js_for_of_to_array", &[(DOUBLE, &v)]))
}
Expr::ForAwaitToArray(o) => {
let v = lower_expr(ctx, o)?;
let undefined = double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED));
Ok(ctx.block().call(
DOUBLE,
"js_array_from_async",
&[(DOUBLE, &v), (DOUBLE, &undefined), (DOUBLE, &undefined)],
))
}
Expr::WeakRefDeref(o) => {
// `ref.deref()` — returns the wrapped target (or undefined if
// collected; GC never clears the stub slot, so always returns
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1575,6 +1575,7 @@ pub(crate) fn lower_expr(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
| Expr::IteratorToArray(..)
| Expr::GetIterator(..)
| Expr::ForOfToArray(..)
| Expr::ForAwaitToArray(..)
| Expr::WeakRefDeref(..)
| Expr::Uint8ArrayNew(..)
| Expr::Uint8ArrayLength(..)
Expand Down
5 changes: 5 additions & 0 deletions crates/perry-hir/src/ir/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2085,6 +2085,11 @@ pub enum Expr {
/// else drives its `[Symbol.iterator]`. Without it the index loop read
/// `.length` off a raw Map/Set handle (→ 0) and iterated zero times.
ForOfToArray(Box<Expr>),
/// Materialize an untyped `for await...of` receiver into a plain Array.
/// This routes through the async-iterator protocol first, then falls back
/// to the existing array/array-like behavior used by `Array.fromAsync`.
/// The lowering wraps this in `Await` before the index loop reads it.
ForAwaitToArray(Box<Expr>),
/// Array.from(iterable, mapFn, thisArg?) -> Array
/// Creates a new array by applying mapFn to each element of the iterable.
/// `this_arg` (#2773) binds `this` inside a non-arrow mapFn.
Expand Down
6 changes: 5 additions & 1 deletion crates/perry-hir/src/lower/stmt_loops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,11 @@ pub(crate) fn lower_stmt_for_of(
} else if is_iterable_typed_array {
Expr::ArrayFrom(Box::new(arr_expr))
} else if needs_runtime_iterator {
Expr::ForOfToArray(Box::new(arr_expr))
if for_of_stmt.is_await {
Expr::Await(Box::new(Expr::ForAwaitToArray(Box::new(arr_expr))))
} else {
Expr::ForOfToArray(Box::new(arr_expr))
}
} else {
arr_expr
};
Expand Down
6 changes: 5 additions & 1 deletion crates/perry-hir/src/lower_decl/body_stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1299,7 +1299,11 @@ pub fn lower_body_stmt(ctx: &mut LoweringContext, stmt: &ast::Stmt) -> Result<Ve
} else if is_iterable_typed_array {
Expr::ArrayFrom(Box::new(arr_expr))
} else if needs_runtime_iterator {
Expr::ForOfToArray(Box::new(arr_expr))
if for_of_stmt.is_await {
Expr::Await(Box::new(Expr::ForAwaitToArray(Box::new(arr_expr))))
} else {
Expr::ForOfToArray(Box::new(arr_expr))
}
} else {
arr_expr
};
Expand Down
1 change: 1 addition & 0 deletions crates/perry-hir/src/stable_hash/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@ impl SH for Expr {
Expr::IteratorToArray(e) => { tag(h, 398); e.as_ref().hash(h); }
Expr::GetIterator(e) => { tag(h, 11238); e.as_ref().hash(h); }
Expr::ForOfToArray(e) => { tag(h, 11243); e.as_ref().hash(h); }
Expr::ForAwaitToArray(e) => { tag(h, 11244); e.as_ref().hash(h); }
Expr::ArrayFromMapped { iterable, map_fn, this_arg } => { tag(h, 399); iterable.as_ref().hash(h); map_fn.as_ref().hash(h); this_arg.is_some().hash(h); if let Some(t) = this_arg { t.as_ref().hash(h); } }
Expr::ParseInt { string, radix } => { tag(h, 400); string.as_ref().hash(h); radix.hash(h); }
Expr::ParseFloat(e) => { tag(h, 401); e.as_ref().hash(h); }
Expand Down
1 change: 1 addition & 0 deletions crates/perry-hir/src/walker/expr_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ where
| Expr::IteratorToArray(v)
| Expr::GetIterator(v)
| Expr::ForOfToArray(v)
| Expr::ForAwaitToArray(v)
| Expr::ObjectRest { object: v, .. }
| Expr::ProxyRevoke(v)
| Expr::ReflectOwnKeys(v)
Expand Down
1 change: 1 addition & 0 deletions crates/perry-hir/src/walker/expr_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ where
| Expr::IteratorToArray(v)
| Expr::GetIterator(v)
| Expr::ForOfToArray(v)
| Expr::ForAwaitToArray(v)
| Expr::ObjectRest { object: v, .. }
| Expr::ProxyRevoke(v)
| Expr::ReflectOwnKeys(v)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
async function* letters() {
yield "a";
await Promise.resolve();
yield "b";
}

const collect = async (label: string, iterable: AsyncIterable<unknown>) => {
const values: string[] = [];
for await (const value of iterable) {
values.push(String(value));
if (values.length > 4) {
values.push("guard");
break;
}
}
console.log(`${label}:`, values.join(","));
};

await collect("direct", letters());

const held = letters();
await collect("variable", held);

const runner = {
async *run(prefix: string) {
yield `${prefix}1`;
await Promise.resolve();
yield `${prefix}2`;
},
};
await collect("method result", runner.run("m"));

const custom = {
async *[Symbol.asyncIterator]() {
yield "s1";
yield Promise.resolve("s2");
},
};
await collect("custom async iterable", custom);

const promised: unknown[] = [];
for await (const value of [Promise.resolve(1), 2]) {
promised.push(value);
}
console.log("array promises:", promised.join(","));
Loading