Add array_slice, array_map, array_filter, array_fold (v0.0.91)#319
Conversation
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>
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds 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
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
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 📝 Coding Plan
Comment |
There was a problem hiding this comment.
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 | 🔴 CriticalDuplicate dictionary keys will silently drop allowlist entries, and multiple line numbers are incorrect.
Five duplicate keys in
ALLOWLISTwill 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.
Usingarray_map,array_filter,array_fold, andarray_slicehere 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_mapchanging 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_namesAlso 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 addingstrict=Truetozipfor defensive programming.While the invariant that
forall_varsandconcrete_typeshave equal length is maintained by the call chain, addingstrict=Truewould 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
zipon line 469 is safe. However, addingstrict=Truewould 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
📒 Files selected for processing (21)
CHANGELOG.mdREADME.mdROADMAP.mdSKILL.mdTESTING.mddocs/index.htmlpyproject.tomlscripts/check_skill_examples.pyscripts/check_spec_examples.pyspec/04-expressions.mdspec/09-standard-library.mdtests/test_codegen_monomorphize.pytests/test_prelude.pyvera/README.mdvera/__init__.pyvera/codegen/modules.pyvera/codegen/monomorphize.pyvera/environment.pyvera/prelude.pyvera/wasm/calls.pyvera/wasm/inference.py
| ### 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. |
There was a problem hiding this comment.
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.
| 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)".
| # 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}") |
There was a problem hiding this comment.
🧩 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) →0x7FFFFFFF→2147483647
Sources: [1] (developer.mozilla.org), [2] (webassembly.github.io)
Citations:
- 1: https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Numeric/Wrap?utm_source=openai
- 2: https://webassembly.github.io/spec/versions/core/WebAssembly-2.0.pdf
🏁 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
fiRepository: 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 -nRepository: 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.
- 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>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@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
📒 Files selected for processing (2)
scripts/check_spec_examples.pyspec/09-standard-library.md
🚧 Files skipped from review as they are similar to previous changes (1)
- spec/09-standard-library.md
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
vera/wasm/calls.py (2)
1001-1005:⚠️ Potential issue | 🟠 Major
_infer_concat_elem_typestill rejectsarray_map(...)results.This helper now knows about
array_sliceandarray_filter, butarray_slice(array_map(xs, f), ...)still bottoms out at Line 845 and returnsNone. Please deriveArray<T>from the expression's inferred return type here, or handlearray_mapexplicitly, 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 | 🟠 MajorClamp indices in
i64space before wrapping.Line 876 and Line 881 still truncate the user-supplied
Intindices before any bounds check. Values like1 << 33will wrap into unrelatedi32values, 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 bareArrayhandling consistent across the helper predicates.This branch now treats bare
Arrayas pair-like, but_is_array_type_name()on Lines 560-562 still returnsFalsefor 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
📒 Files selected for processing (3)
scripts/check_skill_examples.pyvera/wasm/calls.pyvera/wasm/inference.py
✅ Files skipped from review due to trivial changes (1)
- scripts/check_skill_examples.py
| if isinstance(expr, ast.IndexExpr): | ||
| elem = self._infer_index_element_type(expr) | ||
| return elem |
There was a problem hiding this comment.
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).
| 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 |
There was a problem hiding this comment.
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.
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>
There was a problem hiding this comment.
♻️ Duplicate comments (1)
README.md (1)
587-587:⚠️ Potential issue | 🟡 MinorUse “GitHub CI” as the link label for naming consistency.
Line [587] still uses
[CI](.github/workflows/ci.yml). Please rename the visible label toGitHub CIwhile 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
📒 Files selected for processing (3)
README.mdscripts/check_readme_examples.pytests/test_readme.py
✅ Files skipped from review due to trivial changes (1)
- tests/test_readme.py
CodeRabbit Review — Resolution SummaryFixed (4 findings)
Not fixing (4 findings)
|
Summary
array_slice(WASM intrinsic),array_map(generic A→B),array_filter(predicate),array_fold(left fold with accumulator)Closes #133
Fixes #313, #314, #315, #316, #317, #318
Implementation
vera/environment.pyvera/prelude.pyvera/wasm/calls.pyarray_sliceWASM intrinsic (memcopy with clamping)vera/wasm/inference.pyvera/codegen/monomorphize.pyvera/codegen/modules.pyarray_slicein known namesBug fixes
A→Int, B→Int,@Array<A>.0and@Array<B>.0now correctly reindex to@Array<Int>.1and@Array<Int>.0_is_pair_type_name("Array")now returns True_infer_vera_typehandles indexed elements and conditionals_infer_fncall_vera_typeresolves closure return via alias substitution_get_arg_type_infoextracts types from array literals and function callsDocumentation
Test plan
pytest tests/ -v— 2,390 tests pass (15 new)mypy vera/— cleanpython scripts/check_conformance.py— 55 conformance programs passpython scripts/check_examples.py— 25 examples passpython scripts/check_version_sync.py— version in syncpython scripts/check_doc_counts.py— counts matchpython scripts/check_spec_examples.py— spec blocks passpython scripts/check_skill_examples.py— SKILL blocks passpython scripts/check_readme_examples.py— README blocks passpython scripts/check_faq_examples.py— FAQ blocks passpython scripts/check_html_examples.py— HTML blocks passpython scripts/check_limitations_sync.py— tables in sync🤖 Generated with Claude Code