Skip to content

Add Map<K, V> collection type (#62, PR 1/3)#332

Merged
aallan merged 9 commits into
mainfrom
feat/map-collection
Mar 23, 2026
Merged

Add Map<K, V> collection type (#62, PR 1/3)#332
aallan merged 9 commits into
mainfrom
feat/map-collection

Conversation

@aallan

@aallan aallan commented Mar 23, 2026

Copy link
Copy Markdown
Owner

Summary

Implement Map<K, V> as an opaque host-import type with Eq<K> + Hash<K> ability constraints. This is PR 1 of 3 for #62 (Map portion; Set and Decimal deferred).

  • 8 built-in operations: map_new, map_insert, map_get, map_contains, map_remove, map_size, map_keys, map_values
  • Host-import architecture: Python runtime (wasmtime) maintains dict objects behind i32 handles; JS runtime (browser) uses Map objects. Type-specific imports (map_insert$ks_vi) handle serialization across the WASM boundary
  • Functional semantics: map_insert and map_remove return new maps (copy-on-write)
  • Type inference fix: Bidirectional coercion for zero-arg generic calls (e.g., map_new()) when expected type is available; skip ADT args whose TypeVars match callee forall vars during unification

Compiler changes

Area Files
Type system checker/resolution.py, checker/expressions.py, environment.py
Codegen pipeline codegen/core.py, codegen/assembly.py, codegen/compilability.py, codegen/functions.py, codegen/modules.py
WASM translation wasm/calls.py, wasm/context.py, wasm/inference.py
Host runtime codegen/api.py (Python/wasmtime), browser/runtime.mjs (JS)
Verifier smt.py (uninterpreted map_size)

Documentation

  • Spec 9.4.3 updated (no longer Future)
  • SKILL.md: Map operations section added
  • README.md: Collections feature updated
  • Doc counts: 57 conformance programs (was 56), 2754 tests (was 2749)

Test plan

  • All 21 pre-commit hooks pass
  • mypy clean (46 files)
  • 2,754 tests pass (5 new from conformance test)
  • 57 conformance programs pass (new ch09_map.vera with 10 test functions)
  • All validation scripts pass
  • CI passes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a Map<K, V> collection with pure functional operations (create, insert, get→Option, contains, remove, size, keys, values) with runtime and compilation support.
  • Documentation

    • Published full Map<K, V> specification, examples and technical notes; updated README, CONTRIBUTING, TESTING and skill/spec docs to reference maps and adjust test metrics.
  • Tests

    • Added unit and conformance tests for Map; conformance suite now lists 57 programs and overall unit test count updated to 2,780.

Implement Map<K, V> as an opaque host-import type with Eq<K> + Hash<K>
ability constraints. Keys must be hashable primitives; values may be any
type. All operations are pure (functional copy-on-write semantics).

Built-in operations: map_new, map_insert, map_get, map_contains,
map_remove, map_size, map_keys, map_values. Host runtime maintains
Python dicts (wasmtime) / JS Maps (browser) behind i32 handles.

Type inference fix: skip ADT args whose TypeVars match the callee own
forall vars during unification (prevents map_new returning Map<K, V>
from poisoning inference). Bidirectional coercion for FnCall with
unresolved TypeVars when expected type is available.

Compiler changes: checker/resolution.py (built-in type + inference fix),
environment.py (function signatures), codegen pipeline (compilability,
core, assembly, functions, modules), wasm/ (calls, context, inference),
smt.py (uninterpreted map_size), browser/runtime.mjs (JS host bindings).

Tests: 57 conformance programs (was 56), 2754 total tests (was 2749).
New ch09_map.vera exercises all 8 operations across 10 test functions.

Spec 9.4.3 updated (no longer Future), SKILL.md documents Map operations,
README.md features table updated.

Closes #62 (Map portion; Set and Decimal deferred to follow-up PRs).

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

coderabbitai Bot commented Mar 23, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds first-class, pure functional Map<K, V> support across spec, docs, checker, environment, codegen, WASM translation, SMT, runtime, tests, scripts, and a new conformance test; introduces builtins map_new, map_insert, map_get, map_contains, map_remove, map_size, map_keys, map_values.

Changes

Cohort / File(s) Summary
Documentation & CI
CLAUDE.md, CONTRIBUTING.md, README.md, TESTING.md
Incremented conformance count (56 → 57), updated unit-test totals and README Collections row to mention Map<K,V>.
Specification & Guides
spec/09-standard-library.md, SKILL.md
Replaced Map placeholder with concrete Map<K,V> spec, declared builtin signatures and semantics, added SKILL usage examples and guidance.
Tests, Manifests & Scripts
tests/conformance/manifest.json, tests/test_checker.py, tests/test_codegen.py, scripts/check_skill_examples.py, scripts/check_spec_examples.py
Added ch09_map conformance entry; new Map-focused unit tests; updated example allowlists and shifted line-number mappings.
Type Environment & Builtins
vera/environment.py
Registered eight Map builtins with forall<K,V> signatures and Eq<K>, Hash<K> constraints (pure semantics).
Type Resolution & Checker
vera/checker/resolution.py, vera/checker/expressions.py, vera/wasm/inference.py
Treat Map/Set as ADTs with arity; added typevar-aware coercion in call synthesis; map builtins mapped to Vera-level return types.
SMT / Verification
vera/smt.py
Translated map_size to an uninterpreted Z3 function with size ≥ 0; map_contains left opaque (falls back to Tier 3).
Codegen State & Compilation
vera/codegen/core.py, vera/codegen/api.py, vera/codegen/assembly.py, vera/codegen/compilability.py, vera/codegen/functions.py, vera/codegen/modules.py
Tracked _map_ops_used/_map_imports through compilation; treat Map/Set as opaque i32 handles; emit per-instantiation imports and enable $alloc export when maps are used.
WASM Translation & Inference
vera/wasm/calls.py, vera/wasm/context.py, vera/wasm/inference.py
Added monomorphisation helpers, import-name construction/registration, call-site type inference and translators that emit typed host imports and handle Option/array returns.
Runtime Host Bindings
vera/browser/runtime.mjs, vera/codegen/api.py
Implemented host-side mapStore keyed by opaque i32 handles; regex-matched typed import bindings; marshalled Option and Array results into WASM memory/handles.
Other scripts & housekeeping
scripts/check_skill_examples.py, scripts/check_spec_examples.py
Adjusted allowlist entries and coordinates to account for new/shifted spec and example content.

Sequence Diagram

sequenceDiagram
    autonumber
    participant Dev as Developer
    participant Checker as Type Checker
    participant Codegen as Code Generator
    participant WASM as WASM Emitter
    participant Runtime as Browser Runtime

    Dev->>Checker: Write Vera using map_* builtins
    Checker->>Checker: Resolve Map<K,V>, enforce Eq/Hash constraints
    Checker-->>Codegen: Type-checked AST (map ops present)
    Codegen->>Codegen: Record _map_ops_used and _map_imports
    Codegen->>WASM: Request monomorphized import names
    WASM->>WASM: Register per-type imports, emit calls returning i32/ptrs
    WASM-->>Runtime: Module imports like "vera.map_insert$k<int>_v<string>"
    Runtime->>Runtime: buildImportObject matches regex, implements mapStore[handle]
    Runtime-->>Dev: Returns i32 handle / Option pointer / arrays
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

compiler, tests, spec, ci, docs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title directly and specifically describes the primary change: adding Map<K, V> collection type, with issue and PR series context.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/map-collection

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

@codecov

codecov Bot commented Mar 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.83368% with 97 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.40%. Comparing base (8ebd845) to head (0cbbf41).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
vera/wasm/calls.py 78.37% 48 Missing ⚠️
vera/codegen/api.py 82.08% 36 Missing ⚠️
vera/smt.py 22.22% 7 Missing ⚠️
vera/wasm/inference.py 58.33% 5 Missing ⚠️
vera/codegen/core.py 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #332      +/-   ##
==========================================
- Coverage   95.88%   95.40%   -0.48%     
==========================================
  Files          45       45              
  Lines       15150    15626     +476     
==========================================
+ Hits        14526    14908     +382     
- Misses        624      718      +94     

☔ 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.

Eight spec code blocks in chapter 9 were no longer allowlisted after
the Map section insertion shifted their line numbers. Add the correct
entries.

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: 6

Caution

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

⚠️ Outside diff range comments (1)
vera/checker/resolution.py (1)

163-173: 🧹 Nitpick | 🔵 Trivial

Correct logic for forall_vars propagation.

The conversion to set(forall_vars) enables O(1) membership checks in the unification loop.

Static analysis flags zip() without strict= (Ruff B905). If param_types and arg_types are guaranteed equal length by the caller, this is fine. Otherwise, consider strict=True or length assertions for defensive checking in compiler code.

🔧 Optional: Add strict=True for defensive checking
-        for param_ty, arg_ty in zip(param_types, arg_types):
+        for param_ty, arg_ty in zip(param_types, arg_types, strict=True):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vera/checker/resolution.py` around lines 163 - 173, The conversion of
forall_vars to a set (forall_set = set(forall_vars)) is fine for O(1) membership
checks, but the iteration uses zip(param_types, arg_types) without strict
checking; update _infer_type_args to guard against mismatched lengths by either
using zip(..., strict=True) or asserting len(param_types) == len(arg_types)
before the loop, and keep the existing behavior of skipping None/UnknownType
arg_types and calling _unify_for_inference(param_ty, arg_ty, mapping,
forall_set).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/check_skill_examples.py`:
- Around line 114-120: The ALLOWLIST dict has duplicate key 1326 so the second
entry ("Wrong: if/else without braces (bare expression)") silently overwrites
the first ("Wrong: non-exhaustive match (missing arm)"); locate the ALLOWLIST in
scripts/check_skill_examples.py and correct one of the numeric keys (either
change the second 1326 to the actual fence line number in SKILL.md for the
if/else fragment or change the first 1326 to the correct fence line for the
non-exhaustive match) so each entry (e.g., the entries labeled "Wrong:
non-exhaustive match (missing arm)" and "Wrong: if/else without braces (bare
expression)") has a unique key matching its fence line in SKILL.md.

In `@spec/09-standard-library.md`:
- Around line 256-259: The doc overstates Map support: update the Map section so
it matches the current ABI instead of claiming "Values may be any type" —
specify that keys require Eq and Hash and are limited to the presently
marshalled primitives (i64/Int/Nat mapping to i64, f64/Float64, String) and that
map entries in the ABI are marshalled as single i32 handles for opaque/value
shapes; change the sentence about values to state that values must be types
supported by the host marshalling (primitive f64/i64/String or single-i32
handle) and keep the note that Map is opaque and that map_insert and map_remove
are pure functional operations (retain those symbols Map<K,V>, Eq, Hash,
map_insert, map_remove to locate the text).
- Around line 260-275: Add a small, valid vera example demonstrating Map usage
so CI can parse it: include a vera block that defines a function (e.g.,
map_demo) that calls map_new(), uses map_insert(...) to add a key ("answer", 42)
and then uses map_get(...) to retrieve it, returning the Some branch value (or 0
on None); reference the builtins map_new, map_insert, and map_get in that
example and ensure the function signature and match arms use the correct
`@Map`<String, Int>, `@Int` and Some/None patterns so the snippet is syntactically
valid for the parser.

In `@vera/codegen/api.py`:
- Around line 1064-1069: The helper _define_map_values is registering imports
with the wrong name suffix ('map_values$k{vt}') while the dispatched ops and
browser runtime expect 'map_values$v{vt}'; update the registration name in
_define_map_values to use 'v' (i.e., f"map_values$v{vt}") so Wasmtime defines
the symbol the module imports, and apply the same fix to the other identical
helper block (the second occurrence around the other define helper) to ensure
both registrations use the 'v' spelling.

In `@vera/wasm/calls.py`:
- Around line 7263-7288: _map_wasm_tag currently only treats "String" as a
two-word ABI and falls through Arrays to "b", causing mismatched import
signatures for map_insert/map_get/map_values when translate_expr pushes
(ptr,len). Modify _map_wasm_tag to detect Array types (e.g. string forms like
"Array<...>" or whatever Array syntax your type strings use) and return the "s"
tag for them (so _map_wasm_types will emit ["i32","i32"]) ensuring the WASM
import signature matches translate_expr's (ptr,len) ABI used by
map_insert/map_get/map_values.

---

Outside diff comments:
In `@vera/checker/resolution.py`:
- Around line 163-173: The conversion of forall_vars to a set (forall_set =
set(forall_vars)) is fine for O(1) membership checks, but the iteration uses
zip(param_types, arg_types) without strict checking; update _infer_type_args to
guard against mismatched lengths by either using zip(..., strict=True) or
asserting len(param_types) == len(arg_types) before the loop, and keep the
existing behavior of skipping None/UnknownType arg_types and calling
_unify_for_inference(param_ty, arg_ty, mapping, forall_set).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6923cfc4-26c1-466a-b12e-5d2a7f7337eb

📥 Commits

Reviewing files that changed from the base of the PR and between 8ebd845 and 250b764.

⛔ Files ignored due to path filters (3)
  • docs/llms-full.txt is excluded by !docs/**
  • docs/sitemap.xml is excluded by !docs/**
  • tests/conformance/ch09_map.vera is excluded by !**/*.vera
📒 Files selected for processing (23)
  • CLAUDE.md
  • CONTRIBUTING.md
  • README.md
  • SKILL.md
  • TESTING.md
  • scripts/check_skill_examples.py
  • scripts/check_spec_examples.py
  • spec/09-standard-library.md
  • tests/conformance/manifest.json
  • vera/browser/runtime.mjs
  • vera/checker/expressions.py
  • vera/checker/resolution.py
  • vera/codegen/api.py
  • vera/codegen/assembly.py
  • vera/codegen/compilability.py
  • vera/codegen/core.py
  • vera/codegen/functions.py
  • vera/codegen/modules.py
  • vera/environment.py
  • vera/smt.py
  • vera/wasm/calls.py
  • vera/wasm/context.py
  • vera/wasm/inference.py
👮 Files not reviewed due to content moderation or server errors (8)
  • CLAUDE.md
  • TESTING.md
  • vera/wasm/inference.py
  • SKILL.md
  • vera/smt.py
  • vera/codegen/assembly.py
  • vera/codegen/core.py
  • scripts/check_spec_examples.py

Comment thread scripts/check_skill_examples.py
Comment thread spec/09-standard-library.md Outdated
Comment thread spec/09-standard-library.md
Comment thread vera/codegen/api.py
Comment thread vera/wasm/calls.py
Comment thread vera/wasm/calls.py
@aallan

aallan commented Mar 23, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Addressed all 6 review comments in the next push:

  1. Duplicate allowlist key 1326 — Fixed, removed duplicate and added correct entry at 1343
  2. Spec overstates Map value types — Narrowed to types with a WASM representation; Array pair-ABI now handled
  3. Missing spec example — Added parsable map_demo function to spec section 9.4.3
  4. map_values wrong import name — Fixed to use $v{vt} consistently across codegen, api.py, and browser runtime
  5. Pair-ABI Array values — _map_wasm_tag now recognises Array types as pair-ABI (s tag)
  6. SlotRef type inference — Now uses ast.SlotRef.type_args (typed AST) with string parsing as fallback

- 22 new unit tests (9 checker, 13 codegen) exercise all Map operations
- Fix map_values naming: use $v{vt} convention (was $k{vt})
- Fix _map_wasm_tag: handle Array<T> as pair-ABI type
- Fix _infer_map_*_from_map_arg: use SlotRef.type_args (typed AST)
- Fix _slot_name_to_wasm_type: handle Map/Set slot names
- Fix assembly.py: set _needs_memory when map ops used
- Narrow spec 9.4.3: values must have WASM representation
- Add parsable vera example to spec Map section
- Fix duplicate allowlist keys in check_spec_examples.py
- Fix duplicate allowlist key 1326 in check_skill_examples.py
- Split Decimal to separate issue #333
- Doc counts: 2776 tests (was 2754)

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

coderabbitai Bot commented Mar 23, 2026

Copy link
Copy Markdown

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== Fix 1: Duplicate allowlist key in check_skill_examples.py (around 1326/1343) ==="
rg -n "1326|1343" scripts/check_skill_examples.py | head -30

echo ""
echo "=== Fix 2 & 3: Spec section 9.4.3 value types and map_demo example ==="
rg -n "map_demo\|value.*type\|String\|Int\|Bool\|Array\|Float" spec/09-standard-library.md | head -40

echo ""
echo "=== Fix 4: map_values import name in wasm/calls.py ==="
rg -n "map_values\|v{vt}\|vt" vera/wasm/calls.py | head -30

echo ""
echo "=== Fix 4: map_values import name in codegen/api.py ==="
rg -n "map_values\|v{vt}\|vt" vera/codegen/api.py | head -30

echo ""
echo "=== Fix 4: map_values import name in browser/runtime.mjs ==="
rg -n "map_values\|v{vt}\|vt" vera/browser/runtime.mjs | head -30

echo ""
echo "=== Fix 5: _map_wasm_tag Array/pair-ABI recognition ==="
rg -n "_map_wasm_tag\|pair.*abi\|Array\|s tag" vera/wasm/calls.py | head -30

echo ""
echo "=== Fix 6: SlotRef type_args inference ==="
rg -n "SlotRef\|type_args\|slotref" vera/wasm/calls.py | head -30
rg -n "SlotRef\|type_args\|slotref" vera/wasm/inference.py | head -20

Length of output: 1005


🏁 Script executed:

#!/bin/bash
echo "=== Fix 1: Full allowlist around lines 116-125 in check_skill_examples.py ==="
sed -n '110,130p' scripts/check_skill_examples.py

echo ""
echo "=== Fix 2 & 3: Spec 9.4.3 — map_demo and value type constraints ==="
grep -n "map_demo\|value\|String\|Int\|Bool\|Array\|Float\|key type\|value type\|WASM" spec/09-standard-library.md | head -50

echo ""
echo "=== Fix 4: map_values import name consistency ==="
grep -n "map_values" vera/wasm/calls.py | head -20
grep -n "map_values" vera/codegen/api.py | head -20
grep -n "map_values" vera/browser/runtime.mjs | head -20

echo ""
echo "=== Fix 5: _map_wasm_tag definition ==="
grep -n "_map_wasm_tag\|pair.abi\|\"s\"\|= \"s\"\|Array" vera/wasm/calls.py | head -30

echo ""
echo "=== Fix 6: SlotRef and type_args usage ==="
grep -n "SlotRef\|type_args" vera/wasm/calls.py | head -20
grep -n "SlotRef\|type_args" vera/wasm/inference.py | head -20

Length of output: 10167


🏁 Script executed:

#!/bin/bash
echo "=== Fix 1: Still-duplicate key 1326 context ==="
grep -n "1326" scripts/check_skill_examples.py

echo ""
echo "=== Fix 3: map_demo in spec ==="
grep -n "map_demo\|9\.4\.3\|Map.*K.*V\|map_new\|map_insert\|map_get" spec/09-standard-library.md | head -40

echo ""
echo "=== Fix 4: api.py map_values naming ==="
sed -n '1060,1125p' vera/codegen/api.py

echo ""
echo "=== Fix 4: wasm/calls.py _map_import_name and map_values registration ==="
sed -n '7264,7310p' vera/wasm/calls.py
echo "---"
sed -n '7515,7545p' vera/wasm/calls.py

echo ""
echo "=== Fix 4: browser/runtime.mjs map_values pattern ==="
sed -n '1210,1235p' vera/browser/runtime.mjs

Length of output: 8619


🏁 Script executed:

#!/bin/bash
echo "=== Fix 3: Full spec section 9.4.3 ==="
sed -n '250,310p' spec/09-standard-library.md

echo ""
echo "=== Fix 5: Full _map_wasm_tag function ==="
sed -n '7260,7295p' vera/wasm/calls.py

echo ""
echo "=== Fix 5: _infer_map_value_from_map_arg to check what it returns for Array ==="
sed -n '7540,7620p' vera/wasm/calls.py

echo ""
echo "=== Fix 4: Confirm wasm/calls.py _map_import_name for map_values ==="
sed -n '7290,7370p' vera/wasm/calls.py

Length of output: 10719


@aallan: Thanks for the detailed summary. I've verified the updated code and can confirm three fixes are fully applied, but found three residual issues still present in the push:


✅ Verified clean

  • Fix 2 — Spec now correctly restricts keys to hashable primitives (Int, Nat, Bool, Float64, String, Byte, Unit); "Values may be any type" is appropriate at the specification level.
  • Fix 6ast.SlotRef.type_args is confirmed in use at multiple sites in vera/wasm/calls.py (lines 1006–1008, 7418, 7538) and vera/wasm/inference.py.

❌ Still outstanding

Fix 1 — Duplicate key 1326 in scripts/check_skill_examples.py

The duplicate is still there. Lines 116 and 119 both carry 1326 as a dict key; Python silently drops the first:

# line 116 — will be LOST at runtime
1326: ("FRAGMENT", "Wrong: non-exhaustive match (missing arm)"),
# ...
# line 119 — wins, silently shadowing line 116
1326: ("FRAGMENT", "Wrong: if/else without braces (bare expression)"),

The entry at line 116 needs a different key (e.g. 1320 or whatever its actual fence start line is).


Fix 4 — map_values import name mismatch: browser runtime vs. WAT codegen

wasm/calls.py calls _map_import_name("map_values", vt, None). Because val_tag is None, _map_import_name uses the key_tag slot, producing map_values$k{vt} (e.g. map_values$ki). codegen/api.py is consistent with this (f"map_values$k{vt}").

However, browser/runtime.mjs still matches:

m = name.match(/^map_values\$v(.)$/);

…which expects map_values$vi, not map_values$ki. These names will never match, so map_values calls are silently ignored in the browser runtime.

The fix is either to update runtime.mjs to match $k(.), or update wasm/calls.py to pass vt as the val_tag argument (and None as key_tag) so the suffix becomes $v{vt}. The latter is arguably the more semantically correct choice since it is the value type that is being tagged.


Fix 5 — _map_wasm_tag still missing the Array branch

The updated function in vera/wasm/calls.py (lines 7264–7280):

def _map_wasm_tag(vera_type: str | None) -> str:
    if vera_type in ("Int", "Nat"):
        return "i"
    if vera_type == "Float64":
        return "f"
    if vera_type in ("String",):
        return "s"   # i32_pair
    # Bool, Byte, ADTs, Map handles → i32
    return "b"        # ← Array falls here — wrong!

"Array" (returned by _infer_vera_type for array-valued maps) falls through to "b" (i32), but an Array<…> is a pair-ABI type ((i32, i32)) — the same representation as String. It should return "s":

    if vera_type in ("String",) or (
        vera_type is not None and vera_type.startswith("Array")
    ):
        return "s"   # i32_pair

Without this, map_values on a Map<String, Array<Int>> (for example) will produce a broken import signature.


Fix 3 — map_demo code block not found in spec/09-standard-library.md §9.4.3

Lines 254–279 of the spec contain only the prose and operations table for Map<K, V>; no parseable code example with map_demo is present. If the intent was to satisfy check_spec_examples.py, the fenced code block still needs to be inserted into the section.

@aallan

aallan commented Mar 23, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai All four items were fixed in commit 09971e8 (the third commit on this PR). Please re-review — the fixes are in the latest push:

  1. Duplicate 1326: Line 119 now reads # 1326 duplicate removed, entry moved to 1343 (line 49)
  2. map_values naming: _translate_map_values now passes val_tag=vt (not key_tag), producing $v{vt}. Confirmed consistent across calls.py, api.py, and runtime.mjs
  3. Array pair-ABI: _map_wasm_tag now checks vera_type.startswith("Array") and returns "s"
  4. map_demo example: Present in spec lines 277-284 (after the operations table)

@coderabbitai

coderabbitai Bot commented Mar 23, 2026

Copy link
Copy Markdown

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== Fix 1: Duplicate key 1326 — check lines 45-55 and 115-125 ==="
sed -n '44,55p' scripts/check_skill_examples.py
echo "---"
sed -n '115,125p' scripts/check_skill_examples.py

echo ""
echo "=== Fix 2: map_values naming in wasm/calls.py — _translate_map_values ==="
grep -n "_translate_map_values\|val_tag\|key_tag\|map_values" vera/wasm/calls.py | head -30

echo ""
echo "=== Fix 2: map_values naming in api.py ==="
grep -n "map_values" vera/codegen/api.py | head -15

echo ""
echo "=== Fix 2: map_values naming in browser/runtime.mjs ==="
grep -n "map_values" vera/browser/runtime.mjs | head -15

echo ""
echo "=== Fix 3: _map_wasm_tag definition ==="
grep -n "_map_wasm_tag\|startswith\|Array\|\"s\"\|\"b\"\|\"i\"\|\"f\"" vera/wasm/calls.py | head -30

echo ""
echo "=== Fix 3: Full _map_wasm_tag function body ==="
sed -n '7260,7295p' vera/wasm/calls.py

echo ""
echo "=== Fix 4: map_demo in spec lines 270-290 ==="
sed -n '270,290p' spec/09-standard-library.md

Length of output: 6022


@aallan: All four fixes verified against the latest push. Everything is clean.

Fix Result Detail
1 — Duplicate key 1326 The original 1326 entry ("Wrong: non-exhaustive match (missing arm)") is the sole survivor; the if/else entry is now at 1343 (line 49). The comment on line 119 makes the intent explicit.
2 — map_values naming wasm/calls.py line 7536 now calls _map_import_name("map_values", val_tag=vt), producing map_values$v{vt}. api.py uses f"map_values$v{vt}". runtime.mjs matches /^map_values\$v(.)$/. All three are consistent.
3 — Array pair-ABI in _map_wasm_tag The function now checks vera_type.startswith("Array") inside the "s" (i32_pair) branch, alongside "String". Arrays will correctly produce two-word (i32, i32) import signatures.
4 — map_demo spec example Lines 277–284 of spec/09-standard-library.md contain a complete, parseable map_demo function with requires/ensures/effects annotations and a single-expression body exercising map_new, map_insert, map_get, and option_unwrap_or.

All six original review comments and all four follow-up issues are now resolved. The implementation looks correct and consistent across the codegen pipeline, host runtimes, and specification.

Remove 5 duplicate dict keys that caused silent shadowing. When
fix_allowlists shifted entries to new line numbers, some landed on
the same line as entries in a different section of the allowlist.

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: 7

♻️ Duplicate comments (1)
vera/wasm/calls.py (1)

7397-7402: ⚠️ Potential issue | 🔴 Critical

Do not fall back to b when Map<K, V> recovery fails.

Listing map_new in Lines 7441-7446 does not actually recover its type — zero-arg map_new() still returns None here, and other non-SlotRef map expressions do the same. map_get / map_keys / map_values then mangle that as ...b..., which gives well-typed code the wrong host ABI instead of failing translation. Recover K / V from the typed expression more generally, or return None from these translators when recovery fails.

Also applies to: 7420-7447, 7513-7537, 7547-7568

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/check_spec_examples.py`:
- Around line 180-186: The spec index contains duplicate entries and incorrect
line numbers for several string signatures; remove redundant duplicates so each
signature appears only once and correct their line numbers to match the actual
spec: update the entries for string_upper and string_lower to distinct, correct
line numbers (they were mistakenly duplicated), ensure string_replace,
string_split, string_join, string_from_char_code, and string_repeat each have a
single unique, correct line number (remove the duplicate set from the "main
list" or "String transformation" section), and also delete the duplicate
infinity entry mentioned (keep only the correct one); locate and edit the
mapping that contains keys like "string_upper", "string_lower",
"string_replace", "string_split", "string_join", "string_from_char_code",
"string_repeat" to perform these removals/line-number corrections.

In `@tests/test_checker.py`:
- Around line 1526-1601: The test class TestMapCollection is missing negative
and inference tests: add one test (e.g., test_map_wrong_types) that calls
map_insert with a key/value type mismatch and asserts a type error via
_check_err (look for messages like "mismatched" or "expects"), and add another
test (e.g., test_map_infer_map_new) that uses a bare map_new() in a context
expecting `@Map`<K,V> (use let `@Map`<String, Nat> = map_new() or an expression like
map_size(map_new()) with an expected Map type) and assert it type-checks via
_check_ok; place these new tests inside the TestMapCollection class alongside
the existing test_map_wrong_arity, referencing the same helper functions
(_check_ok/_check_err) so the checker exercises wrong-type path and zero-arg
map_new() inference.

In `@tests/test_codegen.py`:
- Around line 7616-7747: Add three tests to TestMapCollection to cover empty-map
edge cases and typed-context inference: (1) add test_map_keys_empty that asserts
array_length(map_keys(map_new())) == 0 to verify map_keys on an empty map
returns an empty array; (2) add test_map_values_empty that asserts
array_length(map_values(map_new())) == 0 to verify map_values on an empty map
returns an empty array; and (3) add test_map_new_inference that forces
type-inference for a bare map_new (e.g. by binding let `@Map`<String, Nat> =
map_new() or using map_get/map_insert on map_new()) and asserts expected
behavior (size 0 or map_keys length 0) to cover the typed-context path; place
these new tests alongside the existing TestMapCollection methods (see map_keys,
map_values, map_new references) so they run with the other map built-in tests.

In `@vera/codegen/api.py`:
- Around line 869-874: The mapping and codegen treat the "s" suffix as a generic
two-i32 pair but the vt == "s" branches always assume String by calling
_read_wasm_string, _alloc_option_some_string, and _alloc_array_of_strings; to
fix, either propagate the concrete pair-ABI type through the import/type name
(e.g., extend _VAL_WASM_TYPES and the import/type encoding to include the inner
concrete type for "s" so codegen uses the appropriate pair-ABI readers/writers),
or explicitly reject non-String usages of "s" during codegen (raise an error
when encountering vt == "s" for types other than String in functions that
generate insert/get/values code such as the code paths touched around
_VAL_WASM_TYPES, the vt=="s" branches, and callers of
_read_wasm_string/_alloc_option_some_string/_alloc_array_of_strings); update the
affected sections (including the similar blocks around lines referenced) to use
one consistent approach.
- Around line 754-761: The _map_store/_map_next_handle allocation never reclaims
handles causing unbounded growth; add a _map_free function and a reusable
free-handle pool so _map_alloc reuses freed handles instead of always
incrementing _map_next_handle, and ensure map_remove (and map_new/map_insert
where items are removed or maps become empty) calls _map_free when a map is no
longer reachable (e.g., when its dict is emptied or when host releases a handle
during execute()). Update all places that drop a handle to call _map_free and/or
push the handle onto the free list so entries in _map_store are deleted (or set
None) and memory is reclaimed; reference symbols: _map_store, _map_next_handle,
_map_alloc, _map_free (new), map_new, map_insert, map_remove, and execute().
- Around line 826-836: The map currently stores ADT pointers as raw i32 payloads
(e.g., via _alloc_array_of_i32 and callers that call _map_store), which lets the
mark-sweep collector free those objects and produce dangling pointers on
subsequent map_get/map_values; fix by either (A) ensuring any heap-backed ADT
pointer placed into an i32 bucket is owned/pinned by making a deep copy into
WASM-managed memory and storing that owned pointer (allocate with _call_alloc
and write the ADT bytes before storing), or (B) restrict the 'b' (i32) cases to
only non-pointer scalar types and refuse/store differently for ADTs; update
_alloc_array_of_i32, the call sites that populate i32 arrays, and
_map_store/_map_get/_map_values to implement the chosen strategy so the GC
cannot collect referenced ADTs prematurely.

In `@vera/wasm/calls.py`:
- Around line 7274-7277: The conditional that treats any type name beginning
with "Array" as the actual Array type is too broad; restrict the pair-ABI check
to only the real Array type by changing the predicate that uses
vera_type.startswith("Array") to an equality check vera_type == "Array" (keep
the existing checks for "String" and the surrounding conditional), so the import
mangling logic that produces 's' only runs for the true Array type and not for
user types like ArrayList.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c75fad6a-5d52-4237-9540-8555d1c814c8

📥 Commits

Reviewing files that changed from the base of the PR and between 00931e7 and 09971e8.

📒 Files selected for processing (11)
  • README.md
  • TESTING.md
  • scripts/check_skill_examples.py
  • scripts/check_spec_examples.py
  • spec/09-standard-library.md
  • tests/test_checker.py
  • tests/test_codegen.py
  • vera/codegen/api.py
  • vera/codegen/assembly.py
  • vera/wasm/calls.py
  • vera/wasm/inference.py

Comment thread scripts/check_spec_examples.py Outdated
Comment thread tests/test_checker.py
Comment thread tests/test_codegen.py
Comment thread vera/codegen/api.py
Comment thread vera/codegen/api.py
Comment thread vera/codegen/api.py
Comment thread vera/wasm/calls.py Outdated
aallan and others added 3 commits March 23, 2026 15:44
Two allowlist entries pointed at prose lines instead of code fences.
Remove int_to_float entry (no language tag, heuristic handles it)
and fix float_is_nan to correct fence line 876.

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

- Add 4 new tests: bare map_new() inference, empty keys/values, get-after-remove
- Fix _map_wasm_tag: use exact "Array" match (not startsWith) to avoid
  matching user types like ArrayList
- Revert Array<T> as Map values — host runtime only round-trips String
  for the "s" pair-ABI tag. Documented as future enhancement in spec.
- Doc counts: 2780 tests

Co-Authored-By: Claude <noreply@anthropic.invalid>
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

Caution

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

⚠️ Outside diff range comments (2)
TESTING.md (2)

80-80: ⚠️ Potential issue | 🟡 Minor

Update stale conformance count in narrative text.
This line still says 56 programs, but the file now documents 57 elsewhere. Please align this sentence with the updated suite size.

As per coding guidelines, Markdown files must be reviewed for factual accuracy against the codebase.

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

In `@TESTING.md` at line 80, Update the stale count in the conformance suite
narrative: change the numeric literal "56" to "57" in the sentence that reads
"The conformance suite is a collection of 56 small, focused programs in
`tests/conformance/`..." so it matches the documented suite size elsewhere in
TESTING.md; ensure the sentence now reads "57" and run a quick grep for the
phrase "conformance suite" or "tests/conformance/" to confirm no other
mismatched counts remain.

148-148: ⚠️ Potential issue | 🟡 Minor

Parametrised test count is now outdated.
test_conformance.py is listed as 285 tests (Line 73), so this inline note should no longer say 280.

As per coding guidelines, Markdown files must be reviewed for factual accuracy against the codebase.

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

In `@TESTING.md` at line 148, The comment notes the inline test-count in
TESTING.md ("# Via pytest (parametrized — 280 tests)") is stale; update that
header to match the current count reported for test_conformance.py (285) or make
the note generic. Edit the header text in TESTING.md to replace "280 tests" with
"285 tests" (or change to a non-numeric phrase like "parametrized tests") so the
markdown accurately reflects test_conformance.py's test count.
♻️ Duplicate comments (4)
vera/wasm/calls.py (1)

7263-7277: ⚠️ Potential issue | 🔴 Critical

Recover Map<K, V> from the inferred expression type, not from hard-coded call shapes.

_infer_map_value_from_map_arg() still only recognises three built-ins, and _infer_map_key_from_map_arg() peels the first argument off any other ast.FnCall. That still misses the typed map_new() flow from this PR and user functions returning maps. When either helper returns None, _map_wasm_tag() silently falls back to "b", so map_get / map_keys / map_values can register the wrong host import instead of failing cleanly.

Also applies to: 7436-7444, 7560-7565

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

In `@vera/wasm/calls.py` around lines 7263 - 7277, The current mapping falls back
to "b" when helpers can't find map types by call-shape; update
_infer_map_value_from_map_arg and _infer_map_key_from_map_arg to recover
Map<K,V> from the expression's inferred type information (e.g. inspect the AST
node's resolved/inferred type or type annotation, and specifically handle
map_new and user functions that return maps) rather than only pattern-matching
built-in call shapes, and change _map_wasm_tag to not silently default to "b"
when the key/value type is unknown — instead propagate a clear error or None so
callers (map_get, map_keys, map_values) can fail fast and register the correct
host import or raise a meaningful error; ensure the same fix is applied to the
other occurrences that call these helpers.
tests/test_codegen.py (1)

7616-7777: ⚠️ Potential issue | 🟡 Minor

Add a direct codegen/runtime test for bare map_new() type inference.

The suite still does not directly exercise the zero-arg generic inference path (let @Map<..., ...> = map_new();) that this PR introduces. That leaves a regression gap in tests/test_codegen.py.

Suggested test addition
 class TestMapCollection:
@@
+    def test_map_new_infers_from_context(self) -> None:
+        """Bare map_new() infers K/V from expected map type."""
+        source = """
+public fn main(-> `@Int`)
+  requires(true) ensures(true) effects(pure)
+{
+  let `@Map`<String, Nat> = map_new();
+  map_size(`@Map`<String, Nat>.0)
+}
+"""
+        assert _run(source) == 0

As per coding guidelines, "Add codegen/runtime tests in tests/test_codegen.py for built-in functions — cover 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 7616 - 7777, Add a new test method in the
TestMapCollection class that directly exercises the zero-arg generic inference
path for map_new(): construct a bare map via "let `@Map`<SomeKey, SomeVal> =
map_new()" (e.g. `@Map`<String, Nat>), then call a built-in like map_size or
map_keys/map_values or map_get to verify correct runtime/codegen behavior
(assert expected value, e.g. size 0 or lookup returns None). Reference the
existing test names and helpers (TestMapCollection, map_new, map_size, map_keys,
map_values, map_get) so the new test mirrors the style of the other map tests
and covers the bare map_new() inference scenario.
tests/test_checker.py (1)

1526-1613: ⚠️ Potential issue | 🟠 Major

Missing wrong-type coverage for Map built-ins in checker tests.

Line 1595 adds an arity failure case, but this class still lacks a wrong-argument-type case (e.g., wrong V for map_insert). Please add at least one _check_err test for type mismatch so the new Map built-ins exercise both success and wrong-type paths.

🧪 Suggested test addition
 class TestMapCollection:
@@
     def test_map_wrong_arity(self) -> None:
         """map_insert with wrong number of args produces error."""
         _check_err("""
 private fn foo(`@Unit` -> `@Int`)
   requires(true) ensures(true) effects(pure)
 { map_size(map_insert(map_new(), "k")) }
 """, "expects")
+
+    def test_map_insert_wrong_value_type(self) -> None:
+        """map_insert rejects value type mismatch against V."""
+        _check_err("""
+private fn foo(`@Unit` -> `@Int`)
+  requires(true) ensures(true) effects(pure)
+{
+  let `@Map`<String, Nat> = map_new();
+  map_size(map_insert(`@Map`<String, Nat>.0, "k", "oops"))
+}
+""", "expected Nat")

As per coding guidelines, "Add type checker tests in tests/test_checker.py for built-in functions — at minimum, one test with correct types and one with wrong argument types".

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

In `@tests/test_checker.py` around lines 1526 - 1613, Add a negative type-check
test in the TestMapCollection class that mirrors test_map_insert_and_size but
supplies a wrong value type to map_insert so the checker rejects it; create a
new test (e.g., test_map_insert_wrong_value_type) that calls _check_err with an
expression using map_insert(map_new(), "k", "not_a_nat") (or otherwise wrong V
type) and assert an appropriate type-mismatch error (use the same error
substring style as test_map_wrong_arity), referencing map_insert and map_new so
both success and wrong-type paths are covered.
scripts/check_spec_examples.py (1)

64-67: ⚠️ Potential issue | 🔴 Critical

Duplicate ALLOWLIST key still causes silent shadowing and Ruff F601 failure.

Line 166 repeats the exact key from Line 65: ("09-standard-library.md", 876). In Python dict literals, the later entry overrides the earlier one, so one mapping is silently discarded.

🛠️ Suggested fix
-    ("09-standard-library.md", 876): "FRAGMENT",  # float_is_nan signature (no body)
+    # 876 handled in main section above (float_is_nan)
     ("09-standard-library.md", 893): "FRAGMENT",  # float_is_infinite signature (no body)
     ("09-standard-library.md", 912): "FRAGMENT",  # nan signature (no body)

Run this to confirm duplicate tuple keys in ALLOWLIST:

#!/bin/bash
python - <<'PY'
import ast
from pathlib import Path

path = Path("scripts/check_spec_examples.py")
tree = ast.parse(path.read_text(encoding="utf-8"))

for node in tree.body:
    if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name) and node.target.id == "ALLOWLIST":
        d = node.value
        if not isinstance(d, ast.Dict):
            continue
        seen = {}
        for k in d.keys:
            if isinstance(k, ast.Tuple) and len(k.elts) == 2 and all(isinstance(e, ast.Constant) for e in k.elts):
                key = (k.elts[0].value, k.elts[1].value)
                line = k.lineno
                if key in seen:
                    print(f"DUPLICATE {key}: first at line {seen[key]}, duplicated at line {line}")
                else:
                    seen[key] = line
PY

As per coding guidelines, "Flag actual duplicate keys within a single dict".

Also applies to: 166-167

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

In `@scripts/check_spec_examples.py` around lines 64 - 67, The ALLOWLIST dict in
scripts/check_spec_examples.py contains duplicate tuple keys (e.g.
("09-standard-library.md", 876) appears twice) which causes silent shadowing and
hides entries; open the ALLOWLIST definition and remove or consolidate the
duplicated tuple key so each (filename, line) tuple appears only once, ensuring
any associated value strings (e.g. "FRAGMENT") are preserved or merged as
appropriate; validate by scanning the ALLOWLIST literal in the file (symbol:
ALLOWLIST) and run the provided duplicate-detection snippet or add a small
runtime/assertion to fail fast if duplicate tuple keys are present.
🤖 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/wasm/calls.py`:
- Around line 7430-7435: The Map<...> fallback in the block that reads name =
expr.type_name and then uses split(", ", 1) is not nesting-aware and will break
on inner commas (e.g., Map<Result<Int, Bool>, String>); fix by replacing that
raw split with a depth-aware top-level-comma finder that walks the string and
splits at the comma where nesting depth is zero, or better yet use the parsed
TypeExpr data (e.g., expr.type_args or equivalent parsed fields) instead of
parsing expr.type_name raw string; update the same pattern used later (the
repeated code around the other Map fallback) to the same depth-aware or
parsed-data approach.

---

Outside diff comments:
In `@TESTING.md`:
- Line 80: Update the stale count in the conformance suite narrative: change the
numeric literal "56" to "57" in the sentence that reads "The conformance suite
is a collection of 56 small, focused programs in `tests/conformance/`..." so it
matches the documented suite size elsewhere in TESTING.md; ensure the sentence
now reads "57" and run a quick grep for the phrase "conformance suite" or
"tests/conformance/" to confirm no other mismatched counts remain.
- Line 148: The comment notes the inline test-count in TESTING.md ("# Via pytest
(parametrized — 280 tests)") is stale; update that header to match the current
count reported for test_conformance.py (285) or make the note generic. Edit the
header text in TESTING.md to replace "280 tests" with "285 tests" (or change to
a non-numeric phrase like "parametrized tests") so the markdown accurately
reflects test_conformance.py's test count.

---

Duplicate comments:
In `@scripts/check_spec_examples.py`:
- Around line 64-67: The ALLOWLIST dict in scripts/check_spec_examples.py
contains duplicate tuple keys (e.g. ("09-standard-library.md", 876) appears
twice) which causes silent shadowing and hides entries; open the ALLOWLIST
definition and remove or consolidate the duplicated tuple key so each (filename,
line) tuple appears only once, ensuring any associated value strings (e.g.
"FRAGMENT") are preserved or merged as appropriate; validate by scanning the
ALLOWLIST literal in the file (symbol: ALLOWLIST) and run the provided
duplicate-detection snippet or add a small runtime/assertion to fail fast if
duplicate tuple keys are present.

In `@tests/test_checker.py`:
- Around line 1526-1613: Add a negative type-check test in the TestMapCollection
class that mirrors test_map_insert_and_size but supplies a wrong value type to
map_insert so the checker rejects it; create a new test (e.g.,
test_map_insert_wrong_value_type) that calls _check_err with an expression using
map_insert(map_new(), "k", "not_a_nat") (or otherwise wrong V type) and assert
an appropriate type-mismatch error (use the same error substring style as
test_map_wrong_arity), referencing map_insert and map_new so both success and
wrong-type paths are covered.

In `@tests/test_codegen.py`:
- Around line 7616-7777: Add a new test method in the TestMapCollection class
that directly exercises the zero-arg generic inference path for map_new():
construct a bare map via "let `@Map`<SomeKey, SomeVal> = map_new()" (e.g.
`@Map`<String, Nat>), then call a built-in like map_size or map_keys/map_values or
map_get to verify correct runtime/codegen behavior (assert expected value, e.g.
size 0 or lookup returns None). Reference the existing test names and helpers
(TestMapCollection, map_new, map_size, map_keys, map_values, map_get) so the new
test mirrors the style of the other map tests and covers the bare map_new()
inference scenario.

In `@vera/wasm/calls.py`:
- Around line 7263-7277: The current mapping falls back to "b" when helpers
can't find map types by call-shape; update _infer_map_value_from_map_arg and
_infer_map_key_from_map_arg to recover Map<K,V> from the expression's inferred
type information (e.g. inspect the AST node's resolved/inferred type or type
annotation, and specifically handle map_new and user functions that return maps)
rather than only pattern-matching built-in call shapes, and change _map_wasm_tag
to not silently default to "b" when the key/value type is unknown — instead
propagate a clear error or None so callers (map_get, map_keys, map_values) can
fail fast and register the correct host import or raise a meaningful error;
ensure the same fix is applied to the other occurrences that call these helpers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d8a1e877-2b3f-4ced-8b3f-9e3d0a1ad30a

📥 Commits

Reviewing files that changed from the base of the PR and between f9b97b3 and 2d523db.

📒 Files selected for processing (7)
  • README.md
  • TESTING.md
  • scripts/check_spec_examples.py
  • spec/09-standard-library.md
  • tests/test_checker.py
  • tests/test_codegen.py
  • vera/wasm/calls.py

Comment thread vera/wasm/calls.py Outdated
Replace naive split(", ", 1) with nesting-aware parser that tracks
angle-bracket depth. Handles Map<Result<Int, Bool>, String> correctly.
Primary path still uses ast.SlotRef.type_args; this fixes the string
fallback for monomorphized composite slot names.

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

aallan commented Mar 23, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Fixed the nesting-aware split — replaced naive split(", ", 1) with a depth-tracking parser that counts angle brackets. Map<Result<Int, Bool>, String> now splits correctly at the top-level comma. Primary path still uses ast.SlotRef.type_args; this only affects the string fallback for monomorphized composite names.

@coderabbitai

coderabbitai Bot commented Mar 23, 2026

Copy link
Copy Markdown

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== Nesting-aware split — locate the parser ==="
grep -n "split\|depth\|angle\|bracket\|<\|type_arg" vera/wasm/calls.py | grep -i "depth\|bracket\|split\|angle" | head -30

echo ""
echo "=== Find the string fallback parser function/block ==="
grep -n "def.*split\|split.*,.*1\|depth\|angle_depth\|bracket_depth\|nest" vera/wasm/calls.py | head -30

echo ""
echo "=== Show context around the fix (look for depth tracking near SlotRef/type_args fallback) ==="
grep -n "depth\|fallback\|string.*split\|split.*1\b" vera/wasm/calls.py | head -30

Length of output: 2265


🏁 Script executed:

#!/bin/bash
echo "=== Full _split_map_type_args implementation ==="
sed -n '7546,7575p' vera/wasm/calls.py

echo ""
echo "=== Usage site 1 — line 7430 context ==="
sed -n '7420,7450p' vera/wasm/calls.py

echo ""
echo "=== Usage site 2 — line 7578 context ==="
sed -n '7568,7595p' vera/wasm/calls.py

Length of output: 3992


@aallan: Verified. The implementation is correct.

_split_map_type_args (line 7546) is clean:

  • The name[4:-1] slice is safe — both call sites already guard with name.startswith("Map<") and name.endswith(">").
  • The depth counter correctly handles all nesting levels. Tracing Map<Result<Int, Bool>, String>: depth reaches 1 at the < inside Result, drops back to 0 at the closing >, then the top-level comma is correctly identified — splitting into ("Result<Int, Bool>", "String").
  • The if k and v guard in the return means malformed monomorphized names (e.g. a bare Map<A> with no second arg) produce None and fall through safely rather than crashing.
  • Both call sites index the result correctly: v[1] for value inference, v[0] for key inference.

The primary ast.SlotRef.type_args path is unaffected; this only sharpens the string-fallback branch for monomorphized composite names.


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

Timestamp: 2026-03-23T16:09:48.784Z
Learning: In vera/wasm/calls.py, `_split_map_type_args` (static method, ~line 7546) implements a depth-tracking nesting-aware comma split for monomorphized Map type name strings (e.g. "Map<Result<Int, Bool>, String>"). It strips "Map<" and ">" via `name[4:-1]`, tracks angle-bracket depth, and splits only at depth==0 commas. It is only reached as a string-fallback when `ast.SlotRef.type_args` is unavailable; the primary inference path uses the typed AST directly.

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.


🧠 Learnings used
Learnt from: aallan
Repo: aallan/vera PR: 332
File: vera/codegen/api.py:869-874
Timestamp: 2026-03-23T15:52:53.357Z
Learning: In `vera/codegen/api.py`, the Map host-import runtime (`_VAL_WASM_TYPES` and all `vt == "s"` branches in `_define_map_insert`, `_define_map_get`, `_define_map_values`) restricts Map value types to primitives (i64 → `"i"`, f64 → `"f"`, i32/Bool/Byte/ADT/Map handle → `"b"`), and String (two-i32 pair → `"s"`). `Array<T>` as a Map value type is intentionally unsupported and documented as a future enhancement in spec 9.4.3, because element-type-aware serialization for array values would require a compound tag (e.g., `a{et}`) and element-stride-aware allocation on both the Python/wasmtime and browser JS runtimes.

Learnt from: aallan
Repo: aallan/vera PR: 332
File: vera/codegen/api.py:826-836
Timestamp: 2026-03-23T15:53:22.344Z
Learning: In vera/codegen/api.py, the `_map_store` Python dict (used to back Map<K,V> host imports) stores raw WASM heap addresses as Python integers. The Vera GC (bump allocator + mark-sweep) operates exclusively on WASM linear memory and the WASM stack — it has no visibility into Python-side structures. Therefore, ADT pointers stored as i32 values inside `_map_store` are NOT at risk of premature GC collection by the Vera GC. Raising concerns about dangling pointers due to GC collecting map-stored ADTs is incorrect for this architecture.

- 9 new codegen tests covering String values, Bool keys, Int keys,
  String+String maps, and map_keys/map_values type variants
- Fix stale "56" in TESTING.md conformance narrative (now 57)
- Add Decimal (#333) to ROADMAP Tier 2
- Use depth-aware comma split for Map type string fallback
- Restrict Array pair-ABI: documented as future enhancement in spec
- Doc counts: 2789 tests

Co-Authored-By: Claude <noreply@anthropic.invalid>
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.

1 participant