Skip to content

Fix monomorphization for parameterized opaque types (Map, Set)#345

Merged
aallan merged 3 commits into
mainfrom
fix/monomorphization-parameterized-types
Mar 25, 2026
Merged

Fix monomorphization for parameterized opaque types (Map, Set)#345
aallan merged 3 commits into
mainfrom
fix/monomorphization-parameterized-types

Conversation

@aallan

@aallan aallan commented Mar 25, 2026

Copy link
Copy Markdown
Owner

Summary

  • Root cause: The monomorphizer dropped type arguments when inferring type variable bindings from SlotRefs. @Option<Map<String, Int>>.0 produced T=Map instead of T=Map<String, Int>, causing option_unwrap_or$Map to fail codegen because the substituted body had bare @Map.0 references without type args.
  • Fix: Preserve full parameterized type names through inference (_infer_vera_type_name, _infer_vera_type, _get_arg_type_info), parse them back into NamedType AST during substitution (_parse_type_name), and sanitize angle brackets in mangled WAT function names.
  • Scope: Fixes option_unwrap_or for Map<K,V>, Set<T>, and Decimal. The xfail test is now a passing test.
  • Remove Monomorphization gap for opaque handle types (Decimal, Map, Set) #341 from README Known Bugs table
  • Update CHANGELOG to reflect full fix

Closes #341.

Test plan

  • All 2,921 tests pass (was 2,920 — added test_option_unwrap_or_set)
  • All 21 pre-commit hooks pass
  • test_option_unwrap_or_map now passes (was xfail)
  • CI passes

Summary by CodeRabbit

  • Bug Fixes

    • Fixed monomorphization for option_unwrap_or across all opaque handle types (preserving full parameterised type names to avoid prior codegen failures).
  • Tests

    • Expanded opaque-handle monomorphization tests to 12 and updated codegen tests to expect success; overall test total updated to 2,924.
  • Documentation

    • Removed the known-bug entry about option_unwrap_or; updated changelog, README and testing metrics.

The monomorphizer dropped type arguments when inferring type variable
bindings from SlotRefs. Fix preserves full parameterized type names
through inference, substitution, and WAT name mangling. Closes #341.

Co-Authored-By: Claude <noreply@anthropic.invalid>
@coderabbitai

coderabbitai Bot commented Mar 25, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4b9958bf-bb5b-4b40-b65b-2b32e1c4aedb

📥 Commits

Reviewing files that changed from the base of the PR and between 0fbc127 and 0f97921.

📒 Files selected for processing (2)
  • TESTING.md
  • tests/test_codegen.py

📝 Walkthrough

Walkthrough

Preserves parameterised type names for opaque handle types (Decimal, Map, Set, Ordering) across inference, substitution and mangling so generic functions (e.g., option_unwrap_or) are monomorphised and generate WAT-safe specialised identifiers; tests and docs updated to reflect the fix.

Changes

Cohort / File(s) Summary
Changelog & docs
CHANGELOG.md, README.md, TESTING.md
Clarified v0.0.98 "Fixed" entry to cover all opaque handle types and increased unit-test counts; removed the README known-bug line for option_unwrap_or with Map/Set; updated aggregate testing tallies.
README parsing allowlist
scripts/check_readme_examples.py, tests/test_readme.py
Adjusted the allowlist entry opening fence line number for the "Project Roadmap" Vision example (635→634) so the intended fenced vera block is skipped by the checker.
Tests — codegen
tests/test_codegen.py
Removed pytest.mark.xfail for Map/Set monomorphization, changed assertions to expect success, and added tests covering option_unwrap_or behaviour for Map/Set (including None/default and mixed instantiations).
WASM inference helpers
vera/wasm/inference.py
Added InferenceMixin._format_named_type and updated SlotRef handling to produce fully formatted parameterised type strings (e.g., Map<String, Int>) during inference and argument-type collection.
Monomorphizer core
vera/codegen/monomorphize.py
Added _parse_type_name / _format_type_name; preserve type_args from ast.SlotRef during inference; record full parameterised type-argument strings; extended substitution to parse parameterised replacement strings into NamedType and reindex SlotRef De Bruijn indices; sanitize mangled names for WAT safety.
Call-site name mangling
vera/wasm/calls.py
Sanitises inferred type-variable bindings (replacing <, >, , ) when constructing call-site mangled names so parameterised types produce valid WAT identifiers.

Sequence Diagram

sequenceDiagram
    actor Caller as Call site (e.g. option_unwrap_or<Map<String,Int>>)
    participant Infer as Type Inference\n(vera/wasm/inference.py)
    participant Mono as Monomorphizer\n(vera/codegen/monomorphize.py)
    participant Subst as AST Substitution\n(monomorphize.py)
    participant Mangle as Name Mangling\n(vera/wasm/calls.py)
    participant WAT as WAT Codegen

    Caller->>Infer: Provide SlotRef with type_args
    Infer->>Infer: _format_named_type -> "Map<String, Int>"
    Infer-->>Caller: Binding T="Map<String, Int>"

    Caller->>Mono: Request monomorphisation with binding
    Mono->>Mono: _parse_type_name -> NamedType(Map,[String,Int])
    Mono->>Subst: Prepare mapping T -> NamedType(Map,...)
    Subst->>Subst: Substitute NamedType into AST (handle SlotRef, reindex De Bruijn)
    Subst-->>Mono: Specialised AST

    Mono->>Mangle: Provide concrete type string "Map<String, Int>"
    Mangle->>Mangle: Sanitise -> "Map_String_Int"
    Mangle-->>Mono: Mangled, WAT-safe identifier

    Mono->>WAT: Emit option_unwrap_or$Map_String_Int
    WAT-->>Caller: Compilable, executable specialised function
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

compiler, tests, ci, docs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: fixing monomorphization to handle parameterized opaque types (Map and Set) by preserving type arguments during inference and substitution.
Linked Issues check ✅ Passed All coding requirements from issue #341 are met: type arguments are preserved during inference via _infer_vera_type_name and _infer_vera_type; parameterized names are parsed back to AST via _parse_type_name; mangling is sanitised; and tests confirm option_unwrap_or now monomorphizes correctly for Decimal, Map, and Set.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing monomorphization for opaque handle types: core logic in monomorphize.py, inference updates, test additions, and documentation cleanup removing the now-fixed bug from README.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 fix/monomorphization-parameterized-types

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

@codecov

codecov Bot commented Mar 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.54430% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.98%. Comparing base (b5734ab) to head (0f97921).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
vera/codegen/monomorphize.py 81.35% 11 Missing ⚠️
vera/wasm/inference.py 88.88% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #345      +/-   ##
==========================================
- Coverage   92.01%   91.98%   -0.04%     
==========================================
  Files          47       47              
  Lines       17879    17950      +71     
  Branches      211      211              
==========================================
+ Hits        16452    16511      +59     
- Misses       1423     1435      +12     
  Partials        4        4              
Flag Coverage Δ
javascript 59.71% <ø> (ø)
python 95.37% <83.54%> (-0.06%) ⬇️

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

☔ View full report in Codecov by Sentry.
📢 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)
tests/test_codegen.py (1)

8808-8832: ⚠️ Potential issue | 🟡 Minor

Add None-path coverage for option_unwrap_or<Map<...>> and option_unwrap_or<Set<...>>.

These new tests only exercise the Some(...) path. Please add None-case tests so we also validate default-branch behaviour for parameterised opaque-handle monomorphisation.

Suggested test additions
 class TestDecimalMonomorphization:
@@
     def test_option_unwrap_or_set(self) -> None:
         """option_unwrap_or<Set<Int>> monomorphization."""
         source = """
 public fn main(-> `@Int`)
   requires(true) ensures(true) effects(pure)
 {
   let `@Option`<Set<Int>> = Some(set_add(set_new(), 42));
   let `@Set`<Int> = option_unwrap_or(`@Option`<Set<Int>>.0, set_new());
   set_size(`@Set`<Int>.0)
 }
 """
         assert _run(source) == 1
+
+    def test_option_unwrap_or_map_none(self) -> None:
+        """option_unwrap_or<Map<String, Int>> with None returns default map."""
+        source = """
+public fn main(-> `@Int`)
+  requires(true) ensures(true) effects(pure)
+{
+  let `@Option`<Map<String, Int>> = None;
+  let `@Map`<String, Int> = option_unwrap_or(
+    `@Option`<Map<String, Int>>.0,
+    map_insert(map_new(), "d", 1)
+  );
+  map_size(`@Map`<String, Int>.0)
+}
+"""
+        assert _run(source) == 1
+
+    def test_option_unwrap_or_set_none(self) -> None:
+        """option_unwrap_or<Set<Int>> with None returns default set."""
+        source = """
+public fn main(-> `@Int`)
+  requires(true) ensures(true) effects(pure)
+{
+  let `@Option`<Set<Int>> = None;
+  let `@Set`<Int> = option_unwrap_or(`@Option`<Set<Int>>.0, set_add(set_new(), 7));
+  set_size(`@Set`<Int>.0)
+}
+"""
+        assert _run(source) == 1

As per coding guidelines, "Add codegen/runtime tests in tests/test_codegen.py for built-in functions covering normal cases, edge cases (empty inputs, zero values), and composition with other built-ins".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_codegen.py` around lines 8808 - 8832, Add tests covering the
None-path for parameterized opaque-handle monomorphisation by creating two new
test cases mirroring test_option_unwrap_or_map and test_option_unwrap_or_set but
passing None instead of Some; call option_unwrap_or(`@Option`<Map<String, Int>>.0,
map_new()) and option_unwrap_or(`@Option`<Set<Int>>.0, set_new()) where the option
value is None, then assert map_size(...) == 0 and set_size(...) == 0
respectively to validate the default branch returns an empty map/set; place them
alongside the existing tests and name them e.g. test_option_unwrap_or_map_none
and test_option_unwrap_or_set_none.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@vera/codegen/monomorphize.py`:
- Around line 464-479: The _format_type_name implementation in
MonomorphizationMixin duplicates the logic in InferenceMixin._format_named_type;
add a brief cross-reference comment above
MonomorphizationMixin._format_type_name mentioning
InferenceMixin._format_named_type (e.g., "Duplicate logic; keep in sync with
InferenceMixin._format_named_type") so future maintainers know the intentional
duplication, and leave the implementation itself unchanged.

---

Outside diff comments:
In `@tests/test_codegen.py`:
- Around line 8808-8832: Add tests covering the None-path for parameterized
opaque-handle monomorphisation by creating two new test cases mirroring
test_option_unwrap_or_map and test_option_unwrap_or_set but passing None instead
of Some; call option_unwrap_or(`@Option`<Map<String, Int>>.0, map_new()) and
option_unwrap_or(`@Option`<Set<Int>>.0, set_new()) where the option value is None,
then assert map_size(...) == 0 and set_size(...) == 0 respectively to validate
the default branch returns an empty map/set; place them alongside the existing
tests and name them e.g. test_option_unwrap_or_map_none and
test_option_unwrap_or_set_none.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8f3d5f94-3b46-46c3-a557-1417802ae086

📥 Commits

Reviewing files that changed from the base of the PR and between b5734ab and 03dd304.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • README.md
  • TESTING.md
  • scripts/check_readme_examples.py
  • tests/test_codegen.py
  • tests/test_readme.py
  • vera/codegen/monomorphize.py
  • vera/wasm/calls.py
  • vera/wasm/inference.py
💤 Files with no reviewable changes (1)
  • README.md

Comment thread vera/codegen/monomorphize.py
Two new tests exercise the None branch of option_unwrap_or for
Map<String, Int> and Set<Int>. Add cross-reference comments between
duplicated _format_type_name / _format_named_type helpers.

Co-Authored-By: Claude <noreply@anthropic.invalid>

@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 the current code and only fix it if needed.

Inline comments:
In `@tests/test_codegen.py`:
- Around line 8849-8876: Add a new test (e.g.
test_option_unwrap_or_mixed_instantiations) that exercises two distinct
parameterisations in the same module — for example call option_unwrap_or on both
`@Option`<Map<String, Int>> and `@Option`<Map<Int, Int>> (or Map<String, Int> and
Map<String, Bool>) within one source string, provide different defaults (like
map_insert(map_new(), "d", 1) and map_insert(map_new(), 42, 2)), and assert both
returned maps resolve correctly (e.g. by calling map_size on each and asserting
expected sizes). Place it alongside the existing test_option_unwrap_or_map_none
and test_option_unwrap_or_set_none tests to guard against
monomorphisation/mangling collisions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c7cce4cb-10d9-4199-b3a3-18e948af6ea1

📥 Commits

Reviewing files that changed from the base of the PR and between 03dd304 and 0fbc127.

📒 Files selected for processing (4)
  • TESTING.md
  • tests/test_codegen.py
  • vera/codegen/monomorphize.py
  • vera/wasm/inference.py

Comment thread tests/test_codegen.py
Guards against mangling collisions when Map<String, Int> and
Map<Int, Int> coexist in the same module.

Co-Authored-By: Claude <noreply@anthropic.invalid>
@aallan aallan merged commit a774288 into main Mar 25, 2026
15 of 17 checks passed
@aallan aallan deleted the fix/monomorphization-parameterized-types branch March 25, 2026 12:42
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.

Monomorphization gap for opaque handle types (Decimal, Map, Set)

1 participant