fix(codegen): injective monomorphized WAT name mangling (#775)#883
Conversation
|
Important Review skippedNo new commits to review since the last review. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughFixes monomorphized WAT symbol collisions by introducing a shared injective type-name mangler, wiring clone emission, generic-call rewriting, registry lookup, and equality helper naming through it, and adding regression coverage plus matching test/documentation updates. ChangesInjective mangling fix and propagation
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant CallsResolver
participant InferenceLookup
participant AdtEqNaming
participant Monomorphizer
CallsResolver->>Monomorphizer: _mangle_fn_name(call target, type vars)
InferenceLookup->>Monomorphizer: _mangle_fn_name(call.name, parts)
AdtEqNaming->>Monomorphizer: mangle_type_name(type_name)
Monomorphizer-->>CallsResolver: mangled call symbol
Monomorphizer-->>InferenceLookup: mangled registry key
Monomorphizer-->>AdtEqNaming: escaped type name
Possibly related issues
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 7 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (7 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #883 +/- ##
=======================================
Coverage 93.01% 93.01%
=======================================
Files 95 95
Lines 29233 29232 -1
Branches 360 360
=======================================
Hits 27190 27190
+ Misses 2037 2036 -1
Partials 6 6
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
🤖 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/calls.py`:
- Around line 606-616: The comment in Monomorphizer._infer_type_args_from_call
is stale and contradicts the actual mangling contract; update the wording to say
phantom vars default to Bool, matching the existing mapping assignment and the
shared injective mangler behavior used by Monomorphizer._mangle_fn_name. Keep
the code unchanged and revise the inline documentation near the forall_vars loop
so it reflects the true default and avoids suggesting Unit anywhere in this
call-site path.
🪄 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: 22bbe5a3-d029-4e05-a3fa-0ffe0c4903cb
⛔ Files ignored due to path filters (1)
tests/conformance/ch09_option_result_combinators.verais excluded by!**/*.vera
📒 Files selected for processing (14)
CHANGELOG.mdKNOWN_ISSUES.mdROADMAP.mdTESTING.mdtests/test_checker_errors.pytests/test_codegen_data_types.pytests/test_codegen_monomorphize.pytests/test_monomorphize_differential.pytests/test_runtime_traps.pyvera/codegen/core.pyvera/monomorphize.pyvera/wasm/calls.pyvera/wasm/inference.pyvera/wasm/operators.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
…#883 review) Skip-changelog: comment-only edit; the PR already carries its CHANGELOG bullet Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Claude <[email protected]>
Shared prefix-code escape (mangle_type_name) proven injective; #773's _adt_eq_fn_name delegates to it; three producer sites unified (clone emission, calls.py, inference.py). Rename-purity differential: 0 semantic WAT changes across the corpus. Includes the #884 intake row (Z3 sort-name collision — the same lossy encoding still latent verifier-side). Co-Authored-By: Claude <[email protected]>
551424f to
cb0f080
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
…onger collide (#884) The verifier named Z3 datatype sorts with the old lossy sanitize (`key.replace("<", "_").replace(">", "").replace(", ", "_")`), so a monomorphized `Box<Int>` and a flat ADT literally named `Box_Int` both became the Z3 name `Box_Int`. Z3's per-context datatype cache conflates same-named sorts (the last `create()` wins and earlier same-named sorts silently adopt its structure), so `Box<Int>`'s sort acquired the flat ADT's `MkBoxInt(Bool)` constructor — a false E500 counterexample (`@Box<Int>.0 = MkBoxInt(False)`) on trivially-true valid code. Both Z3 sort-name sites (`_get_or_create_adt_sort` and `_get_or_create_tuple_sort` in `vera/smt.py`) now route through the injective `mangle_type_name` mangler #775 introduced for WAT symbols, so distinct type keys can never share a Z3 sort name. The full-type-string sort cache and the #871 nested-Float64 fpEQ walk are unaffected (the cache is keyed by the type string, not the Z3-visible name). Soundness-safe: the conflation only freed field constraints, producing conservative false negatives, never a false Tier-1 — a genuinely-false ensures over the colliding pair is still disproved after the fix, now with the correct `MkBox` constructor. Both sort-name sites are independently pinned, one collision per site, so neither `mangle_type_name` call can be reverted without flipping a test: - ADT site (`_get_or_create_adt_sort`): the `Box<Int>` / flat `Box_Int` collision. Reverting it re-collides the sorts and flips the three `Box`-collision tests (verifies-clean, correct-constructor, and the false-prove probe) back to the false E500. - Tuple site (`_get_or_create_tuple_sort`): a `Tuple<Tuple<Int>, Int>` vs `Tuple<Tuple<Int, Int>>` collision. Both keys lossy-sanitize to `Tuple_Tuple_Int_Int`; under the lossy name the two synthesised tuple sorts share one Z3 datatype, and a match arm projecting a field index the conflated sort does not carry crashes the verifier with `z3.z3types.Z3Exception: Invalid accessor index` at `sort.accessor` (in `_bind_pattern`). Reverting it flips the new tuple-collision test. Confirmed load-bearing: reverting this site with only the `Box` collision test present flipped nothing. The rename also broke the verifier's Array-element reverse lookup, fixed here since #884 caused it. `_get_element_sort_for_array`'s tier-3 fallback reconstructed the `_z3_sorts` key by string surgery (`elt_key.replace("_", "<", 1) + ">"`) that assumed the old lossy naming, so after the rename a generic-ADT element (`Array<Box<Int>>` → sort `Array_Box_LInt_R`) produced the non-matching candidate `Box<LInt_R>` and the lookup silently returned None. Latent — tier 1's creation-time direct map (`_array_element_sorts`) fires for every live path, and no corpus program indexes an `Array<Generic<T>>` in a contract this way — but dead-on-arrival for that shape. A new injective inverse `unmangle_type_name` (a left-to-right prefix-code decode, round-trip-tested against `mangle_type_name`) recovers the key correctly, and the stale tier-3 comment is corrected. Pinned by tests/test_verifier_sort_name_collision_884.py (ADT + tuple collisions, rename-control, false-prove disproved for the right reason), the ch06_adt_sort_disambiguation conformance program, TestSmtContextDirect.test_element_sort_reverse_lookup_tier3_generic_adt (the Array-element unit pin), and the unmangle round-trip / non-range-input tests in TestMangleInjectivity. Mutation-validated: reverting either sort-name site to the lossy sanitize flips its collision test RED, and reverting the Array-element lookup to the old string surgery flips the reverse-lookup unit test RED. Pre-existing on main; found by the PR #883 adversarial panel. Co-Authored-By: Claude <[email protected]>
…onger collide (#884) The verifier named Z3 datatype sorts with the old lossy sanitize (`key.replace("<", "_").replace(">", "").replace(", ", "_")`), so a monomorphized `Box<Int>` and a flat ADT literally named `Box_Int` both became the Z3 name `Box_Int`. Z3's per-context datatype cache conflates same-named sorts (the last `create()` wins and earlier same-named sorts silently adopt its structure), so `Box<Int>`'s sort acquired the flat ADT's `MkBoxInt(Bool)` constructor — a false E500 counterexample (`@Box<Int>.0 = MkBoxInt(False)`) on trivially-true valid code. Both Z3 sort-name sites (`_get_or_create_adt_sort` and `_get_or_create_tuple_sort` in `vera/smt.py`) now route through the injective `mangle_type_name` mangler #775 introduced for WAT symbols, so distinct type keys can never share a Z3 sort name. The full-type-string sort cache and the #871 nested-Float64 fpEQ walk are unaffected (the cache is keyed by the type string, not the Z3-visible name). Soundness-safe: the conflation only freed field constraints, producing conservative false negatives, never a false Tier-1 — a genuinely-false ensures over the colliding pair is still disproved after the fix, now with the correct `MkBox` constructor. Both sort-name sites are independently pinned, one collision per site, so neither `mangle_type_name` call can be reverted without flipping a test: - ADT site (`_get_or_create_adt_sort`): the `Box<Int>` / flat `Box_Int` collision. Reverting it re-collides the sorts and flips the three `Box`-collision tests (verifies-clean, correct-constructor, and the false-prove probe) back to the false E500. - Tuple site (`_get_or_create_tuple_sort`): a `Tuple<Tuple<Int>, Int>` vs `Tuple<Tuple<Int, Int>>` collision. Both keys lossy-sanitize to `Tuple_Tuple_Int_Int`; under the lossy name the two synthesised tuple sorts share one Z3 datatype, and a match arm projecting a field index the conflated sort does not carry crashes the verifier with `z3.z3types.Z3Exception: Invalid accessor index` at `sort.accessor` (in `_bind_pattern`). Reverting it flips the new tuple-collision test. Confirmed load-bearing: reverting this site with only the `Box` collision test present flipped nothing. The rename also broke the verifier's Array-element reverse lookup, fixed here since #884 caused it. `_get_element_sort_for_array`'s tier-3 fallback reconstructed the `_z3_sorts` key by string surgery (`elt_key.replace("_", "<", 1) + ">"`) that assumed the old lossy naming, so after the rename a generic-ADT element (`Array<Box<Int>>` → sort `Array_Box_LInt_R`) produced the non-matching candidate `Box<LInt_R>` and the lookup silently returned None. Latent — tier 1's creation-time direct map (`_array_element_sorts`) fires for every live path, and no corpus program indexes an `Array<Generic<T>>` in a contract this way — but dead-on-arrival for that shape. A new injective inverse `unmangle_type_name` (a left-to-right prefix-code decode, round-trip-tested against `mangle_type_name`) recovers the key correctly, and the stale tier-3 comment is corrected. Pinned by tests/test_verifier_sort_name_collision_884.py (ADT + tuple collisions, rename-control, false-prove disproved for the right reason), the ch06_adt_sort_disambiguation conformance program, TestSmtContextDirect.test_element_sort_reverse_lookup_tier3_generic_adt (the Array-element unit pin), and the unmangle round-trip / non-range-input tests in TestMangleInjectivity. Mutation-validated: reverting either sort-name site to the lossy sanitize flips its collision test RED, and reverting the Array-element lookup to the old string surgery flips the reverse-lookup unit test RED. Pre-existing on main; found by the PR #883 adversarial panel. Co-Authored-By: Claude <[email protected]>
Summary
_mangle_fn_namebuilt monomorphized WAT symbols by lossy</>/,replacement, so distinct instantiations collided onto one symbol —g<A_B, C>andg<A, B_C>both mangled tog$A_B_C, andg<Map<String, Int>>collided with a user ADT literally namedMap_String_Int. On current main every reachable collision is loud (both clones emit → wasmtimeduplicate func identifier); the RED battery also settles the issue's open question — a silent-wrong-value shape was not constructible on main (three documented attempts, each route ends loud).The fix
One shared escape,
mangle_type_nameinvera/monomorphize.py:_→__,<→_L,>→_R,,→_C, space→_S, vectors joined with_J. Injectivity proof in the docstring (mechanical, prefix-code argument: every escaped output is uniquely decodable left-to-right;_Jcannot occur inside a component). #773's_adt_eq_fn_namenow delegates to it — one naming convention, not two. Three producer sites unified (clone emission;calls.py::_resolve_generic_call, previously a hand-copied sanitizer;inference.pyret-type lookup, previously a raw join that missed every parameterized instantiation — the three-site desync a partial fix would have introduced is pinned by a purpose-built nested-generic test).Rename-purity differential (the re-baseline sign-off)
All 140 compilable conformance+example programs compiled on main and on the branch, WAT diffed: 134 byte-identical, 6 pure-rename, 0 semantic changes (9 distinct symbol renames, e.g.
option_map$Int_Int→option_map$Int_JInt). One file is rename-pure modulo a bounded data-offset shift — the clone name is embedded in postcondition-message data strings. Replication script included in the report.Evidence
non-injective mangle: g<A_B, C> and g<A, B_C> both -> g$A_B_C; e2eduplicate func identifier).tests/test_codegen_monomorphize.pyon main = 6 failed / 73 passed; on the branch = 79 passed._-doubling dropped → 1 RED (forging-pair battery).Gates
pytest 5907 passed / 43 skipped (browser 111 passed, 0 skipped); mypy clean; ruff (incl.
--select S) clean; conformance 108/108; examples 37/37; all doc gates; site assets rebuilt.Docs
CHANGELOG
[Unreleased]; #775 KNOWN_ISSUES Bugs row removed; counts refreshed. No version bump (rides the v0.1.0 burndown).Closes #775
🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests
Documentation