Skip to content

Add array_slice, array_map, array_filter, array_fold (v0.0.91)#319

Merged
aallan merged 5 commits into
mainfrom
feat/array-operations
Mar 19, 2026
Merged

Add array_slice, array_map, array_filter, array_fold (v0.0.91)#319
aallan merged 5 commits into
mainfrom
feat/array-operations

Conversation

@aallan

@aallan aallan commented Mar 19, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #133
Fixes #313, #314, #315, #316, #317, #318

Implementation

Component Change
vera/environment.py Register 4 new function signatures
vera/prelude.py Array combinator source (map_go, filter_go, fold_go + wrappers)
vera/wasm/calls.py array_slice WASM intrinsic (memcopy with clamping)
vera/wasm/inference.py Fix bare Array pair type, IndexExpr/IfExpr inference, apply_fn return type
vera/codegen/monomorphize.py De Bruijn reindexing, transitive monomorphization, ArrayLit/FnCall type inference
vera/codegen/modules.py array_slice in known names

Bug fixes

  1. Monomorphization: De Bruijn indices not adjusted when type variables collapse #316 De Bruijn reindexing — when A→Int, B→Int, @Array<A>.0 and @Array<B>.0 now correctly reindex to @Array<Int>.1 and @Array<Int>.0
  2. Monomorphization: no transitive instantiation of generic-calling-generic #317 Transitive monomorphization — worklist-based closure for generic-calling-generic
  3. WASM inference: _is_pair_type_name misses bare "Array" type name #313 Bare Array pair type_is_pair_type_name("Array") now returns True
  4. WASM inference: _infer_vera_type missing IndexExpr and IfExpr #314 IndexExpr/IfExpr inference_infer_vera_type handles indexed elements and conditionals
  5. WASM inference: _infer_fncall_vera_type missing apply_fn #315 apply_fn return type_infer_fncall_vera_type resolves closure return via alias substitution
  6. Monomorphization: type inference misses ArrayLit and FnCall argument types #318 ArrayLit/FnCall type inference_get_arg_type_info extracts types from array literals and function calls

Documentation

  • Spec §9.6.5–9.6.8: Full documentation for all four operations
  • Spec §4.12: Updated from 'future' to 'implemented'
  • SKILL.md: Array operations reference with examples
  • vera/README.md: New design pattern ci: bump actions/checkout from 4 to 6 #7 (De Bruijn + monomorphization)
  • ROADMAP: Array operations (map, fold, slice) #133 struck through, version updated
  • CHANGELOG: New [0.0.91] entry with Added + Fixed sections

Test plan

  • pytest tests/ -v — 2,390 tests pass (15 new)
  • mypy vera/ — clean
  • python scripts/check_conformance.py — 55 conformance programs pass
  • python scripts/check_examples.py — 25 examples pass
  • python scripts/check_version_sync.py — version in sync
  • python scripts/check_doc_counts.py — counts match
  • python scripts/check_spec_examples.py — spec blocks pass
  • python scripts/check_skill_examples.py — SKILL blocks pass
  • python scripts/check_readme_examples.py — README blocks pass
  • python scripts/check_faq_examples.py — FAQ blocks pass
  • python scripts/check_html_examples.py — HTML blocks pass
  • python scripts/check_limitations_sync.py — tables in sync
  • All 20 pre-commit hooks pass

🤖 Generated with Claude Code

Four new array operations completing #133:
- array_slice: WASM intrinsic with index clamping
- array_map: generic, element type can change (A → B)
- array_filter: predicate-based element selection
- array_fold: left fold with arbitrary accumulator type

Higher-order operations (map/filter/fold) implemented via prelude
source injection with recursive helpers, following the same pattern
as Option/Result combinators.

Six compiler bug fixes discovered during implementation:
- De Bruijn index reindexing when type variables collapse (#316)
- Transitive monomorphization for generic-calling-generic (#317)
- _is_pair_type_name missing bare "Array" (#313)
- _infer_vera_type missing IndexExpr and IfExpr (#314)
- _infer_fncall_vera_type missing apply_fn (#315)
- _get_arg_type_info missing ArrayLit and FnCall (#318)

15 new unit tests, spec §9.6.5–9.6.8, documentation updates
across SKILL.md, README, TESTING, ROADMAP, CHANGELOG, FAQ.

Closes #133
Fixes #313, #314, #315, #316, #317, #318

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

coderabbitai Bot commented Mar 19, 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 four built-in array functions (array_slice, array_map, array_filter, array_fold) with specs, prelude injections, builtin registrations, WASM translation for slice, monomorphisation improvements (worklist closure and De Bruijn reindexing), plus documentation, tests, and script updates for v0.0.91.

Changes

Cohort / File(s) Summary
Version & Metadata
pyproject.toml, vera/__init__.py, docs/index.html
Bumped package/docs version to 0.0.91.
Release & Project Docs
CHANGELOG.md, README.md, ROADMAP.md, TESTING.md
Added v0.0.91 release entry; updated test counts, roadmap, and built-in counts to include array operations.
Specification & Standard Library
spec/09-standard-library.md, spec/04-expressions.md, SKILL.md
Added formal specs for array_slice, array_map, array_filter, array_fold (generic signatures, effects, semantics); reindexed sections and updated references.
Prelude Injection & Implementations
vera/prelude.py
Injected array combinator type aliases and private recursive implementations; injection is no longer gated on Option/Result presence and now avoids name collisions.
Type Environment / Builtins
vera/environment.py
Registered new pure built-in signatures for the four array operations with explicit forall variables.
Monomorphisation
vera/codegen/monomorphize.py
Moved to worklist-driven transitive instantiation; added array-literal and array-builtin type hints; implemented De Bruijn slot reindexing and reindex-aware SlotRef substitution.
WASM Codegen / Calls
vera/wasm/calls.py
Added _translate_array_slice (index clamping, allocation, byte-wise copy) and made slice/filter recognized as array-producing for element-type inference.
WASM Type Inference
vera/wasm/inference.py
Treats array_slice as i32_pair; added inference for IndexExpr, IfExpr, and apply_fn closures; broadened pair-type recognition to include bare Array.
Cross-module Call Detection
vera/codegen/modules.py
Added array_slice to known built-in names to avoid false undefined cross-module reports.
Tests
tests/test_codegen_monomorphize.py, tests/test_prelude.py
Added TestArrayOperations tests (compile & runtime) for slice/map/filter/fold; updated prelude tests to expect array declarations injected regardless of Option/Result ADTs.
Scripts & Docs Maintenance
scripts/check_skill_examples.py, scripts/check_spec_examples.py, scripts/check_readme_examples.py, tests/test_readme.py, vera/README.md, docs/index.html
Adjusted allowlist line numbers and added spec/test fragments for new array functions; documented monomorphisation/De Bruijn and WASM inference notes.

Sequence Diagram(s)

sequenceDiagram
    participant User as User Code
    participant Prelude as Prelude Injector
    participant TypeEnv as Type Environment
    participant Monomorph as Monomorphiser
    participant WASM as WASM Codegen

    User->>Prelude: Submit program using array_map/array_slice
    Prelude->>Prelude: Inject array combinators and aliases
    Prelude->>TypeEnv: Register built-in type signatures
    TypeEnv-->>Prelude: Ack registrations

    User->>Monomorph: Request monomorphisation
    Monomorph->>Monomorph: Worklist discovers generic instantiations
    Monomorph->>Monomorph: Infer types (including from array literals/builtins)
    Monomorph->>Monomorph: Build De Bruijn reindex map if namespaces merge
    Monomorph->>Monomorph: Substitute AST slots using reindex mapping
    Monomorph-->>User: Return monomorphised definitions

    Monomorph->>WASM: Emit monomorphic definitions
    WASM->>WASM: Infer pair/element types (array_slice → i32_pair)
    WASM->>WASM: Translate array_slice (clamp, alloc, copy)
    WASM-->>User: WASM-compatible (ptr,len) results
Loading
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarises the main change: adding four new array operations (array_slice, array_map, array_filter, array_fold) and version bump to v0.0.91.
Description check ✅ Passed The description is comprehensive and clearly related to the changeset, detailing the four array operations, six compiler bug fixes, implementation details, and documentation updates.
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.

📝 Coding Plan
  • Generate coding plan for human review comments

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

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

Caution

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

⚠️ Outside diff range comments (1)
scripts/check_spec_examples.py (1)

51-203: ⚠️ Potential issue | 🔴 Critical

Duplicate dictionary keys will silently drop allowlist entries, and multiple line numbers are incorrect.

Five duplicate keys in ALLOWLIST will cause Python to silently overwrite earlier entries, preventing spec examples from being properly validated. Additionally, many line numbers are misaligned with the actual spec positions:

Current Line Duplicate Key Duplicates Spec Line Current Value Impact
152 ("09-standard-library.md", 694) Line 82 761 694 float_to_int entry lost; wrong line
159 ("09-standard-library.md", 853) Line 84 854 853 nan entry lost; off-by-one
168 ("09-standard-library.md", 889) Line 163 956 889 to_upper entry lost; significant mismatch
169 ("09-standard-library.md", 904) Line 164 971 904 to_lower entry lost; significant mismatch
194 ("09-standard-library.md", 1228) Line 187 1295 1228 similarity entry lost; significant mismatch

Additionally, several non-duplicate entries also have incorrect line numbers (lines 82, 84, 163, 164, 187 are all off from spec reality). Remove duplicate keys and correct all line numbers to match the actual code block locations in spec/09-standard-library.md.

🤖 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 51 - 203, ALLOWLIST contains
duplicate tuple keys that silently overwrite earlier entries and several entries
have incorrect line numbers; remove duplicate keys and correct the line numbers
to match spec/09-standard-library.md so no entries are lost. Specifically, in
the ALLOWLIST dict update/remove duplicate keys such as
("09-standard-library.md", 694), ("09-standard-library.md", 853),
("09-standard-library.md", 889), ("09-standard-library.md", 904),
("09-standard-library.md", 1228) and ensure the entries for float_to_int, nan,
to_upper, to_lower, similarity (and any other misaligned entries) use the
correct line numbers from spec/09-standard-library.md; after editing ALLOWLIST,
verify there are no repeated tuple keys and run the spec example checker to
confirm all intended allowlist entries are present.
🧹 Nitpick comments (7)
README.md (1)

653-653: Consider using explicit builtin names in the Collections row.
Using array_map, array_filter, array_fold, and array_slice here would remove ambiguity about whether these are methods or standalone built-ins.

Suggested wording tweak
-| Collections | `Array<T>` with `map`, `filter`, `fold`, `slice` | Functional iteration — no mutation, no loop constructs |
+| Collections | `Array<T>` with `array_map`, `array_filter`, `array_fold`, `array_slice` | Functional iteration — no mutation, no loop constructs |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 653, The Collections row currently lists `map`, `filter`,
`fold`, `slice` which is ambiguous about whether they are methods or standalone
builtins; update that row to use explicit builtin names like `array_map`,
`array_filter`, `array_fold`, and `array_slice` so readers know these are
top-level functions (refer to the table row labeled "Collections" and the items
`map`, `filter`, `fold`, `slice` for the exact place to change).
tests/test_codegen_monomorphize.py (1)

704-897: Add coverage for generic type-shape behaviour and fold argument order.
The suite validates happy paths well, but it does not yet lock in two core semantics: (1) array_map changing element type, and (2) fold callback argument order on a non-commutative operation.

Proposed test additions
 class TestArrayOperations:
@@
+    def test_array_map_type_change(self) -> None:
+        """Map Int -> String changes element type."""
+        source = """\
+public fn main(-> `@Bool`)
+  requires(true) ensures(true) effects(pure)
+{
+  let `@Array`<String> = array_map([1, 2], fn(`@Int` -> `@String`) effects(pure) { to_string(`@Int.0`) });
+  eq(`@Array`<String>.0[1], "2")
+}
+"""
+        assert _run(source, fn="main") == 1
+
+    def test_array_fold_order_non_commutative(self) -> None:
+        """Fold callback order is (acc, elem), left-to-right."""
+        source = """\
+public fn main(-> `@Int`)
+  requires(true) ensures(true) effects(pure)
+{
+  array_fold([1, 2, 3], 0, fn(`@Int`, `@Int` -> `@Int`) effects(pure) { `@Int.1` * 10 + `@Int.0` })
+}
+"""
+        assert _run(source, fn="main") == 123
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_codegen_monomorphize.py` around lines 704 - 897, Add tests
covering (1) array_map changing element type and (2) array_fold argument order
for non-commutative ops: add a test that uses array_map to transform Int->Bool
(e.g., map [1,2,3] to [true/false] and assert type-shaped behavior or element
values) referencing array_map and assert result indexing/length, and add a test
for array_fold where the fold callback distinguishes accumulator vs element
order (e.g., subtract or concatenate with order-sensitive operation) referencing
array_fold and asserting the exact numeric/string result to lock in the callback
argument order.
tests/test_prelude.py (1)

69-70: Prefer name-based assertions over raw declaration-count checks.
These assertions are fragile and can mask incorrect injection content.

Suggested assertion hardening
-        # 9 array declarations: 3 type aliases + 6 functions
-        assert len(prog.declarations) == orig_len + 9
+        fn_names = {
+            tld.decl.name
+            for tld in prog.declarations
+            if isinstance(tld.decl, ast.FnDecl)
+        }
+        alias_names = {
+            tld.decl.name
+            for tld in prog.declarations
+            if isinstance(tld.decl, ast.TypeAliasDecl)
+        }
+        assert {
+            "array_map", "array_map_go",
+            "array_filter", "array_filter_go",
+            "array_fold", "array_fold_go",
+        }.issubset(fn_names)
+        assert {"ArrayMapFn", "ArrayFilterFn", "ArrayFoldFn"}.issubset(alias_names)
+        assert "option_map" not in fn_names
+        assert "result_map" not in fn_names
@@
-        # Only array declarations injected (9), not Option combinators
-        assert len(prog.declarations) == orig_len + 9
+        fn_names = {
+            tld.decl.name
+            for tld in prog.declarations
+            if isinstance(tld.decl, ast.FnDecl)
+        }
+        assert {
+            "array_map", "array_map_go",
+            "array_filter", "array_filter_go",
+            "array_fold", "array_fold_go",
+        }.issubset(fn_names)
+        assert "option_unwrap_or" not in fn_names
+        assert "option_map" not in fn_names
+        assert "option_and_then" not in fn_names

Also applies to: 85-86

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

In `@tests/test_prelude.py` around lines 69 - 70, The test currently asserts a
fragile raw count on prog.declarations; instead replace the length checks with
name-based assertions that ensure the specific 3 type-alias names and 6 function
names were injected into prog.declarations. Locate the checks that use
prog.declarations (the count assertion around the "9 array declarations" and the
similar assertion at the later block) and assert presence by collecting
declaration names from prog.declarations and comparing against an expected set
of declaration names (or asserting each expected name exists) so the test
verifies exact injected symbols rather than just a length.
ROADMAP.md (2)

35-35: Incomplete operation list in the Tier 1 ASCII diagram.

The diagram entry "#133 map/fold" only mentions 2 of the 4 delivered operations (map and fold), omitting filter and slice. For consistency with lines 50 and 58 which list all four operations, consider:

  • Using "array ops" (matching the Tier 0 entry on the same line), or
  • Expanding to "map/fold/filter/slice" if space permits.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ROADMAP.md` at line 35, The Tier 1 ASCII diagram currently lists "#133
map/fold" but omits the other delivered operations; update that entry (the text
"#133 map/fold") to either "✓ `#133` array ops" to match the Tier 0 wording or
expand it to "✓ `#133` map/fold/filter/slice" so all four operations are listed
(locate the string "#133 map/fold" in ROADMAP.md and replace accordingly).

21-21: Clarify whether the counts are cumulative or overlapping.

The phrasing "72 built-in functions plus 5 Option/Result combinators plus 4 higher-order array operations" is ambiguous about whether these counts overlap. Consider either:

  • "72 built-in functions (including 5 Option/Result combinators and 4 array operations)" if 72 is the total, or
  • "68 built-in functions plus 5 Option/Result combinators plus 4 higher-order array operations (77 total)" if they're separate counts.

Based on the 68 → 72 increment matching the 4 new array operations, the former interpretation appears correct.

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

In `@ROADMAP.md` at line 21, The statement under v0.0.91 in ROADMAP.md is
ambiguous about whether "72 built-in functions plus 5 Option/Result combinators
plus 4 higher-order array operations" are overlapping counts; update that
sentence (the v0.0.91 release description) to explicitly state the intended
interpretation—for example, "72 built-in functions (including 5 Option/Result
combinators and 4 higher-order array operations)" if 72 is the total, or
alternatively list each as a separate subtotal and include the total sum—so
readers know whether the combinators and array ops are included in the 72 or are
additional.
vera/codegen/monomorphize.py (1)

529-539: Consider adding strict=True to zip for defensive programming.

While the invariant that forall_vars and concrete_types have equal length is maintained by the call chain, adding strict=True would catch any future regression and make the assumption explicit.

♻️ Optional improvement
         assert decl.forall_vars is not None
-        mapping = dict(zip(decl.forall_vars, concrete_types))
+        mapping = dict(zip(decl.forall_vars, concrete_types, strict=True))
         mangled = self._mangle_fn_name(decl.name, concrete_types)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vera/codegen/monomorphize.py` around lines 529 - 539, The zip between
decl.forall_vars and concrete_types should be made strict to fail fast on length
mismatches: when building mapping = dict(zip(decl.forall_vars, concrete_types))
update it to use zip(..., strict=True) so any future mismatch between
decl.forall_vars and concrete_types is caught; the change affects the code path
that computes mapping used by _mangle_fn_name, _build_reindex_map, and
_substitute_in_ast.
vera/wasm/inference.py (1)

457-479: LGTM with optional improvement.

The closure return type inference logic is sound. The length check on lines 465–467 ensures the zip on line 469 is safe. However, adding strict=True would make the intent explicit and catch any future regression if the guard is accidentally removed.

♻️ Optional: add `strict=True` for clarity
-                        alias_map = dict(zip(alias_params,
-                                             closure_arg.type_args))
+                        alias_map = dict(zip(alias_params,
+                                             closure_arg.type_args,
+                                             strict=True))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vera/wasm/inference.py` around lines 457 - 479, The zip used to build
alias_map in the apply_fn closure inference should be made explicit/strict to
ensure lengths are enforced; update the dict(zip(alias_params,
closure_arg.type_args)) call inside the apply_fn handling (where alias_params
and closure_arg.type_args are checked and alias_map is created) to use
strict=True so any future removal of the length guard will raise if lengths
differ, leaving the surrounding checks (closure_arg, alias_te, alias_params)
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Line 585: Update the link label "CI" in the README sentence describing
coverage enforcement to read "GitHub CI"; locate the string "pre-commit hooks
and [CI](.github/workflows/ci.yml)" in the README (around the testing summary)
and change the visible text from "CI" to "GitHub CI" while keeping the existing
link target intact so it now reads "[GitHub CI](.github/workflows/ci.yml)".

In `@scripts/check_skill_examples.py`:
- Around line 99-108: The ALLOWLIST dictionary contains duplicate numeric keys
(notably 1132 and 1294) which silently overwrite earlier entries; locate the
ALLOWLIST definition (the mapping with entries like ("FRAGMENT", "Wrong: missing
contracts") keyed by line numbers) and remove or correct the duplicate keys so
each line-number key is unique — either delete the redundant entry if it refers
to the same SKILL.md block or change the duplicated key to the correct line
number that matches the intended SKILL.md fragment, ensuring both intended
allowlist entries remain present.

In `@vera/wasm/calls.py`:
- Around line 845-852: The code currently falls back to elem_size = 8 when
CallsMixin._infer_concat_elem_type(arr_arg) returns None; instead, remove the
unconditional 8-byte default and only use a fallback when the array is provably
empty or when you can deduce emptiness from the node (e.g., a literal empty
slice). If the element type cannot be inferred, try to derive the element size
from call-site/type information (e.g., function/type annotations or the caller's
expected Array<T> type) before calling _element_mem_size; if no safe size can be
determined, return None (propagating the failure) rather than guessing 8 so that
callers (e.g., the concat/slice lowering logic) can abort or handle it safely.
Ensure the logic touches the branch around _infer_concat_elem_type, the use of
_element_mem_size, and the places that previously relied on the 8-byte default.
- Around line 872-880: The current code evaluates start/end, immediately calls
i32.wrap_i64 and stores into locals s/e, which truncates large i64 indices;
instead clamp start and end as i64 against 0 and the array length (arr_len)
before wrapping. Replace the sequence after start_instrs/end_instrs with i64
comparisons and selects: compare with i64.const 0 and i64.const arr_len (use the
same arr_len value used elsewhere), clamp using i64.lt_s/i64.gt_s + select or
min/max pattern to force values into [0, arr_len] while still i64, then call
i32.wrap_i64 and finally local.set {s} / local.set {e}; keep references to
start_instrs, end_instrs, i32.wrap_i64, and the locals s and e when making the
change.

In `@vera/wasm/inference.py`:
- Around line 395-396: The current handling of ast.IfExpr in _infer_vera_type
blindly calls self._infer_vera_type(expr.then_branch.expr) which can pass None;
update the ast.IfExpr branch in _infer_vera_type to first check whether
expr.then_branch.expr is None and handle that case (e.g., return None or infer
from the branch's last statement/value) before calling self._infer_vera_type;
reference ast.IfExpr, then_branch.expr and _infer_vera_type to locate and fix
the code.

---

Outside diff comments:
In `@scripts/check_spec_examples.py`:
- Around line 51-203: ALLOWLIST contains duplicate tuple keys that silently
overwrite earlier entries and several entries have incorrect line numbers;
remove duplicate keys and correct the line numbers to match
spec/09-standard-library.md so no entries are lost. Specifically, in the
ALLOWLIST dict update/remove duplicate keys such as ("09-standard-library.md",
694), ("09-standard-library.md", 853), ("09-standard-library.md", 889),
("09-standard-library.md", 904), ("09-standard-library.md", 1228) and ensure the
entries for float_to_int, nan, to_upper, to_lower, similarity (and any other
misaligned entries) use the correct line numbers from
spec/09-standard-library.md; after editing ALLOWLIST, verify there are no
repeated tuple keys and run the spec example checker to confirm all intended
allowlist entries are present.

---

Nitpick comments:
In `@README.md`:
- Line 653: The Collections row currently lists `map`, `filter`, `fold`, `slice`
which is ambiguous about whether they are methods or standalone builtins; update
that row to use explicit builtin names like `array_map`, `array_filter`,
`array_fold`, and `array_slice` so readers know these are top-level functions
(refer to the table row labeled "Collections" and the items `map`, `filter`,
`fold`, `slice` for the exact place to change).

In `@ROADMAP.md`:
- Line 35: The Tier 1 ASCII diagram currently lists "#133 map/fold" but omits
the other delivered operations; update that entry (the text "#133 map/fold") to
either "✓ `#133` array ops" to match the Tier 0 wording or expand it to "✓ `#133`
map/fold/filter/slice" so all four operations are listed (locate the string
"#133 map/fold" in ROADMAP.md and replace accordingly).
- Line 21: The statement under v0.0.91 in ROADMAP.md is ambiguous about whether
"72 built-in functions plus 5 Option/Result combinators plus 4 higher-order
array operations" are overlapping counts; update that sentence (the v0.0.91
release description) to explicitly state the intended interpretation—for
example, "72 built-in functions (including 5 Option/Result combinators and 4
higher-order array operations)" if 72 is the total, or alternatively list each
as a separate subtotal and include the total sum—so readers know whether the
combinators and array ops are included in the 72 or are additional.

In `@tests/test_codegen_monomorphize.py`:
- Around line 704-897: Add tests covering (1) array_map changing element type
and (2) array_fold argument order for non-commutative ops: add a test that uses
array_map to transform Int->Bool (e.g., map [1,2,3] to [true/false] and assert
type-shaped behavior or element values) referencing array_map and assert result
indexing/length, and add a test for array_fold where the fold callback
distinguishes accumulator vs element order (e.g., subtract or concatenate with
order-sensitive operation) referencing array_fold and asserting the exact
numeric/string result to lock in the callback argument order.

In `@tests/test_prelude.py`:
- Around line 69-70: The test currently asserts a fragile raw count on
prog.declarations; instead replace the length checks with name-based assertions
that ensure the specific 3 type-alias names and 6 function names were injected
into prog.declarations. Locate the checks that use prog.declarations (the count
assertion around the "9 array declarations" and the similar assertion at the
later block) and assert presence by collecting declaration names from
prog.declarations and comparing against an expected set of declaration names (or
asserting each expected name exists) so the test verifies exact injected symbols
rather than just a length.

In `@vera/codegen/monomorphize.py`:
- Around line 529-539: The zip between decl.forall_vars and concrete_types
should be made strict to fail fast on length mismatches: when building mapping =
dict(zip(decl.forall_vars, concrete_types)) update it to use zip(...,
strict=True) so any future mismatch between decl.forall_vars and concrete_types
is caught; the change affects the code path that computes mapping used by
_mangle_fn_name, _build_reindex_map, and _substitute_in_ast.

In `@vera/wasm/inference.py`:
- Around line 457-479: The zip used to build alias_map in the apply_fn closure
inference should be made explicit/strict to ensure lengths are enforced; update
the dict(zip(alias_params, closure_arg.type_args)) call inside the apply_fn
handling (where alias_params and closure_arg.type_args are checked and alias_map
is created) to use strict=True so any future removal of the length guard will
raise if lengths differ, leaving the surrounding checks (closure_arg, alias_te,
alias_params) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d6570814-029b-4a8a-aadb-d55b94be8e2c

📥 Commits

Reviewing files that changed from the base of the PR and between aacbcb2 and b908d78.

📒 Files selected for processing (21)
  • CHANGELOG.md
  • README.md
  • ROADMAP.md
  • SKILL.md
  • TESTING.md
  • docs/index.html
  • pyproject.toml
  • scripts/check_skill_examples.py
  • scripts/check_spec_examples.py
  • spec/04-expressions.md
  • spec/09-standard-library.md
  • tests/test_codegen_monomorphize.py
  • tests/test_prelude.py
  • vera/README.md
  • vera/__init__.py
  • vera/codegen/modules.py
  • vera/codegen/monomorphize.py
  • vera/environment.py
  • vera/prelude.py
  • vera/wasm/calls.py
  • vera/wasm/inference.py

Comment thread README.md
### Testing

Testing is organized in three layers: **unit tests** (2,375 tests across 24 files, testing compiler internals and browser parity), a **conformance suite** (55 programs across 9 spec chapters, systematically validating every language feature against the spec), and **example programs** (25 end-to-end demos). The compiler has 91% code coverage, enforced by pre-commit hooks and [CI](.github/workflows/ci.yml) across 6 Python/OS combinations plus a dedicated browser parity job (Node.js 22). Every commit validates all conformance programs, example programs, and specification code blocks. See **[TESTING.md](TESTING.md)** for the full testing reference.
Testing is organized in three layers: **unit tests** (2,390 tests across 24 files, testing compiler internals and browser parity), a **conformance suite** (55 programs across 9 spec chapters, systematically validating every language feature against the spec), and **example programs** (25 end-to-end demos). The compiler has 91% code coverage, enforced by pre-commit hooks and [CI](.github/workflows/ci.yml) across 6 Python/OS combinations plus a dedicated browser parity job (Node.js 22). Every commit validates all conformance programs, example programs, and specification code blocks. See **[TESTING.md](TESTING.md)** for the full testing reference.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use the official “GitHub” capitalisation.
Line [585] currently uses “CI” as a link label; prefer “GitHub CI” for naming consistency and clarity.

Suggested wording tweak
-... enforced by pre-commit hooks and [CI](.github/workflows/ci.yml) ...
+... enforced by pre-commit hooks and [GitHub CI](.github/workflows/ci.yml) ...
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Testing is organized in three layers: **unit tests** (2,390 tests across 24 files, testing compiler internals and browser parity), a **conformance suite** (55 programs across 9 spec chapters, systematically validating every language feature against the spec), and **example programs** (25 end-to-end demos). The compiler has 91% code coverage, enforced by pre-commit hooks and [CI](.github/workflows/ci.yml) across 6 Python/OS combinations plus a dedicated browser parity job (Node.js 22). Every commit validates all conformance programs, example programs, and specification code blocks. See **[TESTING.md](TESTING.md)** for the full testing reference.
Testing is organized in three layers: **unit tests** (2,390 tests across 24 files, testing compiler internals and browser parity), a **conformance suite** (55 programs across 9 spec chapters, systematically validating every language feature against the spec), and **example programs** (25 end-to-end demos). The compiler has 91% code coverage, enforced by pre-commit hooks and [GitHub CI](.github/workflows/ci.yml) across 6 Python/OS combinations plus a dedicated browser parity job (Node.js 22). Every commit validates all conformance programs, example programs, and specification code blocks. See **[TESTING.md](TESTING.md)** for the full testing reference.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~585-~585: The official name of this software platform is spelled with a capital “H”.
Context: ...ge, enforced by pre-commit hooks and CI across 6 Python/OS co...

(GITHUB)

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

In `@README.md` at line 585, Update the link label "CI" in the README sentence
describing coverage enforcement to read "GitHub CI"; locate the string
"pre-commit hooks and [CI](.github/workflows/ci.yml)" in the README (around the
testing summary) and change the visible text from "CI" to "GitHub CI" while
keeping the existing link target intact so it now reads "[GitHub
CI](.github/workflows/ci.yml)".

Comment thread scripts/check_skill_examples.py Outdated
Comment thread vera/wasm/calls.py
Comment thread vera/wasm/calls.py
Comment on lines +872 to +880
# Evaluate start → i64, wrap to i32
instructions.extend(start_instrs)
instructions.append("i32.wrap_i64")
instructions.append(f"local.set {s}")

# Evaluate end → i64, wrap to i32
instructions.extend(end_instrs)
instructions.append("i32.wrap_i64")
instructions.append(f"local.set {e}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

In WebAssembly, does i32.wrap_i64truncate the upper 32 bits of ani64 value rather than clamp or saturate, and what result does it give for values outside the 32-bit signed range?

💡 Result:

Yes. i32.wrap_i64 wraps/truncates an i64 to i32 by discarding the upper 32 bits (equivalently, computing the value modulo (2^{32})); it does not clamp or saturate. [1]

For inputs outside the 32-bit signed range ([-2^{31}, 2^{31}-1]), the result is just the low 32 bits, which—when interpreted as signed—uses two’s-complement (so values with bit 31 set become negative). [2] For example: [1][2]

  • 2147483648 (0x0000000080000000) → 0x80000000-2147483648
  • -2147483649 (…0x7FFFFFFF) → 0x7FFFFFFF2147483647

Sources: [1] (developer.mozilla.org), [2] (webassembly.github.io)

Citations:


🏁 Script executed:

#!/bin/bash
# Check if the file exists and get its line count
if [ -f "vera/wasm/calls.py" ]; then
  echo "File exists. Line count:"
  wc -l vera/wasm/calls.py
else
  echo "File not found. Searching for calls.py..."
  find . -name "calls.py" -type f
fi

Repository: aallan/vera

Length of output: 103


🏁 Script executed:

#!/bin/bash
# Read the code around the specified lines for context
sed -n '860,930p' vera/wasm/calls.py | cat -n

Repository: aallan/vera

Length of output: 3294


Clamp in i64 space before wrapping.

i32.wrap_i64 truncates by discarding the upper 32 bits; it does not clamp. Large Int indices can therefore wrap to small or negative i32 values before clamping logic runs. For example, array_slice(xs, 1 << 33, (1 << 33) + 1) wraps to a small value instead of clamping to the correct range. Compare start and end against 0 and arr_len as i64 values, then wrap to i32 only after ensuring they are in range.

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

In `@vera/wasm/calls.py` around lines 872 - 880, The current code evaluates
start/end, immediately calls i32.wrap_i64 and stores into locals s/e, which
truncates large i64 indices; instead clamp start and end as i64 against 0 and
the array length (arr_len) before wrapping. Replace the sequence after
start_instrs/end_instrs with i64 comparisons and selects: compare with i64.const
0 and i64.const arr_len (use the same arr_len value used elsewhere), clamp using
i64.lt_s/i64.gt_s + select or min/max pattern to force values into [0, arr_len]
while still i64, then call i32.wrap_i64 and finally local.set {s} / local.set
{e}; keep references to start_instrs, end_instrs, i32.wrap_i64, and the locals s
and e when making the change.

Comment thread vera/wasm/inference.py Outdated
aallan and others added 2 commits March 19, 2026 16:43
- Change infinity signature block from bare ``` to ```vera
- Fix allowlist line number 803 → 802

Co-Authored-By: Claude <noreply@anthropic.invalid>
- Guard against None in _infer_vera_type IfExpr handler
- Only use 8-byte elem_size fallback for provably empty arrays
- Fix duplicate dict keys 1132 and 1294 in check_skill_examples.py

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 `@scripts/check_spec_examples.py`:
- Line 82: The ALLOWLIST contains duplicate dictionary keys causing earlier
entries to be overwritten; locate the repeated tuple keys (e.g.,
("09-standard-library.md", 694), ("09-standard-library.md", 853),
("09-standard-library.md", 889), ("09-standard-library.md", 904),
("09-standard-library.md", 1228") in the ALLOWLIST definition and remove the
redundant entries so each tuple appears only once, ensuring you keep the correct
intended value for each key; for the ("09-standard-library.md", 853) duplicate
specifically verify the associated function name/value matches the actual
example (fix the function name if mismatched) before deleting the extra entry.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8f8d19a3-2cc6-411c-9c48-85af7c43f9f7

📥 Commits

Reviewing files that changed from the base of the PR and between b908d78 and 402c9d9.

📒 Files selected for processing (2)
  • scripts/check_spec_examples.py
  • spec/09-standard-library.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • spec/09-standard-library.md

Comment thread scripts/check_spec_examples.py

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

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

1001-1005: ⚠️ Potential issue | 🟠 Major

_infer_concat_elem_type still rejects array_map(...) results.

This helper now knows about array_slice and array_filter, but array_slice(array_map(xs, f), ...) still bottoms out at Line 845 and returns None. Please derive Array<T> from the expression's inferred return type here, or handle array_map explicitly, so the new array combinators actually compose.

🔧 Fix sketch
     def _infer_concat_elem_type(self, expr: ast.Expr) -> str | None:
+        arg_info = self._get_arg_type_info_wasm(expr)
+        if arg_info and arg_info[0] == "Array" and arg_info[1]:
+            return arg_info[1][0]
+
         if isinstance(expr, ast.SlotRef):
             if expr.type_name == "Array" and expr.type_args:
                 ta = expr.type_args[0]
                 if isinstance(ta, ast.NamedType):
                     return ta.name
@@
         if isinstance(expr, ast.FnCall):
             if expr.name == "array_range":
                 return "Int"
             if expr.name in (
                 "array_concat", "array_append", "array_slice",
                 "array_filter",
             ) and expr.args:
                 return self._infer_concat_elem_type(expr.args[0])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vera/wasm/calls.py` around lines 1001 - 1005, The branch that returns
self._infer_concat_elem_type(expr.args[0]) for array combinators (expr.name in
("array_concat","array_append","array_slice","array_filter")) fails when the arg
is an array-producing call like array_map; update this logic in the function
handling expr.name to either (A) if expr.args[0] is a call expression (e.g.
expr.args[0].name == "array_map") handle it explicitly by extracting the mapped
element type (using the map's return type), or (B) more generally obtain the
inferred return type of expr.args[0] and, if it is an Array<T>, derive and
return T; ensure this uses the existing _infer_concat_elem_type helper and any
available type inference routine so array_slice(array_map(...)) and
array_filter(array_map(...)) correctly yield the inner element type instead of
None.

876-921: ⚠️ Potential issue | 🟠 Major

Clamp indices in i64 space before wrapping.

Line 876 and Line 881 still truncate the user-supplied Int indices before any bounds check. Values like 1 << 33 will wrap into unrelated i32 values, so the later clamp can produce the wrong slice instead of clamping to the array bounds.

🔧 Fix sketch
-        # Evaluate start → i64, wrap to i32
-        instructions.extend(start_instrs)
-        instructions.append("i32.wrap_i64")
-        instructions.append(f"local.set {s}")
-
-        # Evaluate end → i64, wrap to i32
-        instructions.extend(end_instrs)
-        instructions.append("i32.wrap_i64")
-        instructions.append(f"local.set {e}")
+        s64 = self.alloc_local("i64")
+        e64 = self.alloc_local("i64")
+
+        # Evaluate start/end as i64 first
+        instructions.extend(start_instrs)
+        instructions.append(f"local.set {s64}")
+        instructions.extend(end_instrs)
+        instructions.append(f"local.set {e64}")
+
+        # Clamp start/end against 0 and arr_len while still in i64,
+        # then wrap once the values are known to be in range.
+        ...
+        instructions.append(f"local.get {s64}")
+        instructions.append("i32.wrap_i64")
+        instructions.append(f"local.set {s}")
+        instructions.append(f"local.get {e64}")
+        instructions.append("i32.wrap_i64")
+        instructions.append(f"local.set {e}")
In WebAssembly, does `i32.wrap_i64` truncate the upper 32 bits rather than clamp, and should user-supplied `i64` indices be range-checked before wrapping to `i32`?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vera/wasm/calls.py` around lines 876 - 921, The code currently uses
i32.wrap_i64 on start/end before clamping which truncates 64-bit indices; change
the flow to perform all bounds checks and clamps in i64 space first (use
i64.const 0, i64.lt_s, i64.gt_s and compare against arr_len promoted to i64),
ensure "Clamp start" and "Clamp end" logic operates on i64 values (variables
referenced as start_instrs, end_instrs, s, e, arr_len), then call i32.wrap_i64
only after the final clamped i64 values and set the i32 locals; adjust the
generated instructions accordingly so no wrapping happens prior to range checks.
🧹 Nitpick comments (1)
vera/wasm/inference.py (1)

569-574: Keep bare Array handling consistent across the helper predicates.

This branch now treats bare Array as pair-like, but _is_array_type_name() on Lines 560-562 still returns False for the same string. That means callers can now get different answers for the erased array representation depending on which helper they consult.

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

In `@vera/wasm/inference.py` around lines 569 - 574, The helper predicates are
inconsistent: one treats bare "Array" as an array but _is_array_type_name(...)
still returns False for "Array"; update _is_array_type_name to also return True
when type_name == "Array" (in addition to existing checks like
type_name.startswith("Array<")), so both _is_array_type_name and the pair-like
predicate (the function that currently checks type_name == "Array" or
type_name.startswith("Array<")) agree on the erased array representation.
🤖 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/inference.py`:
- Around line 392-394: The index-element inference path for IndexExpr is
incomplete because _infer_index_element_type only handles SlotRef and nested
IndexExprs; update _infer_index_element_type to also recognize CallExpr nodes
(e.g., calls like array_slice(...) and array_map(...)) by inspecting the
inferred return type of the callee/CallExpr and, if that return type is
Array[T], return T as the element type; this ensures IndexExpr over
array-returning calls yields the element type (so downstream arithmetic
inference in the same module sees f64 for float arrays rather than falling back
to i64).
- Around line 471-481: When inferring apply_fn the code currently returns only
the base name (ta.name or ret.name) and discards type arguments, losing
parametrised return types; instead, when alias_te.return_type is an
ast.NamedType and matches alias_map, construct and return a fully substituted
ast.NamedType preserving its type_args (e.g., replace the NamedType with a new
ast.NamedType using the resolved ta or ret plus the appropriate
closure_arg.type_args) so downstream generic rewrites (like the Future<...>
unwrap) still see the inner type; update the branches that reference alias_map,
alias_params, closure_arg.type_args, alias_te.return_type, and ast.NamedType to
return the complete ast.NamedType rather than just the name string.

---

Duplicate comments:
In `@vera/wasm/calls.py`:
- Around line 1001-1005: The branch that returns
self._infer_concat_elem_type(expr.args[0]) for array combinators (expr.name in
("array_concat","array_append","array_slice","array_filter")) fails when the arg
is an array-producing call like array_map; update this logic in the function
handling expr.name to either (A) if expr.args[0] is a call expression (e.g.
expr.args[0].name == "array_map") handle it explicitly by extracting the mapped
element type (using the map's return type), or (B) more generally obtain the
inferred return type of expr.args[0] and, if it is an Array<T>, derive and
return T; ensure this uses the existing _infer_concat_elem_type helper and any
available type inference routine so array_slice(array_map(...)) and
array_filter(array_map(...)) correctly yield the inner element type instead of
None.
- Around line 876-921: The code currently uses i32.wrap_i64 on start/end before
clamping which truncates 64-bit indices; change the flow to perform all bounds
checks and clamps in i64 space first (use i64.const 0, i64.lt_s, i64.gt_s and
compare against arr_len promoted to i64), ensure "Clamp start" and "Clamp end"
logic operates on i64 values (variables referenced as start_instrs, end_instrs,
s, e, arr_len), then call i32.wrap_i64 only after the final clamped i64 values
and set the i32 locals; adjust the generated instructions accordingly so no
wrapping happens prior to range checks.

---

Nitpick comments:
In `@vera/wasm/inference.py`:
- Around line 569-574: The helper predicates are inconsistent: one treats bare
"Array" as an array but _is_array_type_name(...) still returns False for
"Array"; update _is_array_type_name to also return True when type_name ==
"Array" (in addition to existing checks like type_name.startswith("Array<")), so
both _is_array_type_name and the pair-like predicate (the function that
currently checks type_name == "Array" or type_name.startswith("Array<")) agree
on the erased array representation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d60a2813-2f4c-42aa-8623-b22467c9215b

📥 Commits

Reviewing files that changed from the base of the PR and between 402c9d9 and 64460b2.

📒 Files selected for processing (3)
  • scripts/check_skill_examples.py
  • vera/wasm/calls.py
  • vera/wasm/inference.py
✅ Files skipped from review due to trivial changes (1)
  • scripts/check_skill_examples.py

Comment thread vera/wasm/inference.py
Comment on lines +392 to +394
if isinstance(expr, ast.IndexExpr):
elem = self._infer_index_element_type(expr)
return elem

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

IndexExpr inference still misses array-returning calls.

Lines 392-394 now route indexed expressions through _infer_index_element_type(), but that helper only understands SlotRef and nested IndexExpr collections. array_slice(xs, ...)[0] and array_map(f, xs)[0] will therefore still infer as None, even though the new call inference says those functions return Array. That gap leaks into the WASM path too: arithmetic inference on Line 118 falls back to i64 when the indexed value should be f64, so float slices/maps can be compiled with the wrong operator family.

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

In `@vera/wasm/inference.py` around lines 392 - 394, The index-element inference
path for IndexExpr is incomplete because _infer_index_element_type only handles
SlotRef and nested IndexExprs; update _infer_index_element_type to also
recognize CallExpr nodes (e.g., calls like array_slice(...) and array_map(...))
by inspecting the inferred return type of the callee/CallExpr and, if that
return type is Array[T], return T as the element type; this ensures IndexExpr
over array-returning calls yields the element type (so downstream arithmetic
inference in the same module sees f64 for float arrays rather than falling back
to i64).

Comment thread vera/wasm/inference.py
Comment on lines +471 to +481
alias_map = dict(zip(alias_params,
closure_arg.type_args))
ret = alias_te.return_type
if isinstance(ret, ast.NamedType):
if ret.name in alias_map:
ta = alias_map[ret.name]
if isinstance(ta, ast.NamedType):
return ta.name
return ret.name
elif isinstance(alias_te.return_type, ast.NamedType):
return alias_te.return_type.name

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Preserve parametrised return types when inferring apply_fn.

The new branch returns ta.name / ret.name, which strips type arguments. A closure alias returning Future<Int> or Array<String> becomes bare Future / Array, so the Future<...> unwrap on Line 499 cannot fire and downstream generic rewriting loses the inner type as well. Please carry the fully substituted NamedType here rather than only its base name.

🧰 Tools
🪛 Ruff (0.15.6)

[warning] 471-472: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)

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

In `@vera/wasm/inference.py` around lines 471 - 481, When inferring apply_fn the
code currently returns only the base name (ta.name or ret.name) and discards
type arguments, losing parametrised return types; instead, when
alias_te.return_type is an ast.NamedType and matches alias_map, construct and
return a fully substituted ast.NamedType preserving its type_args (e.g., replace
the NamedType with a new ast.NamedType using the resolved ta or ret plus the
appropriate closure_arg.type_args) so downstream generic rewrites (like the
Future<...> unwrap) still see the inner type; update the branches that reference
alias_map, alias_params, closure_arg.type_args, alias_te.return_type, and
ast.NamedType to return the complete ast.NamedType rather than just the name
string.

aallan and others added 2 commits March 19, 2026 17:01
Remove 5 duplicate dictionary keys that caused earlier entries to be
silently overwritten. Verified no duplicates remain via AST analysis.

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.

♻️ Duplicate comments (1)
README.md (1)

587-587: ⚠️ Potential issue | 🟡 Minor

Use “GitHub CI” as the link label for naming consistency.

Line [587] still uses [CI](.github/workflows/ci.yml). Please rename the visible label to GitHub CI while keeping the same target URL.

Suggested wording tweak
-... enforced by pre-commit hooks and [CI](.github/workflows/ci.yml) ...
+... enforced by pre-commit hooks and [GitHub CI](.github/workflows/ci.yml) ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 587, Update the README link label from "CI" to "GitHub CI"
while keeping the same target URL (.github/workflows/ci.yml); locate the
markdown snippet in README.md that reads `[CI](.github/workflows/ci.yml)` and
replace the visible text to `[GitHub CI](.github/workflows/ci.yml)` so the link
target is unchanged but the label matches the project naming convention.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@README.md`:
- Line 587: Update the README link label from "CI" to "GitHub CI" while keeping
the same target URL (.github/workflows/ci.yml); locate the markdown snippet in
README.md that reads `[CI](.github/workflows/ci.yml)` and replace the visible
text to `[GitHub CI](.github/workflows/ci.yml)` so the link target is unchanged
but the label matches the project naming convention.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c2feb0b3-f6d7-4cc9-aedc-b78d6993731e

📥 Commits

Reviewing files that changed from the base of the PR and between d14a346 and 6e05dea.

📒 Files selected for processing (3)
  • README.md
  • scripts/check_readme_examples.py
  • tests/test_readme.py
✅ Files skipped from review due to trivial changes (1)
  • tests/test_readme.py

@aallan

aallan commented Mar 19, 2026

Copy link
Copy Markdown
Owner Author

CodeRabbit Review — Resolution Summary

Fixed (4 findings)

Finding Severity Commit
Duplicate dict keys in check_skill_examples.py (1132, 1294) Major 64460b2
elem_size 8-byte fallback for non-empty slices in calls.py Major 64460b2
None dereference on IfExpr.then_branch.expr in inference.py Minor 64460b2
Duplicate ALLOWLIST keys in check_spec_examples.py (5 pairs) Major d14a346

Not fixing (4 findings)

Finding Severity Reason
"CI" → "GitHub CI" (README.md:587) Minor Cosmetic. The existing text is clear and consistent with other references in the repo.
i32.wrap_i64 before clamping (calls.py:884) Minor Architecturally valid but practically unreachable — Vera Int values used as array indices are always within i32 range. An array with >2^31 elements would exhaust WASM linear memory (2 GB) long before this path triggers.
IndexExpr on array-returning calls (inference.py:394) Major Edge case: array_slice(xs, ...)[0] without binding to a slot first. Vera programs idiomatically bind array results to slots (let @Array<T> = ...) before indexing. No existing test or example uses the inline pattern. Filed mentally for future hardening but not blocking.
apply_fn return type loses type args (inference.py:481) Major By design. _infer_vera_type returns a string name (e.g. "Array", "Int"), not a NamedType. The parametrised type information flows through the separate _infer_fncall_wasm_type_infer_apply_fn_return_type path, which does resolve generic aliases with full substitution. The two inference systems serve different purposes and the string-based one doesn't need type args.

@aallan aallan merged commit f8d63aa into main Mar 19, 2026
12 checks passed
@aallan aallan deleted the feat/array-operations branch March 19, 2026 17:16
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.

WASM inference: _is_pair_type_name misses bare "Array" type name Array operations (map, fold, slice)

1 participant