Fix monomorphization for parameterized opaque types (Map, Set)#345
Conversation
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>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughPreserves 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Codecov Report❌ Patch coverage is
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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 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)
tests/test_codegen.py (1)
8808-8832:⚠️ Potential issue | 🟡 MinorAdd None-path coverage for
option_unwrap_or<Map<...>>andoption_unwrap_or<Set<...>>.These new tests only exercise the
Some(...)path. Please addNone-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) == 1As 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
📒 Files selected for processing (9)
CHANGELOG.mdREADME.mdTESTING.mdscripts/check_readme_examples.pytests/test_codegen.pytests/test_readme.pyvera/codegen/monomorphize.pyvera/wasm/calls.pyvera/wasm/inference.py
💤 Files with no reviewable changes (1)
- README.md
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>
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
TESTING.mdtests/test_codegen.pyvera/codegen/monomorphize.pyvera/wasm/inference.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>
Summary
@Option<Map<String, Int>>.0producedT=Mapinstead ofT=Map<String, Int>, causingoption_unwrap_or$Mapto fail codegen because the substituted body had bare@Map.0references without type args._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.option_unwrap_orforMap<K,V>,Set<T>, andDecimal. The xfail test is now a passing test.Closes #341.
Test plan
test_option_unwrap_or_set)test_option_unwrap_or_mapnow passes (was xfail)Summary by CodeRabbit
Bug Fixes
option_unwrap_oracross all opaque handle types (preserving full parameterised type names to avoid prior codegen failures).Tests
Documentation
option_unwrap_or; updated changelog, README and testing metrics.