Skip to content

fix(wasm): fused-async await classifies indirectly-called closures by declared return type#868

Merged
aallan merged 1 commit into
mainfrom
fix/843-fused-await-loud
Jul 3, 2026
Merged

fix(wasm): fused-async await classifies indirectly-called closures by declared return type#868
aallan merged 1 commit into
mainfrom
fix/843-fused-await-loud

Conversation

@aallan

@aallan aallan commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

Burndown PR for #843 (promoted limitation→bug: silent wrong value). Under the #841 fused-async lowering, an inline await around an indirectly-called closure returning Future<Result<String,String>> lowered to identity — the fused wrapper was read as the ADT, the Err payload came out as a zero-length string, and the request outcome was silently discarded.

Shipped the ceiling (correct classification), with the loud floor proven already-guaranteed:

  • await_needs_check gains an apply_fn arm classifying by the closure's declared return type (fn-typed slot through its FnType alias, or inline AnonFn) — the post-apply_fn is unregistered in the checker: spurious E200 on every use, arity/type errors swallowed, failures leak raw WAT assembler errors #854 declared-type precedent. Resolvable indirect awaits now emit the fused-handle check and run correctly.
  • The floor needs no new diagnostic: the arm's shape coverage is deliberately identical to _infer_apply_fn_return_type's, so any closure shape it can't resolve is exactly what _translate_apply_fn already rejects loudly with E616 — verified empirically. No silent path to an identity await remains (differential soundness relationship between the two consultors).
  • Both await_needs_check call sites (calls_markup + compilability) thread type aliases, keeping the two import-emission passes in agreement.
  • RED evidence: the reconstructed repro returned 0 (outcome discarded, no async_await import in the WAT) on the unfixed tree, 1 post-fix with the import present; the let-bind workaround control is green on both sides. Mutation kill: disabling the arm flips both classification tests RED with controls green.
  • Blast radius: zero await(apply_fn(...)) shapes anywhere in conformance/examples/docs — purely additive.
  • Docs: spec §9.5.4's known-limitation sentence updated; the Fused-async await classification misses indirectly-called closure results #843 KNOWN_ISSUES Limitations row deleted.

Discovered during the fix and filed as #867 (row added here as the discovering PR): nested type aliases to fn types break apply_fn lowering (loud WASM validation failure, pre-existing single-level alias resolution). Queued separately.

Orchestrator flip: 2 RED against main's fusion code, 4/4 green on the branch.

Closes #843

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Fixed a case where await on indirectly called closures could return the wrong value or fail silently.
    • await now behaves correctly for supported fused-async closure patterns, while unsupported cases are rejected more clearly.
  • Documentation

    • Updated the changelog, async language notes, known issues, roadmap, and testing information to reflect the latest behaviour and project metrics.
  • Tests

    • Added regression coverage for indirect closure await handling, runtime checks, and the documented workaround.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 19 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c796cd5c-61af-45a4-8b99-98d7320acc58

📥 Commits

Reviewing files that changed from the base of the PR and between 8097d20 and edb962b.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • KNOWN_ISSUES.md
  • ROADMAP.md
  • TESTING.md
  • spec/09-standard-library.md
  • tests/test_codegen_effects.py
  • tests/test_codegen_modules.py
  • vera/codegen/compilability.py
  • vera/wasm/async_fusion.py
  • vera/wasm/calls_markup.py
📝 Walkthrough

Walkthrough

This PR fixes fused-async await classification for indirectly-called closures by resolving declared closure return types (via a new type_aliases parameter threaded through await_needs_check), adds regression tests, and updates changelog, known-issues, spec, roadmap, and testing documentation.

Changes

Indirect closure await classification fix

Layer / File(s) Summary
Closure return-type resolution in await_needs_check
vera/wasm/async_fusion.py, vera/codegen/compilability.py, vera/wasm/calls_markup.py
Adds _apply_fn_closure_ret_type and apply_fn_awaits_fused_future helpers, extends await_needs_check with a type_aliases parameter to classify apply_fn closures via declared return type, and updates call sites to pass self._type_aliases.
Regression tests for indirect closure fused await
tests/test_codegen_modules.py
Adds TestIndirectClosureFusedAwait843, verifying async_await import usage, correct runtime Err-based output, let-bind control behaviour, and E616 rejection with $main omitted for unresolvable closure returns.
Changelog, known issues, spec, and metrics updates
CHANGELOG.md, KNOWN_ISSUES.md, spec/09-standard-library.md, ROADMAP.md, TESTING.md
Documents the fix and a new #867 bug, removes the resolved #841 limitation entry, revises spec §9.5.4 wording, and bumps reported test counts/descriptions.

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

Sequence Diagram(s)

sequenceDiagram
  participant CallsMarkup as calls_markup._translate_await
  participant Compilability as compilability._scan_io_ops
  participant AsyncFusion as async_fusion.await_needs_check
  participant ClosureResolver as _apply_fn_closure_ret_type

  CallsMarkup->>AsyncFusion: await_needs_check(arg, future_ret_fns, future_ret_module_fns, type_aliases)
  Compilability->>AsyncFusion: await_needs_check(arg, future_ret_fns, future_ret_module_fns, type_aliases)
  AsyncFusion->>ClosureResolver: resolve apply_fn closure declared return type
  ClosureResolver-->>AsyncFusion: TypeExpr or None
  AsyncFusion-->>AsyncFusion: apply_fn_awaits_fused_future checks Future<Result<String,String>>
  AsyncFusion-->>CallsMarkup: bool needs fused-handle check
  AsyncFusion-->>Compilability: bool registers async_await op
Loading

Possibly related issues

Possibly related PRs

  • aallan/vera#842: Modifies the same fused-async await classification pipeline (async_fusion.py, calls_markup.py, compilability.py) that this PR extends.

Suggested labels: compiler, tests, spec, docs

🚥 Pre-merge checks | ✅ 8
✅ Passed checks (8 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise, specific, and matches the main fix around indirectly-called closure await classification.
Linked Issues check ✅ Passed The PR implements the #843 fix by classifying indirect closure awaits via declared return type and adds the required tests and docs.
Out of Scope Changes check ✅ Passed No clear out-of-scope code changes are present; the extra docs and metrics updates support the new async-fusion work.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Changelog Covers Public-Surface Changes ✅ Passed CHANGELOG.md explicitly describes the §9.5.4 async/await spec update and the E616 loud-failure floor, covering the public-surface change.
Spec And Implementation Move Together ✅ Passed Spec §9.5.4 matches the new apply_fn await classification and E616 floor, and both await-check call sites pass type_aliases to implement it.
Diagnostics Carry An Error Code ✅ Passed No new diagnostic site was added; the only cited failure path is existing E616, which already has a stable code in vera/errors.py and codegen/core.py.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/843-fused-await-loud

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@vera/wasm/async_fusion.py`:
- Around line 168-231: The closure-arg shape handling in
_apply_fn_closure_ret_type is duplicated from vera/wasm/inference.py, which
risks fused-await classification drifting from apply_fn lowering if supported
shapes change. Factor the shared “resolve declared return TypeExpr from closure
arg” logic into one canonical helper and have both _apply_fn_closure_ret_type
and the inference path reuse it, while keeping each caller’s own
translation/classification logic separate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4035b8de-d7ed-4e5f-aeab-8c9632bd3da2

📥 Commits

Reviewing files that changed from the base of the PR and between 18cce45 and 8097d20.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • KNOWN_ISSUES.md
  • ROADMAP.md
  • TESTING.md
  • spec/09-standard-library.md
  • tests/test_codegen_modules.py
  • vera/codegen/compilability.py
  • vera/wasm/async_fusion.py
  • vera/wasm/calls_markup.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • aallan/vera-bench (manual)

Comment thread vera/wasm/async_fusion.py
aallan pushed a commit that referenced this pull request Jul 2, 2026
…lassification (PR #868 panel)

The PR #868 review panel found a live silent wrong value on the #843 fix:
a GENERIC FnType alias instantiated to the fused type at the slot —
`type Producer<T> = fn(String -> T) effects(<Http, Async>);` awaited as
`await(apply_fn(@Producer<Future<Result<String, String>>>.0, url))` —
classified as unresolvable because `_apply_fn_closure_ret_type` returned
the alias's RAW bare-`T` return with no type-arg substitution, while
`_infer_apply_fn_return_type` DOES substitute via alias_map and builds a
VALID call_indirect signature. Result: no E616, no WASM trap, identity
await, wrapper read as the ADT — check+verify+run all green, returning 0
where the non-generic equivalent returns 1, with zero `async_await`
references in the WAT instead of the import + probe call.

Fix: `_substitute_type_params` (the TypeExpr-level twin of the
inference's alias_map substitution, same guard) applied in
`_apply_fn_closure_ret_type`; `type_alias_params` threaded through
`await_needs_check` from both call sites. The two closure-type consultors
now genuinely match — which IS the floor invariant.

Also per the panel:
- The #867 nested-alias shape is pinned still-loud (compiles, traps at
  WASM validation — the substitution must not resolve it into a silent
  path), with docstrings/spec §9.5.4 corrected to state BOTH backstops
  precisely: [E616] for FnCall-shaped closure args, the #867
  call_indirect width-mismatch validation trap for alias-of-alias slots
  (the previous wording over-claimed E616 for all unresolvable shapes).
- Ok-path pinned: a local-HTTP-server test requires the response payload
  byte-exact through the indirect await (the Err-substring tests could
  not discriminate Ok-corruption from the same 0).
- A comment on the compilability pre-scan threading notes it is
  redundant-but-benign next to calls_markup's single registration site.

Mutation-validated: disabling the generic substitution flips exactly the
generic-alias WAT pin RED; disabling the whole apply_fn arm flips the
slot, anon, and Ok-path tests RED.

Co-Authored-By: Claude <[email protected]>
@aallan aallan force-pushed the fix/843-fused-await-loud branch from 840a770 to 79ccb42 Compare July 3, 2026 00:02
@aallan

aallan commented Jul 3, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.25000% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.96%. Comparing base (9fd03e4) to head (edb962b).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
vera/wasm/async_fusion.py 81.25% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #868      +/-   ##
==========================================
- Coverage   92.96%   92.96%   -0.01%     
==========================================
  Files          95       95              
  Lines       28914    28945      +31     
  Branches      360      360              
==========================================
+ Hits        26881    26909      +28     
- Misses       2027     2030       +3     
  Partials        6        6              
Flag Coverage Δ
javascript 75.01% <ø> (ø)
python 94.98% <81.25%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aallan

aallan commented Jul 3, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

… declared return type (#843)

Squash of the fix + the #867 discovery row + the panel round (generic-alias
substitution restoring the floor invariant, per-shape backstop wording,
Ok-path pin).

Co-Authored-By: Claude <[email protected]>
@aallan aallan force-pushed the fix/843-fused-await-loud branch from 79ccb42 to edb962b Compare July 3, 2026 00:12
@aallan

aallan commented Jul 3, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@aallan aallan merged commit ab25190 into main Jul 3, 2026
27 checks passed
@aallan aallan deleted the fix/843-fused-await-loud branch July 3, 2026 00:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fused-async await classification misses indirectly-called closure results

1 participant