Skip to content

fix(async): linearize await inside an async-generator finally; fix aliased native-class new - #8739

Merged
proggeramlug merged 1 commit into
mainfrom
merge/b29
Aug 24, 2026
Merged

fix(async): linearize await inside an async-generator finally; fix aliased native-class new#8739
proggeramlug merged 1 commit into
mainfrom
merge/b29

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Lands #8736 and #8738.

#8736 — linearize await inside an async-generator finally (fixes #8715)

This closes the finally analog of the #8681 await-in-catch deadlock that #8707 fixed — and it is precisely the gap #8707's own new test surfaced when it was rebased onto main, which reported:

raw Expr::Await survived async-generator linearization (would block-wait at runtime):
  ["await-in-finally: 2 raw await(s) survived"]

At the time I held #8707 and flagged that resolving it meant deciding whether finally should route through its entry state the way catch now does. This is that decision, made properly: the linearizer already splits the finally into its own dispatch states with a finally_entry_state, and the async-step driver now routes through them, so a finally await lowers to a real microtask suspend rather than a blocking busy-wait.

async_generator_linearizes_every_await_position passes on the merged result — verified explicitly, since that test is what named the gap.

#8738 — aliased native-class import new no longer throws (fixes #8730)

An aliased ESM named import of a Node built-in class threw ReferenceError: identifier is not defined when constructed at module init:

import { BlockList as Wj4 } from "net";              new Wj4()
import { AsyncLocalStorage as J_z } from "async_hooks"; new J_z()
import { PassThrough as Lrz } from "stream";         new Lrz()

lower_new's alias-rewrite block rewrites the callee from the local import name to the class's export name, so the construction path matches the un-aliased form that codegen's builtin-New dispatch recognizes. This broke the natively-compiled Claude Code cli.js 2.1.112 bundle, which constructs all three at module init, so nearly every command crashed with an uncaught ReferenceError.

Validation (on the merged result)

  • All 30 lint-job checkers pass
  • perry-transform --lib: 93 passed, 0 failed
  • perry-hir --lib: 334 passed, 0 failed
  • perry-runtime --lib (RUST_TEST_THREADS=1): 2669 passed, 0 failed
  • perry-codegen --lib: 1222 passed, 0 failed
  • Squashed tree verified identical to the validated tree

Both PRs carried their own changelog.d/ fragments. No version bump.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed async generators becoming unresponsive when await is used inside a finally block during .return() or early loop termination.
    • Ensured asynchronous cleanup continues through microtasks without blocking execution.
    • Fixed aliased imports of native Node.js classes so constructors such as BlockList, AsyncLocalStorage, and PassThrough work correctly.
  • Tests

    • Added coverage for async-generator cleanup scenarios and aliased native class construction.

…iased native-class new

Lands #8736 and #8738.

#8736 (fixes #8715) closes the `finally` analog of the #8681
`await`-in-`catch` deadlock that #8707 fixed. This is the exact gap
#8707's own new test surfaced when it was rebased -- it reported
"await-in-finally: 2 raw await(s) survived" -- so the two land as a pair.
An `await` inside a `finally` of a real `async function*` compiled to a
blocking busy-wait rather than an async suspend; the linearizer already
splits the finally into its own dispatch states with a
`finally_entry_state`, and the async-step driver now routes through them.

#8738 (fixes #8730) stops an aliased ESM named import of a Node built-in
class throwing `ReferenceError: identifier is not defined` when
constructed at module init -- `import { BlockList as Wj4 } from "net";
new Wj4()` and the same shape for `AsyncLocalStorage` and `PassThrough`.
`lower_new`'s alias-rewrite block rewrites the callee from the local
import name to the class's export name so the construction path matches
the un-aliased form that codegen's builtin-`New` dispatch recognizes.
This broke the natively-compiled Claude Code cli.js 2.1.112 bundle, which
constructs all three at module init, so nearly every command crashed.

No version bump.
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 99882508-497d-466f-b008-1712b669b243

📥 Commits

Reviewing files that changed from the base of the PR and between c203c77 and 80fb6c5.

📒 Files selected for processing (6)
  • changelog.d/8715-async-gen-finally-await.md
  • changelog.d/8730-aliased-native-class-new-resolution.md
  • crates/perry-hir/src/lower/expr_new.rs
  • crates/perry-hir/tests/aliased_native_new_resolution.rs
  • crates/perry-transform/src/async_to_generator_tests.rs
  • crates/perry-transform/src/generator/lower.rs

📝 Walkthrough

Walkthrough

The PR fixes async-generator .return() handling for awaits in finally blocks and native-module resolution for aliased Node built-in classes. It adds transform and lowering regression tests plus changelog entries.

Changes

Async-generator finally suspension

Layer / File(s) Summary
Shared async-generator return routing
crates/perry-transform/src/generator/lower.rs, crates/perry-transform/src/async_to_generator_tests.rs, changelog.d/8715-async-gen-finally-await.md
Async-generator .return() now resumes through __agstep. Await expressions in finally blocks suspend asynchronously. Synchronous generators retain local dispatch-loop handling. Tests cover combined try/finally, catch/finally, awaits, and yields.

Aliased native constructor resolution

Layer / File(s) Summary
Native constructor lookup and regression coverage
crates/perry-hir/src/lower/expr_new.rs, crates/perry-hir/tests/aliased_native_new_resolution.rs, changelog.d/8730-aliased-native-class-new-resolution.md
new lowering checks both the rewritten and original constructor identifiers in the native-module registry. Tests cover aliased Node built-in classes, unaliased imports, and unresolved constructors.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant AsyncGenerator
  participant __agstep
  participant FinallyAwait
  Caller->>AsyncGenerator: call .return()
  AsyncGenerator->>__agstep: route pending return as non-error resume
  __agstep->>FinallyAwait: execute finally state
  FinallyAwait-->>__agstep: suspend await on microtask queue
  __agstep-->>AsyncGenerator: complete or re-raise pending return
  AsyncGenerator-->>Caller: resolve .return()
Loading
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch merge/b29

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant