fix: resolve type aliases in the fused-await classifier — silent Result corruption on alias-bound futures (#1109, #1095) - #1110
Conversation
…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.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. 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 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughThe compiler now resolves type aliases transitively when classifying fused concurrent ChangesConcurrent async alias handling
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
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 6 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (6 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Codecov Report❌ Patch coverage is
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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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 liftPreserve 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
Fdifferently, 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
Faliases.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
📒 Files selected for processing (10)
CHANGELOG.mdKNOWN_ISSUES.mdREADME.mdROADMAP.mdTESTING.mdspec/09-standard-library.mdtests/test_codegen_effects.pyvera/codegen/core.pyvera/monomorphize.pyvera/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
Focused adversarial review — sound fix, no blocking problems; one minor (cosmetic) findingRan 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 But the fix's new generic-param substitution in
Verified harmless today: Not independently exercised (for honesty)
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).
Review round addressed — fixes pushed in
|
There was a problem hiding this comment.
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
📒 Files selected for processing (7)
CHANGELOG.mdKNOWN_ISSUES.mdREADME.mdROADMAP.mdTESTING.mdtests/test_codegen_effects.pyvera/wasm/async_fusion.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
aallan/vera-bench(manual)
Re-verified
|
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.
There was a problem hiding this comment.
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
📒 Files selected for processing (6)
CHANGELOG.mdREADME.mdROADMAP.mdTESTING.mdtests/test_wasm.pyvera/wasm/async_fusion.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
aallan/vera-bench(manual)
…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).
|
@coderabbitai — registering the response to your out-of-diff finding on Summary of the verification and disposition (full detail in the earlier comment):
|
|
Tip For best results, initiate chat on the files or code changes.
Given that ✏️ Learnings added
|
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-> @Fcompiled clean, verified clean (2 Tier 1), fused itsasync(async_http_getimport present — the concurrent lowering was exercised), but identity-lowered theawait(async_awaitimport absent). The kind-4 handle wrapper was then read as theResultADT: the wrapper's0xFEEDC004tag word is never constructor tag0(Ok), so every two-arm match tookErron 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-typedletcompile.Root cause: the fused-await classifier matched
Future<Result<String, String>>literally at three sites —await_needs_check'sSlotRefarm,compute_future_ret_fns,compute_future_ret_module_fns— whileasync(...)fusion keys on the call shape only and ignores the binding type.Fix: the classifier now resolves aliases transitively before the literal check:
resolve_type_aliaswalk invera/monomorphize.py(transitive, generic-param-substituting, cycle-guarded, refinement-peeling), placed besideresolve_fn_type_aliasin the deliberately codegen-free homeasync_fusion.pyalready imports.resolve_fn_type_aliasis 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._resolves_to_future_result_stringhelper: theawaitSlotRefarm, both declared-return registries (bare + module-qualified), and the Fused-async await classification misses indirectly-called closure results #843apply_fnclosure-return arm (a closure declaredfn(String -> F)had the same gap one level down).await_needs_checkcall sites (thecompilability.pypre-scan and thecalls_markup.pytranslation) already passedtype_aliases/type_alias_params, so the "both passes MUST agree" invariant holds by construction;codegen/core.pynow threads the same registries into the twocompute_*calls.Regression tests (
TestConcurrentAsyncAlias1109, written test-first and confirmed failing pre-fix —async_awaitimport absent /ERRprinted): WAT-shape pins (async_awaitimported for both alias shapes, synchttp_getstill suppressed) and runtime pins (distinctive body byte-exact through theOkarm against a localThreadingHTTPServer).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
Checklist
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.pyall pass.Summary by CodeRabbit
Bug Fixes
async/awaitlowering to correctly recogniseFuture<Result<…>>through transitive type aliases, including alias chains and payload aliases.Okpayload bytes are preserved and no longer mis-routed.Tests
Documentation