Skip to content

fix: resolve type aliases in the fused-await classifier — silent Result corruption on alias-bound futures (#1109, #1095) - #1110

Merged
aallan merged 4 commits into
mainfrom
fix/1095-alias-future-await
Jul 17, 2026
Merged

fix: resolve type aliases in the fused-await classifier — silent Result corruption on alias-bound futures (#1109, #1095)#1110
aallan merged 4 commits into
mainfrom
fix/1095-alias-future-await

Conversation

@aallan

@aallan aallan commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Summary

The #1095 probe — a genuinely concurrent repro, confirmed concurrent by WAT inspection — found the documented alias-Future limitation had mutated into a silent mis-lower. This PR fixes the classifier and lands the issue's acceptance arm 3 (limitation prose removed, regression tests pinning the behavior).

What the probe found (full matrix in this #1095 comment): a future bound through an alias-typed let (type F = Future<Result<String, String>>) or returned from a helper declared -> @F compiled clean, verified clean (2 Tier 1), fused its async (async_http_get import present — the concurrent lowering was exercised), but identity-lowered the await (async_await import absent). The kind-4 handle wrapper was then read as the Result ADT: the wrapper's 0xFEEDC004 tag word is never constructor tag 0 (Ok), so every two-arm match took Err on a 200/success response. Silent wrong answer, exit 0. The [E602] skip spec §9.5.4 documented as the guard "before any await could mis-lower" no longer fired once the v0.1.5 alias-payload work (#1039/#1046/#1054) let the alias-typed let compile.

Root cause: the fused-await classifier matched Future<Result<String, String>> literally at three sites — await_needs_check's SlotRef arm, compute_future_ret_fns, compute_future_ret_module_fns — while async(...) fusion keys on the call shape only and ignores the binding type.

Fix: the classifier now resolves aliases transitively before the literal check:

  • New shared resolve_type_alias walk in vera/monomorphize.py (transitive, generic-param-substituting, cycle-guarded, refinement-peeling), placed beside resolve_fn_type_alias in the deliberately codegen-free home async_fusion.py already imports.
  • resolve_fn_type_alias is re-based on the general walk — the Nested type alias to a fn type breaks apply_fn lowering: single-level alias resolution emits invalid WASM #867 lesson was that two near-identical alias walks drift; one walk, two views.
  • All classification sites route through one _resolves_to_future_result_string helper: the await SlotRef arm, both declared-return registries (bare + module-qualified), and the Fused-async await classification misses indirectly-called closure results #843 apply_fn closure-return arm (a closure declared fn(String -> F) had the same gap one level down).
  • Both await_needs_check call sites (the compilability.py pre-scan and the calls_markup.py translation) already passed type_aliases/type_alias_params, so the "both passes MUST agree" invariant holds by construction; codegen/core.py now threads the same registries into the two compute_* calls.

Regression tests (TestConcurrentAsyncAlias1109, written test-first and confirmed failing pre-fix — async_await import absent / ERR printed): WAT-shape pins (async_await imported for both alias shapes, sync http_get still suppressed) and runtime pins (distinctive body byte-exact through the Ok arm against a local ThreadingHTTPServer).

Docs (acceptance arm 3): the spec §9.5.4 limitation sentence, the KNOWN_ISSUES row, and the ROADMAP Stage 20 row are removed; gated counts updated (7,992 → 7,996).

Related Issues

Closes #1095
Closes #1109

Type of Change

  • Specification change
  • Compiler implementation
  • Bug fix
  • Tests
  • Documentation

Checklist

  • I have read CONTRIBUTING.md
  • My changes follow the project's coding standards
  • I have added/updated tests as appropriate
  • I have updated relevant documentation
  • All tests pass locally

Full suite including stress tests: 7,917 passed, 79 skipped, 0 failed (7,996 collected). mypy vera/ clean (97 files). check_conformance.py (163), check_examples.py (39), check_spec_examples.py, check_doc_counts.py, check_limitations_sync.py, check_site_assets.py, check_changelog_updated.py all pass.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed concurrent async/await lowering to correctly recognise Future<Result<…>> through transitive type aliases, including alias chains and payload aliases.
    • Ensured awaited Ok payload bytes are preserved and no longer mis-routed.
  • Tests

    • Added regression coverage for alias-based concurrent async/await behaviour, including deterministic overlap.
  • Documentation

    • Updated the async lowering specification and removed the related limitation note.
    • Refreshed changelog/known-issues entries and updated test-count figures.

…lt corruption on alias-bound futures (#1109, #1095)

The concurrent await lowering classified which shapes can carry a fused
future by matching the type Future<Result<String, String>> literally.
Type aliases — transparent everywhere else — did not participate:

* await_needs_check's SlotRef arm required type_name == "Future"
* compute_future_ret_fns/_module_fns matched declared returns literally

Call-shape fusion ignores the binding type, so a future bound through an
alias-typed let (type F = Future<Result<String, String>>) or returned
from a helper declared -> @f fused its async, then identity-lowered the
await: the kind-4 handle wrapper was read as the Result ADT and every
two-arm match took Err on a successful request. Check-clean, verify-
clean, silent wrong answer, exit 0. The E602 skip spec 9.5.4 documented
as the guard no longer fired once the v0.1.5 alias-payload work let the
alias-typed let compile — found by the #1095 genuinely-concurrent probe.

The classifier now resolves aliases transitively before the literal
check: a new shared resolve_type_alias walk in vera/monomorphize.py
(param-substituting, cycle-guarded) that resolve_fn_type_alias is
re-based on, so the fn-type and Future classifications cannot drift.
Covers the await slot arm, both declared-return registries, and the
apply_fn closure-return arm. Regression tests pin the async_await
import and byte-exact Ok payloads for both alias shapes; the spec
9.5.4 limitation sentence, KNOWN_ISSUES row, and ROADMAP Stage 20 row
are removed.
@coderabbitai

coderabbitai Bot commented Jul 17, 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: 10 minutes

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

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: 1c7206ca-647e-4374-9d92-91319738c5ca

📥 Commits

Reviewing files that changed from the base of the PR and between 0fb4208 and 54106f4.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • README.md
  • ROADMAP.md
  • TESTING.md
  • tests/test_wasm.py
  • vera/wasm/async_fusion.py
📝 Walkthrough

Walkthrough

The compiler now resolves type aliases transitively when classifying fused concurrent await operations, including generic substitutions and closure return types. Regression tests cover aliased bindings, helper returns, payloads, cycles, and concurrent overlap, alongside specification and release documentation updates.

Changes

Concurrent async alias handling

Layer / File(s) Summary
Shared transitive alias resolution
vera/monomorphize.py
Adds cycle-guarded alias traversal with refinement unwrapping and generic parameter substitution; function-type resolution reuses the shared resolver.
Alias-aware fused-await classification
vera/wasm/async_fusion.py, vera/codegen/core.py
Applies resolved types to slot awaits, declared function returns, module-qualified returns, and closure returns, passing alias metadata through compilation.
Regression coverage and release documentation
tests/test_codegen_effects.py, tests/test_wasm.py, spec/09-standard-library.md, KNOWN_ISSUES.md, ROADMAP.md, README.md, TESTING.md, CHANGELOG.md
Tests aliased bindings, payloads, helper returns, cycle handling, and concurrent overlap; removes the resolved limitation and updates test metrics and release notes.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CodeGenerator
  participant async_fusion
  participant resolve_type_alias
  participant WASM
  CodeGenerator->>async_fusion: pass return types and alias metadata
  async_fusion->>resolve_type_alias: resolve awaited and declared types
  resolve_type_alias-->>async_fusion: return fused future classification
  async_fusion->>WASM: emit async_http_get and async_await imports
Loading

Possibly related PRs

  • aallan/vera#842: Both modify fused async/await lowering and its future-handle classification.
  • aallan/vera#868: Both address alias-aware declared return classification for fused-await closures.
  • aallan/vera#972: Both modify transitive function-type alias resolution in the monomiser.

Suggested labels: compiler, tests, spec, ci, docs

🚥 Pre-merge checks | ✅ 6 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning A new KNOWN_ISSUES bug entry for cross-module type-alias namespace collisions is unrelated to #1095/#1109. Split that bug note into a separate PR or link it to #1111 instead of bundling it here.
Docstring Coverage ⚠️ Warning Docstring coverage is 72.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarises the alias-resolution fix and its silent wrong-Result impact.
Linked Issues check ✅ Passed The PR adds alias-aware fused-await classification, regression tests, and doc removals that satisfy #1095 and #1109.
Changelog Covers Public-Surface Changes ✅ Passed CHANGELOG.md explicitly covers the §9.5.4 async-await alias-resolution spec change and the limitation-sentence removal.
Spec And Implementation Move Together ✅ Passed PASS: vera/ async-await classification now resolves aliases transitively, and spec/09-standard-library.md was updated to say so; the docs and tests moved with the code.
Diagnostics Carry An Error Code ✅ Passed The patch only changes alias handling, tests, and docs; no new or edited Diagnostic/error_code definitions appear in the diff.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/1095-alias-future-await

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

@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.64286% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.72%. Comparing base (6aa9396) to head (54106f4).

Files with missing lines Patch % Lines
vera/monomorphize.py 91.66% 2 Missing ⚠️
vera/wasm/async_fusion.py 96.87% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1110   +/-   ##
=======================================
  Coverage   93.72%   93.72%           
=======================================
  Files          96       96           
  Lines       32708    32738   +30     
  Branches      456      456           
=======================================
+ Hits        30654    30683   +29     
- Misses       2041     2042    +1     
  Partials       13       13           
Flag Coverage Δ
javascript 78.41% <ø> (ø)
python 95.48% <94.64%> (+<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.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
vera/wasm/async_fusion.py (1)

144-195: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Preserve the declaring module’s alias namespace.

The module return registry is path-qualified, but both classifiers resolve its return expressions through one unqualified alias map. If two imported modules define F differently, one module’s -> @F`` can be silently classified using the other module’s alias, reintroducing the identity-await mis-lowering this change fixes.

Store resolved return expressions during module harvesting, or retain alias maps per module path and select the matching map for each registry entry. Add a collision regression with two modules defining different F aliases.

As per path instructions, “This is the compiler source. Review for correctness, type safety, and consistency with existing patterns.”

🤖 Prompt for 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.

In `@vera/wasm/async_fusion.py` around lines 144 - 195, Preserve each module’s
alias namespace when classifying entries in compute_future_ret_module_fns:
resolve every return expression using the alias map belonging to its module path
rather than the shared unqualified aliases map. Update module harvesting and
registry data as needed to retain per-module alias maps, and add a regression
covering two modules that define conflicting F aliases with different return
shapes.

Source: Path instructions

🤖 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 `@tests/test_codegen_effects.py`:
- Around line 2006-2096: Add a genuinely concurrent alias regression alongside
test_two_async_gets_overlap_deterministically, using an alias chain or generic
alias for the Future/Result type. Issue two async HTTP requests against handlers
that deterministically expose overlap, then await and validate both real
response payloads. Ensure the test fails for eager or fused execution while
covering transitive alias substitution.

---

Outside diff comments:
In `@vera/wasm/async_fusion.py`:
- Around line 144-195: Preserve each module’s alias namespace when classifying
entries in compute_future_ret_module_fns: resolve every return expression using
the alias map belonging to its module path rather than the shared unqualified
aliases map. Update module harvesting and registry data as needed to retain
per-module alias maps, and add a regression covering two modules that define
conflicting F aliases with different return shapes.
🪄 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: 39085359-b0a4-48e8-8d1a-a3f514ec5a1c

📥 Commits

Reviewing files that changed from the base of the PR and between 6aa9396 and af21516.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • KNOWN_ISSUES.md
  • README.md
  • ROADMAP.md
  • TESTING.md
  • spec/09-standard-library.md
  • tests/test_codegen_effects.py
  • vera/codegen/core.py
  • vera/monomorphize.py
  • vera/wasm/async_fusion.py
🔗 Linked repositories identified

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

  • aallan/vera-bench (manual)
💤 Files with no reviewable changes (1)
  • KNOWN_ISSUES.md

Comment thread tests/test_codegen_effects.py
@aallan

aallan commented Jul 17, 2026

Copy link
Copy Markdown
Owner Author

Focused adversarial review — sound fix, no blocking problems; one minor (cosmetic) finding

Ran a focused differential pass over the classifier's shape space against the branch head with the real toolchain. Verdict: the soundness fix is correct — nothing found that blocks merge. One minor, verified-harmless finding is worth a look.

Verified clean

Minor finding (LOW / cosmetic — not a blocker)

The generic-alias variant type F<T> = Future<T>; let @F<Result<String, String>> = async(...) correctly stays loud — E602 at the let-binding type mapper (Function 'main' ... 'F<Result<String, String>>' has no WASM representation — function skipped), $main absent from the module, and vera run fails loudly (No exported functions to call). The issue's claim that this shape stays a loud skip holds. ✅

But the fix's new generic-param substitution in resolve_type_alias now makes the classifier accept F<Result<String, String>> (it resolves the generic alias to Future<Result<String, String>>), so the pre-scan emits a dangling async_await import for the function that is then E602-skipped:

type F<T> = Future<T>;
public fn main(@Unit -> @Unit) requires(true) ensures(true) effects(<IO, Http, Async>) {
  let @F<Result<String, String>> = async(Http.get("http://127.0.0.1:8000/a"));
  let @Result<String, String> = await(@F<Result<String, String>>.0);
  match @Result<String, String>.0 { Ok(@String) -> IO.print(@String.0), Err(@String) -> IO.print("ERR") };
  ()
}

vera compile --wat emits (import "vera" "async_await" ...), but $main is skipped and never calls it — a dangling, unused import. Pre-fix, the classifier didn't resolve generic aliases, so async_await wasn't emitted for this shape.

Verified harmless today: main is loudly skipped, the import is never called, and the wasmtime host provides it unconditionally (the module instantiates — the run error is "no exported functions", not a missing import). But it's #1100-adjacent (a fused import emitted for a skipped function), and a target that provides async_await only when it detects async usage could trip on the dangling declaration. Cheap tidy if you want it: gate the classifier's generic-alias acceptance on the let-binding mapper being able to represent the shape (or, per the #1100 family, don't emit fused imports for E602-skipped functions). Low priority.

Not independently exercised (for honesty)

  • The module-qualified alias-return path (compute_future_ret_module_fns) — covered by construction (same shared helper), but I probed only the bare-fn-return shape.
  • The 2-hop shape at runtime — relied on the WAT differential plus the existing shape-1 runtime test (live server, byte-exact Ok payload).

Overall: strong, correct fix. The single finding is cosmetic and optional.

…ck; concurrent alias-overlap pin (#1109, PR review)

PR #1110 review rounds surfaced two follow-ups:

* Payload aliases: an alias INSIDE the future's type argument
  (Future<R> with type R = Result<String, String>) still identity-
  lowered the await — the outer-name resolver left the terminal's
  type_args untouched, so the exact-shape terminal check saw R, not
  Result.  Same #1109 mis-lower one level down (verified on the branch:
  async_await absent, Err printed on a 200).  The classifier now
  canonicalizes type arguments recursively
  (_canonicalize_type_expr_aliases); payload discrimination is
  unchanged — only the exact terminal Future<Result<String, String>>
  classifies.

* CodeRabbit's genuinely-concurrent ask: an aliased two-gets-overlap
  regression (alias chain type G = Future<...>; type F = G) alongside
  the #841 original — server-held deterministic ordering, both real
  payloads byte-exact; fails under eager evaluation by construction.

Plus two-hop-chain and payload-alias import/runtime pins (8 tests in
TestConcurrentAsyncAlias1109).

The out-of-diff finding (per-module alias namespaces in
compute_future_ret_module_fns) verified as a PRE-EXISTING, broader
bug: the flat _type_aliases setdefault merge corrupts cross-module fn
signatures on main independent of the classifier (future-free repro,
invalid module).  Filed as #1111 with a KNOWN_ISSUES row; the
classifier's dangerous direction is inexpressible (the checker resolves
aliases module-correctly, so an await of a non-future never
typechecks).
@aallan

aallan commented Jul 17, 2026

Copy link
Copy Markdown
Owner Author

Review round addressed — fixes pushed in 415daa22

Both CodeRabbit findings and the focused adversarial review verified against the branch. One still-valid issue fixed, one pre-existing bug filed, one cosmetic finding argued. Full suite incl. stress: 7,921 passed, 79 skipped, 0 failed (8,000 collected); mypy vera/ clean; conformance (163), examples (39), and all doc/count/limitation/changelog gates green.

CodeRabbit inline (tests): genuinely concurrent alias regression — done

test_two_async_gets_overlap_deterministically_aliased — two fused gets bound through an alias chain (type G = Future<Result<String, String>>; type F = G;), server-held deterministic overlap, both payloads byte-exact through Ok; fails under eager evaluation by construction. Details in the inline reply.

Bonus finding while verifying "alias chain": an alias inside the payload (Future<R> with type R = Result<String, String>) still mis-lowered — resolve_type_alias resolves the outer chain but left the terminal's type_args untouched, so the exact-shape check saw R, not Result. Same #1109 mis-lower one level down, confirmed live on the branch (async_await absent, ERR on a 200). Fixed: the terminal check now canonicalizes type arguments recursively (_canonicalize_type_expr_aliases). Payload discrimination unchanged — Future<Result<Int, Int>>, Future<String>, and non-Future aliases still canonicalize to shapes the check rejects (the review's verified-clean property holds). Import + runtime pins added.

CodeRabbit out-of-diff (async_fusion.py): per-module alias namespaces — verified real, but pre-existing and broader; filed #1111, not fixed here

Built the two-module collision (mod_a: type F = Future<Result<String, String>>, mod_b: type F = Int, both imported). Findings:

  • The flat merge itself (codegen/modules.pysetdefault on bare name, first module wins) is the bug, and it pre-exists this PR entirely: a future-free, async-free collision (F = Int vs F = String) check-passes and then emits an invalid WASM module on main (type mismatch: expected i32, found i64 — one module's fn signatures resolve through the other module's alias). Filed as #1111 with the repro; KNOWN_ISSUES row added.
  • For the classifier specifically, the dangerous misclassification direction is inexpressible: the checker resolves aliases module-correctly, so await(mod_b::grab_b(...)) on a non-future -> @F never typechecks — the await can't be written. The identity direction (a future-returning module fn hidden by another module's F) only arises in programs already broken by Cross-module type-alias namespaces merge flatly in codegen — same-named aliases corrupt signatures and emit invalid modules #1111's signature corruption.
  • Fixing per-module namespaces means re-keying every _type_aliases consumer in codegen (registration, signatures, fn-type resolution, eq/show/hash dispatch, …) — a refactor far beyond this PR's minimal scope. It gets its own issue, its own regression test, and its own PR.

Adversarial review: dangling async_await import for the generic-alias E602-skipped shape — argue (no change)

Verified the analysis: type F<T> = Future<T>; let @F<Result<String, String>> = ... still E602-skips loudly, and the classifier (which now resolves the generic alias) emits an async_await import the skipped function never calls. Not fixing here because:

  • Both import-emission passes agree (pre-scan and translation share the predicate — the load-bearing invariant), so the module validates; the import is dead, not dangling-in-the-Caller of an E602-skipped function emits a raw unknown func WAT error instead of a clean diagnostic #1100-sense (nothing calls a missing symbol — the symbol exists and is never called).
  • Every current host tolerates it: wasmtime registers host imports unconditionally; the browser runtime builds the import object by introspection (never invoked); --target wasi-p2 rejects Http programs outright.
  • The tidy "don't emit fused imports for E602-skipped functions" is exactly #1100's scope (import/call-site coherence for skipped functions) — fixing it piecemeal here for one import family would desync the family-wide fix.

Test-count bookkeeping

TestConcurrentAsyncAlias1109 now has 8 tests; gated counts updated (7,996 → 8,000 in TESTING.md / README / ROADMAP, file row 111 → 115).

@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 137-145: Update _canonicalize_type_expr_aliases and its recursive
calls to carry a path-local active-type stack, detecting a type alias already
being resolved before descending and returning None for cycles such as type A =
Future<A>. Ensure the stack is scoped to the current recursion path and is
removed on unwind so valid repeated aliases still resolve. Add a focused unit
test covering the recursive payload-alias cycle and the documented None result.
🪄 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: 766851b3-277b-42bf-9209-23e03e6f636e

📥 Commits

Reviewing files that changed from the base of the PR and between af21516 and 415daa2.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • KNOWN_ISSUES.md
  • README.md
  • ROADMAP.md
  • TESTING.md
  • tests/test_codegen_effects.py
  • vera/wasm/async_fusion.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

aallan commented Jul 17, 2026

Copy link
Copy Markdown
Owner Author

Re-verified 415daa22 — resolved; merge-ready, no blockers

Re-ran the focused pass against the new head. All three responses land.

Bonus fix (payload-inner alias) — verified correct, and this was a genuine gap in my first pass. I probed outer-chain aliases (2-hop) but not an alias inside the payload (Future<R> with type R = Result<String, String>) — good catch. Re-checked on 415daa22:

  • _canonicalize_type_expr_aliases is faithful by inspection: it substitutes alias names for their bodies (outer chain via resolve_type_alias, then recursively into each type_arg) but never rewrites a non-alias name, so a wrong payload (Future<Result<Int, Int>> literal or via alias, Future<String>, non-Future) canonicalizes to a shape the unchanged exact-check rejects — the "discrimination unchanged" claim holds. Cyclic anywhere → None → conservative identity, preserved.
  • End-to-end: the payload-inner-alias shape now fuses (async_await present, eager http_get absent), and the literal + outer-alias positives still fuse — no regression.

#1111 (per-module alias namespaces) — agree with the scope-out. Pre-existing and broader (per your repro, a future-free F=Int / F=String collision already emits an invalid module on main), and the classifier-specific dangerous direction is inexpressible because the checker resolves aliases module-correctly. Filed with a repro + KNOWN_ISSUES row — correct handling. (I did not independently reproduce #1111; the reasoning and repro read as sound.)

Dangling async_await import — accept the argue-no-change. The distinction is right: it's a dead import (the symbol exists and is never called), not a #1100 missing-symbol failure; every current host tolerates it; and the tidy belongs in #1100's family-wide import/skip-coherence fix rather than piecemeal here. I flagged it LOW/optional to begin with — no objection to deferring.

Updated verdict: sound and merge-ready. CI green (29/29 on 415daa22), counts reconciled (8,000). The fix is now complete at both the outer and payload alias levels — stronger than what I first reviewed. Nicely run down.

 review)

CodeRabbit: _canonicalize_type_expr_aliases recursed into type
arguments with no cycle guard, so a cyclic payload alias
(type A = Future<A>, or the mutual A = Future<B> / B = Array<A>)
regenerated itself one descent at a time — the outer resolver's
seen-set never fires because each hop terminates at a non-alias
container — and spun to RecursionError instead of returning the
documented None.  Verified against the branch (RecursionError,
confirmed).

The guard threads a path-local active set through the descent: a name
already being resolved on the current path yields None (the classifier
then conservatively keeps the identity lowering); the set unwinds with
the path, so a name repeated across SIBLING branches (Pair<R, R>)
still resolves.  The checker rejects alias cycles upstream with E132
(#1059) — this is defence-in-depth, matching resolve_type_alias and
_canonicalize_alias_slot_name.

Unit pins in test_wasm.py (TestCanonicalizeTypeExprAliases): direct
cycle -> None + predicate -> False, mutual cycle -> None, sibling
repeats fully resolve.

@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 154-163: Update _canonicalize_type_expr_aliases so the active-path
guard unwraps a RefinementType before determining the argument’s NamedType name,
ensuring refinement-wrapped alias cycles return None instead of recursing.
Preserve existing handling for direct NamedType arguments, and add a regression
test covering an alias such as A = Future<{ `@A` | true }> and asserting
conservative None resolution.
🪄 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: bb2c5397-9e64-499d-9b6f-1f745a094734

📥 Commits

Reviewing files that changed from the base of the PR and between 415daa2 and 0fb4208.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • README.md
  • ROADMAP.md
  • TESTING.md
  • tests/test_wasm.py
  • vera/wasm/async_fusion.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
…review)

CodeRabbit: the path-local active-path guard inspected only bare
NamedType arguments, so a refinement-wrapped payload cycle
(type A = Future<{ @A | true }>) slipped past it — the resolver peels
the refinement inside the recursion, regenerating the same argument
with the active set never marked, and spun to RecursionError.
Verified against the branch (RecursionError, confirmed; doubly-nested
refinements too).

The probe now unwraps refinement layers before determining the name to
guard on; direct NamedType arguments probe as themselves, unchanged.
Regression pins in TestCanonicalizeTypeExprAliases: the refinement-
wrapped cycle resolves to the documented None (predicate -> False),
and a valid refinement-wrapped alias still resolves to its
representation base (refinements erase at runtime).
@aallan

aallan commented Jul 17, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai — registering the response to your out-of-diff finding on vera/wasm/async_fusion.py (per-module alias namespaces in compute_future_ret_module_fns), which was addressed in the main-thread comment above but didn't tag you.

Summary of the verification and disposition (full detail in the earlier comment):

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@aallan Thanks for explicitly registering this. I agree with the disposition: the bare-name _type_aliases merge is a real, pre-existing cross-module codegen correctness bug, but fixing it requires a coherent namespace-aware refactor across codegen—not a local change to compute_future_ret_module_fns.

Given that #1111 tracks the broader defect with a reproduction, and this PR documents it in KNOWN_ISSUES.md, keeping that refactor out of the alias-fusion fix is appropriate. No further action requested on this PR for the out-of-diff finding.


✏️ Learnings added
Learnt from: aallan
URL: https://github.com/aallan/vera/pull/1110

Timestamp: 2026-07-17T23:37:47.245Z
Learning: In the Vera Python compiler, imported type aliases are merged by bare name in `vera/codegen/modules.py` using `setdefault`, so aliases with the same name from different modules can corrupt codegen signatures. This is a pre-existing, repository-wide namespace issue tracked by GitHub issue `#1111`; do not treat it as a local regression in `vera/wasm/async_fusion.py` alias-future classification changes.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@aallan
aallan merged commit afa8532 into main Jul 17, 2026
29 checks passed
@aallan
aallan deleted the fix/1095-alias-future-await branch July 17, 2026 23:41
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