Add Set<T> collection type (#62, PR 2/3)#336
Conversation
Six operations: set_new, set_add, set_contains, set_remove, set_size, set_to_array. Host-import pattern (Python sets / JS Sets behind opaque i32 handles). Pure functional semantics — set_add and set_remove return new sets. Eq<T> + Hash<T> ability constraints enforced. Shared infrastructure (write helpers, array allocators, type tables) extracted from Map block so both Map and Set can use it independently. New conformance test ch09_set (58 programs, was 57). 17 unit tests (8 checker + 9 codegen). Spec §9.4.2 updated from Future to implemented. SKILL.md, ROADMAP, and all doc counts updated. Also: filed #335 (stdin bug) and added to README Known Bugs. Co-Authored-By: Claude <noreply@anthropic.invalid>
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 first-class Set support across spec, type environment, SMT, WASM lowering/codegen, browser runtime imports, tests and conformance manifest, plus documentation and allowlist updates (conformance count 57→58, built-ins 80→86). Changes
Sequence DiagramsequenceDiagram
participant Source as Source Code
participant TypeEnv as Type Environment
participant CodeGen as Code Generator
participant WasmCtx as WASM Context
participant WasmRun as Browser WASM Runtime
Source->>TypeEnv: resolve `set_*` builtins (forall T where Eq+Hash)
TypeEnv-->>Source: function signatures
Source->>CodeGen: compile_program()
CodeGen->>CodeGen: mark `_set_ops_used` / `_set_imports`
CodeGen->>WasmCtx: lower `set_*` calls (mangled imports, mark alloc)
WasmCtx-->>CodeGen: WAT with `set_*` import declarations
CodeGen-->>WasmRun: instantiate WASM + import table
WasmRun->>WasmRun: register `set_new`, `set_add$e`, `set_contains$e`, `set_remove$e`, `set_size`, `set_to_array$e`
WasmRun->>WasmRun: manage opaque handles → JS Set store, encode/decode elements
Note right of WasmRun: `set_to_array` allocates WASM array and returns (ptr,count)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
A key architectural decision here was extracting shared infrastructure from the Map-only block into a if result.map_ops_used or result.set_ops_used: block. Without this, a program using only Set (no Map) would crash because _VAL_WASM_TYPES, _write_i64, _alloc_array_of_i64, etc. wouldn't be defined. This is the kind of subtle dependency that only surfaces when you test the feature in isolation. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #336 +/- ##
==========================================
+ Coverage 95.40% 95.42% +0.02%
==========================================
Files 45 45
Lines 15626 15892 +266
==========================================
+ Hits 14908 15165 +257
- Misses 718 727 +9 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@ROADMAP.md`:
- Line 60: Update the ROADMAP.md entry that groups Map and Set under "key
constraints" to clarify that Map requires key constraints (e.g., Eq + Hash for
keys) while Set requires element/member constraints; edit the bullet mentioning
"Map and Set collections — requires abilities for key constraints (`Eq + Hash`)"
to explicitly state "Map: key constraints (`Eq + Hash`) and Set: element/member
constraints (`Eq + Hash`)" so the documentation accurately distinguishes
constraints for Map vs Set.
In `@spec/09-standard-library.md`:
- Around line 248-261: Update the generic signatures for the Set operations to
include ability constraints so they match the prose: change every `forall<T>` in
the `set_new`, `set_add`, `set_contains`, `set_remove`, `set_size`, and
`set_to_array` declarations to `forall<T where Eq<T>, Hash<T>>`; likewise ensure
Map operation signatures use `forall<K, V where Eq<K>, Hash<K>>` for map APIs.
Locate the table rows referencing the symbols `set_new`, `set_add`,
`set_contains`, `set_remove`, `set_size`, and `set_to_array` and add the `where
Eq<T>, Hash<T>` constraint to their `forall` clauses (and update Map rows to use
`where Eq<K>, Hash<K>`).
In `@TESTING.md`:
- Line 73: The TESTING.md conformance test count is inconsistent: the table
entry for `test_conformance.py` lists 290 tests while the conformance run
snippet still says “parametrized — 280 tests”; verify the actual number of
parametrized tests in `test_conformance.py` and update all occurrences in
TESTING.md (the table row for `test_conformance.py` and the conformance run
snippet that mentions “parametrized — 280 tests”) so both show the correct,
matching count.
- Around line 53-55: The TESTING.md coverage table entries for `test_checker.py`
and `test_codegen.py` still only mention Map collection even though this PR
added Set tests; update those two summary lines to include "Set" alongside "Map"
(e.g., "Map, Set collection") and verify any other collection mentions in the
same file (such as the `test_verifier.py` row) reflect the new Set coverage so
the Markdown accurately matches the tests added.
In `@tests/test_checker.py`:
- Around line 1626-1697: Test suite is missing a negative test for wrong element
type in sets; add a new test method (e.g., test_set_add_wrong_element_type) in
class TestSetChecker that mirrors the existing good-case tests but calls set_add
with a mismatched element type so the type checker fails. Use _check_err
(similar to test_set_wrong_arity) and assert an error message containing "type";
reference set_new(), set_add(...) and the let binding using `@Set`<Int>.0 to
construct the failing call. Ensure the new test name and body follow the same
style as test_set_new_type_checks and test_set_wrong_arity so it runs with the
rest of tests.
In `@tests/test_codegen.py`:
- Around line 7858-7944: Add tests inside the TestSetCollection class that
verify immutability and empty/zero edge paths for the set builtins: ensure
set_add and set_remove return new handles without mutating the original handle
(e.g., capture an original `@Set` from set_new()/set_add, call set_add/set_remove
to get a new set, then assert original's set_size/set_contains unchanged), add
tests for removing a non-existent element and removing from an empty set
(set_remove on set_new() yields same size 0), test set_contains and set_to_array
on an empty set (contains false/0 and array_length 0), and include a test for
adding a zero-like element if relevant (e.g., adding 0 or empty string) to
ensure edge values are handled; reference the existing helpers/set builtins
set_new, set_add, set_remove, set_contains, set_size, set_to_array and add new
test methods (e.g., test_set_immutability, test_set_remove_from_empty,
test_set_contains_empty, test_set_to_array_empty) to follow the existing test
style.
In `@vera/browser/runtime.mjs`:
- Around line 1269-1271: Replace the undefined readStr calls with the existing
readString function in the wasm host wrappers (e.g. the imports.vera[name]
closure and any set_add/set_contains/set_remove string-specialised paths), and
fix the set_to_array$es -> allocArrayOfStrings usage so it passes a proper array
of JS strings (not the raw mem or four args). Concretely: change readStr(...) to
readString(...) wherever used; when converting a Set to an array
(set_to_array$es), build an array like mem.map(([ptr,len]) =>
readString(ptr,len)) and call allocArrayOfStrings(stringsArray) (matching
allocArrayOfStrings' single-argument signature) so the runtime returns the
populated string array.
- Around line 1277-1279: The code coerces WebAssembly i64 import values to
Number (ns.add(et === "i" ? Number(e) : e)), which loses precision for BigInt
values; update the import handlers used for set operations (the
imports.vera[name] functions that implement set_add, set_contains, set_remove
and set_to_array) to stop converting i64 cases to Number and instead use the raw
BigInt (i.e., remove Number(e) for et === "i" so you add/compare/remove e
directly when et is "i"), and apply the same change to the other three analogous
handlers referenced in the diff.
In `@vera/wasm/calls.py`:
- Around line 7638-7656: The helper _infer_set_elem_from_set_arg incorrectly
assumes any FnCall preserves a set by recursing into expr.args[0]; change it to
only recurse for set-preserving builtins (e.g., set_add, set_remove), and for
other FnCall cases attempt to use the callee/return-type info (the function's
return type) instead of the first-arg fallback; likewise handle ModuleCall by
checking the module call's return type before recursing into arguments (do not
recurse blindly), and return None (bail out) when no reliable return-type or set
element can be determined (examples to keep in mind:
set_to_array(build_strings(flag)) where build_strings' arg is not a set). Ensure
references to _infer_vera_type remain for explicit element extraction when
appropriate.
- Around line 7672-7679: When building the set import signature in the
translators (e.g. the block using elem_type =
self._infer_vera_type(call.args[1]) and et = self._map_wasm_tag(elem_type)
before calling _register_set_import), add a guard so unknown element types do
not default to the "b" ABI: if elem_type is None, try to derive the element type
from the set operand (call.args[0]) by inspecting its Set<T> generic; if that
yields a concrete type use it, otherwise treat the element type as unknown and
return None (do not call _map_wasm_tag/_map_wasm_types or register the import).
Apply the same guard to the other two translator sites that construct set
imports (the blocks around set_add/set_contains at the other two locations).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: f771b7d2-c301-495c-87cd-c47fae8ab635
⛔ Files ignored due to path filters (3)
docs/llms-full.txtis excluded by!docs/**docs/sitemap.xmlis excluded by!docs/**tests/conformance/ch09_set.verais excluded by!**/*.vera
📒 Files selected for processing (26)
CLAUDE.mdCONTRIBUTING.mdREADME.mdROADMAP.mdSKILL.mdTESTING.mdscripts/check_readme_examples.pyscripts/check_skill_examples.pyscripts/check_spec_examples.pyspec/09-standard-library.mdtests/conformance/manifest.jsontests/test_checker.pytests/test_codegen.pytests/test_readme.pyvera/browser/runtime.mjsvera/codegen/api.pyvera/codegen/assembly.pyvera/codegen/compilability.pyvera/codegen/core.pyvera/codegen/functions.pyvera/codegen/modules.pyvera/environment.pyvera/smt.pyvera/wasm/calls.pyvera/wasm/context.pyvera/wasm/inference.py
| `Set<T>` is an unordered collection of unique elements. It requires the `Eq` and `Hash` abilities on `T` (see Section 9.8). Element types must be hashable primitives: `Int`, `Nat`, `Bool`, `Float64`, `String`, `Byte`, or `Unit`. | ||
|
|
||
| `Set<T>` will be an unordered collection of unique elements. It will require the `Eq` and `Hash` abilities on `T` (see Section 9.8). | ||
| Set is an opaque built-in type implemented via host imports. The runtime maintains the underlying set; WASM code interacts with sets through `i32` handles. All operations are pure — `set_add` and `set_remove` return new sets (functional semantics). | ||
|
|
||
| Operations will include union, intersection, difference, membership testing, and size. | ||
| **Operations:** | ||
|
|
||
| | Function | Signature | Description | | ||
| |----------|-----------|-------------| | ||
| | `set_new()` | `forall<T> () → Set<T>` | Create an empty set | | ||
| | `set_add(s, t)` | `forall<T> (Set<T>, T) → Set<T>` | Return a new set with the element added | | ||
| | `set_contains(s, t)` | `forall<T> (Set<T>, T) → Bool` | Test whether an element is present | | ||
| | `set_remove(s, t)` | `forall<T> (Set<T>, T) → Set<T>` | Return a new set without the element | | ||
| | `set_size(s)` | `forall<T> (Set<T>) → Int` | Number of elements | | ||
| | `set_to_array(s)` | `forall<T> (Set<T>) → Array<T>` | All elements as an array | |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check if the spec file exists and read the Set<T> section
head -300 spec/09-standard-library.md | tail -100Repository: aallan/vera
Length of output: 5398
🏁 Script executed:
# Search for where clause usage in spec files to understand Vera's syntax for constraints
rg "where\s+\w+\s*:" spec/ -A 2 -B 2Repository: aallan/vera
Length of output: 37
🏁 Script executed:
# Look for other generic function signatures in the standard library spec
rg "forall<\w+>" spec/09-standard-library.md -B 1 -A 3Repository: aallan/vera
Length of output: 1632
🏁 Script executed:
# Search for documentation on function signature constraints in spec
rg "where\s+\w+" spec/ -B 2 -A 2Repository: aallan/vera
Length of output: 10799
🏁 Script executed:
# Look for generic function definitions in spec chapters (especially type system)
fd "spec.*\.md$" | head -10 | xargs -I {} sh -c 'echo "=== {} ===" && rg "fn\s+\w+.*where" {} -B 1 -A 2'Repository: aallan/vera
Length of output: 37
🏁 Script executed:
# Check Chapter 5 (functions) and Chapter 2 (types) for constraint syntax
head -200 spec/05-functions.md | tail -100Repository: aallan/vera
Length of output: 2411
🏁 Script executed:
# Search for how constraints are expressed with generics in the entire spec
rg "Eq<\w+>|Hash<\w+>" spec/ -B 1 -A 1Repository: aallan/vera
Length of output: 1158
Set and Map operation signatures must express ability constraints in their generic parameters.
The prose correctly states that Set operations require Eq<T> and Hash<T> (and Map requires Eq<K> and Hash<K>), but the operation tables omit these constraints from the forall<...> declarations. Vera supports ability-constrained generics via the syntax forall<T where Eq<T>, Hash<T>>, as documented in Section 9.8 and shown in the contains example. Set operations should read forall<T where Eq<T>, Hash<T>>, and Map operations forall<K, V where Eq<K>, Hash<K>>, to match both the prose requirements and the language's constraint semantics.
Correction for Set operations
| `set_new()` | `forall<T where Eq<T>, Hash<T>> () → Set<T>` | Create an empty set |
| `set_add(s, t)` | `forall<T where Eq<T>, Hash<T>> (Set<T>, T) → Set<T>` | Return a new set with the element added |
| `set_contains(s, t)` | `forall<T where Eq<T>, Hash<T>> (Set<T>, T) → Bool` | Test whether an element is present |
| `set_remove(s, t)` | `forall<T where Eq<T>, Hash<T>> (Set<T>, T) → Set<T>` | Return a new set without the element |
| `set_size(s)` | `forall<T where Eq<T>, Hash<T>> (Set<T>) → Int` | Number of elements |
| `set_to_array(s)` | `forall<T where Eq<T>, Hash<T>> (Set<T>) → Array<T>` | All elements as an array |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@spec/09-standard-library.md` around lines 248 - 261, Update the generic
signatures for the Set operations to include ability constraints so they match
the prose: change every `forall<T>` in the `set_new`, `set_add`, `set_contains`,
`set_remove`, `set_size`, and `set_to_array` declarations to `forall<T where
Eq<T>, Hash<T>>`; likewise ensure Map operation signatures use `forall<K, V
where Eq<K>, Hash<K>>` for map APIs. Locate the table rows referencing the
symbols `set_new`, `set_add`, `set_contains`, `set_remove`, `set_size`, and
`set_to_array` and add the `where Eq<T>, Hash<T>` constraint to their `forall`
clauses (and update Map rows to use `where Eq<K>, Hash<K>`).
| imports.vera[name] = (h, e) => { | ||
| const ns = new Set(setStore.get(h)); | ||
| ns.add(et === "i" ? Number(e) : e); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd /tmp && find . -name "runtime.mjs" -type f 2>/dev/null | head -5Repository: aallan/vera
Length of output: 37
🏁 Script executed:
git ls-files | grep -E "(runtime\.mjs|\.mjs$)" | head -10Repository: aallan/vera
Length of output: 104
🏁 Script executed:
# Search for runtime.mjs in the repository
fd -t f "runtime\.mjs"Repository: aallan/vera
Length of output: 79
🏁 Script executed:
# Check Vera spec or documentation for Set element types
fd -t f -E "node_modules" \( -name "*.md" -o -name "*.spec.*" \)Repository: aallan/vera
Length of output: 285
🏁 Script executed:
# Search for Set-related type restrictions in the codebase
rg "Set.*element.*type\|element.*type.*Set" -iRepository: aallan/vera
Length of output: 37
🏁 Script executed:
wc -l vera/browser/runtime.mjsRepository: aallan/vera
Length of output: 84
🏁 Script executed:
sed -n '1270,1350p' vera/browser/runtime.mjsRepository: aallan/vera
Length of output: 2477
🏁 Script executed:
# Search for Set-related imports and element type handling
rg "Set.*et\|element.*type.*Set" vera/browser/runtime.mjs -A 3 -B 3Repository: aallan/vera
Length of output: 37
🏁 Script executed:
# Look for documentation on Set element types
rg -i "set.*element.*type\|element.*type.*set" --type md --type txtRepository: aallan/vera
Length of output: 37
🏁 Script executed:
# Check spec or documentation files
git ls-files | grep -i "spec\|doc" | head -20Repository: aallan/vera
Length of output: 477
🏁 Script executed:
# Check spec for Set element types
cat docs/vera-hero.jpg 2>/dev/null || fd -t f "spec.*\.md" | head -5Repository: aallan/vera
Length of output: 50367
🏁 Script executed:
# Look for Set definition in spec
rg -i "set.*element\|Set<" spec/ -A 5 -B 2Repository: aallan/vera
Length of output: 37
🏁 Script executed:
# Check codegen for Set element type handling
rg "_define_set_add|_define_set_contains|_define_set_remove" vera/codegen/api.py -A 10 -B 2Repository: aallan/vera
Length of output: 2306
🏁 Script executed:
# Search for how Set element types are determined in codegen
rg "set_add|set_contains|set_remove" vera/codegen/api.py -B 5 -A 10 | head -100Repository: aallan/vera
Length of output: 3021
🏁 Script executed:
# Look for Set type restrictions or documentation
rg "Set.*Int\|Set.*i64\|element.*type" vera/ --type py -B 2 -A 2 | head -80Repository: aallan/vera
Length of output: 37
🏁 Script executed:
# Search spec for Set element type constraints
rg -i "set.*element\|element.*type.*set" spec/ --type md -A 3 -B 1 | head -60Repository: aallan/vera
Length of output: 37
🏁 Script executed:
# Check what et="i" maps to in _VAL_WASM_TYPES
rg "_VAL_WASM_TYPES\s*=" vera/codegen/api.py -A 10Repository: aallan/vera
Length of output: 465
🏁 Script executed:
# Search spec for what types are supported in Sets
rg -i "Set<" spec/ --type md -A 2 -B 1 | head -80Repository: aallan/vera
Length of output: 2694
Keep WebAssembly i64 set elements as BigInt.
WebAssembly passes JS i64 imports as BigInt. Coercing them through Number aliases distinct Vera Int values above 2^53 - 1, so set_add, set_contains, set_remove, and set_to_array silently lose precision or round-trip the wrong values.
Remove the Number(e) coercion for et === "i" cases in all four functions:
Proposed fix
- ns.add(et === "i" ? Number(e) : e);
+ ns.add(e);
return setAlloc(ns);
@@
- const val = et === "i" ? Number(e) : e;
+ const val = e;
return setStore.get(h)?.has(val) ? 1 : 0;
@@
- ns.delete(et === "i" ? Number(e) : e);
+ ns.delete(e);
return setAlloc(ns);
@@
- for (let i = 0; i < count; i++) dv.setBigInt64(ptr + i * 8, BigInt(elems[i]), true);
+ for (let i = 0; i < count; i++) dv.setBigInt64(ptr + i * 8, elems[i], true);Also applies to: 1296–1298, 1316–1318, 1336–1340
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vera/browser/runtime.mjs` around lines 1277 - 1279, The code coerces
WebAssembly i64 import values to Number (ns.add(et === "i" ? Number(e) : e)),
which loses precision for BigInt values; update the import handlers used for set
operations (the imports.vera[name] functions that implement set_add,
set_contains, set_remove and set_to_array) to stop converting i64 cases to
Number and instead use the raw BigInt (i.e., remove Number(e) for et === "i" so
you add/compare/remove e directly when et is "i"), and apply the same change to
the other three analogous handlers referenced in the diff.
| elem_type = self._infer_vera_type(call.args[1]) | ||
| et = self._map_wasm_tag(elem_type) | ||
|
|
||
| params = ["i32"] # set handle | ||
| params.extend(self._map_wasm_types(et)) # element | ||
| wasm_name = self._register_set_import( | ||
| "set_add", et, | ||
| extra_params=params, results=["i32"], |
There was a problem hiding this comment.
Don’t let unknown element types fall through to the b ABI.
_map_wasm_tag(None) becomes "b", so any element expression that _infer_vera_type() cannot classify here is emitted as an i32-based import. With the current inference code, ast.ModuleCall is one concrete case, so set_add(s, Mod.make_string()) / set_contains(s, Mod.make_int()) will register the wrong host signature instead of failing cleanly. Please fall back to the set operand’s Set<T> type, and return None if the element type is still unknown.
Suggested guard for all three translators
- elem_type = self._infer_vera_type(call.args[1])
+ elem_type = (
+ self._infer_vera_type(call.args[1])
+ or self._infer_set_elem_from_set_arg(call.args[0])
+ )
+ if elem_type is None:
+ return None
et = self._map_wasm_tag(elem_type)Also applies to: 7694-7701, 7716-7723
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vera/wasm/calls.py` around lines 7672 - 7679, When building the set import
signature in the translators (e.g. the block using elem_type =
self._infer_vera_type(call.args[1]) and et = self._map_wasm_tag(elem_type)
before calling _register_set_import), add a guard so unknown element types do
not default to the "b" ABI: if elem_type is None, try to derive the element type
from the set operand (call.args[0]) by inspecting its Set<T> generic; if that
yields a concrete type use it, otherwise treat the element type as unknown and
return None (do not call _map_wasm_tag/_map_wasm_types or register the import).
Apply the same guard to the other two translator sites that construct set
imports (the blocks around set_add/set_contains at the other two locations).
- Fix readStr → readString in browser Set bindings (3 occurrences) - Fix allocArrayOfStrings call signature in set_to_array$es - Guard _infer_set_elem_from_set_arg to only recurse into set ops - Add test_set_add_wrong_element_type (checker) - Add 5 codegen tests: immutability (add/remove), absent remove, empty contains, empty to_array - ROADMAP: clarify "key/element constraints" - TESTING.md: add Set to coverage summaries, fix stale 280→290 count - Update all doc counts (2817 tests) Co-Authored-By: Claude <noreply@anthropic.invalid>
|
@coderabbitai All 10 issues addressed in commit b44fc26:
|
|
Tip For best results, initiate chat on the files or code changes.
Everything looks good. ✏️ Learnings added
🧠 Learnings used |
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
vera/wasm/calls.py (1)
7638-7658:⚠️ Potential issue | 🔴 CriticalRecover
Set<T>from helper-returned sets before manglingset_to_array.
_infer_set_elem_from_set_arg()now avoids the blind first-argument recursion, but it still cannot inferTfrom ordinary set-producing calls such asset_to_array(build_strings(flag)).ast.ModuleCallmisses this helper too, because module calls are only flattened later intranslate_expr(). In both cases the helper returnsNone, so_translate_set_to_array()falls through to the"b"import suffix and registers the wrong host ABI for non-i32element layouts.Possible fix
def _infer_set_elem_from_set_arg( self, expr: ast.Expr, ) -> str | None: """Infer the element type T from a Set<T> expression.""" + if isinstance(expr, ast.ModuleCall): + expr = ast.FnCall(name=expr.name, args=expr.args, span=expr.span) + + arg_info = self._get_arg_type_info_wasm(expr) + if arg_info and arg_info[0] == "Set" and arg_info[1]: + return arg_info[1][0] + if isinstance(expr, ast.SlotRef): if expr.type_name == "Set" and expr.type_args: if len(expr.type_args) >= 1: elem_te = expr.type_args[0] if isinstance(elem_te, ast.NamedType): return elem_te.name name = expr.type_name if name.startswith("Set<") and name.endswith(">"): return name[4:-1] if isinstance(expr, ast.FnCall): if expr.name == "set_add" and len(expr.args) >= 2: return self._infer_vera_type(expr.args[1]) if expr.name in ("set_new", "set_add", "set_remove"): if expr.args: return self._infer_set_elem_from_set_arg(expr.args[0]) return NoneAlso applies to: 7751-7756
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vera/wasm/calls.py` around lines 7638 - 7658, _infer_set_elem_from_set_arg currently only handles SlotRef and a few FnCall names, so calls like ast.ModuleCall or general helper FnCalls (e.g., set_to_array(build_strings(flag))) return None and cause wrong ABI selection in _translate_set_to_array; update _infer_set_elem_from_set_arg to also detect ModuleCall and general FnCall return types by (1) handling isinstance(expr, ast.ModuleCall) by inspecting expr.target/expr.name or its flattened inner call to see if it yields a Set<T> and then extracting T, and (2) for general ast.FnCall, if the call's own inferred type (use self._infer_vera_type(expr) or expr.type_name/type_args) indicates a Set<...>, extract and return the element type; still fall back to recursing into the first arg when the function is a set-constructor helper. Ensure these checks reference _infer_set_elem_from_set_arg, _infer_vera_type, ast.ModuleCall, ast.FnCall and that _translate_set_to_array will then get the correct element layout.vera/browser/runtime.mjs (1)
1277-1281:⚠️ Potential issue | 🔴 Critical
Number(e)coercion for i64 elements loses precision for large integers.WebAssembly passes
i64values asBigInt. Converting viaNumber(e)silently truncates values exceeding2^53 - 1. The Map implementation (lines 1154–1155) stores keys directly without coercion—Set should do likewise.The same issue affects
set_contains(line 1297),set_remove(line 1318), andset_to_array(line 1339 reconverts impreciseNumberback toBigInt).Proposed fix
} else { imports.vera[name] = (h, e) => { const ns = new Set(setStore.get(h)); - ns.add(et === "i" ? Number(e) : e); + ns.add(e); return setAlloc(ns); }; }Apply the same change to
set_containsandset_remove:} else { imports.vera[name] = (h, e) => { - const val = et === "i" ? Number(e) : e; + const val = e; return setStore.get(h)?.has(val) ? 1 : 0; }; }} else { imports.vera[name] = (h, e) => { const ns = new Set(setStore.get(h)); - ns.delete(et === "i" ? Number(e) : e); + ns.delete(e); return setAlloc(ns); }; }And for
set_to_array(elements are now stored asBigInt):if (et === "i") { const ptr = alloc(count * 8); const dv = new DataView(mem().buffer); - for (let i = 0; i < count; i++) dv.setBigInt64(ptr + i * 8, BigInt(elems[i]), true); + for (let i = 0; i < count; i++) dv.setBigInt64(ptr + i * 8, elems[i], true); return [ptr, count]; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vera/browser/runtime.mjs` around lines 1277 - 1281, The Set insertion callback (imports.vera[name]) currently coerces i64 elements with Number(e) which loses precision; update it to store the element value directly (leave e as a BigInt when et === "i") by adding e (or Number(e) only for non-i types) into the Set created from setStore.get and then call setAlloc; make the same change in set_contains and set_remove so they compare/remove BigInt values without Number coercion; finally, update set_to_array to return elements as BigInt (don't convert stored BigInt back to Number) so consumers expecting i64 receive the original BigInt values. Ensure you reference setStore, setAlloc, imports.vera[name], set_contains, set_remove, and set_to_array when applying the fixes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/test_codegen.py`:
- Around line 7919-7926: Add tests that exercise the typed Set<String> -> array
codepath by creating a typed empty set and a typed set with string elements and
asserting set_to_array returns the expected lengths/contents; mirror the style
of test_set_to_array_length by adding new test functions (e.g.,
test_set_to_array_length_strings and test_set_to_array_empty_typed_strings) that
call _run on sources using set_new(), set_add(..., "str"), and set_to_array and
assert the results (length and/or element values) to cover the string ABI route
for set_to_array.
---
Duplicate comments:
In `@vera/browser/runtime.mjs`:
- Around line 1277-1281: The Set insertion callback (imports.vera[name])
currently coerces i64 elements with Number(e) which loses precision; update it
to store the element value directly (leave e as a BigInt when et === "i") by
adding e (or Number(e) only for non-i types) into the Set created from
setStore.get and then call setAlloc; make the same change in set_contains and
set_remove so they compare/remove BigInt values without Number coercion;
finally, update set_to_array to return elements as BigInt (don't convert stored
BigInt back to Number) so consumers expecting i64 receive the original BigInt
values. Ensure you reference setStore, setAlloc, imports.vera[name],
set_contains, set_remove, and set_to_array when applying the fixes.
In `@vera/wasm/calls.py`:
- Around line 7638-7658: _infer_set_elem_from_set_arg currently only handles
SlotRef and a few FnCall names, so calls like ast.ModuleCall or general helper
FnCalls (e.g., set_to_array(build_strings(flag))) return None and cause wrong
ABI selection in _translate_set_to_array; update _infer_set_elem_from_set_arg to
also detect ModuleCall and general FnCall return types by (1) handling
isinstance(expr, ast.ModuleCall) by inspecting expr.target/expr.name or its
flattened inner call to see if it yields a Set<T> and then extracting T, and (2)
for general ast.FnCall, if the call's own inferred type (use
self._infer_vera_type(expr) or expr.type_name/type_args) indicates a Set<...>,
extract and return the element type; still fall back to recursing into the first
arg when the function is a set-constructor helper. Ensure these checks reference
_infer_set_elem_from_set_arg, _infer_vera_type, ast.ModuleCall, ast.FnCall and
that _translate_set_to_array will then get the correct element layout.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9812e9d0-7611-43ce-977e-bf0b92023514
📒 Files selected for processing (7)
README.mdROADMAP.mdTESTING.mdtests/test_checker.pytests/test_codegen.pyvera/browser/runtime.mjsvera/wasm/calls.py
Exercise all type tag branches (i/f/s/b) for set_add, set_contains, set_remove, and set_to_array. Brings patch coverage up by covering the previously-uncovered host function closures in api.py. Co-Authored-By: Claude <noreply@anthropic.invalid>
There was a problem hiding this comment.
♻️ Duplicate comments (1)
tests/test_codegen.py (1)
7974-8005:⚠️ Potential issue | 🟡 MinorAdd explicit empty-remove and zero-value Set edge tests.
The new suite is strong, but it still misses two edge paths: removing from an already empty set, and inserting/checking a numeric zero value. These are regression-prone host-import edges and should be pinned.
Proposed additions
+ def test_set_remove_from_empty(self) -> None: + """Removing from an empty set keeps size at 0.""" + source = """ +public fn main(-> `@Int`) + requires(true) ensures(true) effects(pure) +{ set_size(set_remove(set_new(), 1)) } +""" + assert _run(source) == 0 + + def test_set_zero_int_element(self) -> None: + """Zero-valued Int element is handled correctly.""" + source = """ +public fn main(-> `@Int`) + requires(true) ensures(true) effects(pure) +{ if set_contains(set_add(set_new(), 0), 0) then { 1 } else { 0 } } +""" + assert _run(source) == 1As per coding guidelines: “Add codegen/runtime tests in
tests/test_codegen.pywhen adding or modifying built-in functions, covering normal cases, edge cases (empty inputs, zero values), and composition with other built-ins”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/test_codegen.py` around lines 7974 - 8005, Add two codegen/runtime tests to tests/test_codegen.py: one that verifies removing an element from an already-empty set leaves size 0 (e.g., add a test similar to test_set_empty_to_array but calling set_remove(set_new(), 5) and asserting result 0 by calling set_size), and another that verifies zero-value handling by inserting 0 into a set then asserting set_contains returns true and set_size is 1 (or checking set_remove of 0 changes size appropriately). Place tests near the existing set tests (see test_set_remove_absent_element, test_set_empty_contains, test_set_empty_to_array) and follow existing test structure and _run usage so they exercise host-import edges for empty inputs and numeric zero values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@tests/test_codegen.py`:
- Around line 7974-8005: Add two codegen/runtime tests to tests/test_codegen.py:
one that verifies removing an element from an already-empty set leaves size 0
(e.g., add a test similar to test_set_empty_to_array but calling
set_remove(set_new(), 5) and asserting result 0 by calling set_size), and
another that verifies zero-value handling by inserting 0 into a set then
asserting set_contains returns true and set_size is 1 (or checking set_remove of
0 changes size appropriately). Place tests near the existing set tests (see
test_set_remove_absent_element, test_set_empty_contains,
test_set_empty_to_array) and follow existing test structure and _run usage so
they exercise host-import edges for empty inputs and numeric zero values.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 427af35b-249a-46ad-899b-c0ddab2877f1
📒 Files selected for processing (3)
README.mdTESTING.mdtests/test_codegen.py
Exercises the 'f' and 'b' to_array host function paths in api.py, closing the last uncovered Set branch. Co-Authored-By: Claude <noreply@anthropic.invalid>
Co-Authored-By: Claude <noreply@anthropic.invalid>
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 `@README.md`:
- Line 599: The README's hard-coded test count ("2,824 tests") is inconsistent
with the PR status (2,811); update the README.md line containing the sentence
that begins "Testing is organized in three layers: **unit tests**..." to either
reflect the current accurate test count or remove the exact numeric literal and
replace it with a non-hardcoded phrasing (e.g., "≈2,800 unit tests" or
"thousands of unit tests") to avoid churn; ensure the linked TESTING.md
reference and any other counts in README.md are checked for consistency after
the change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 60c693ef-39a6-419a-8f71-7a8b66a6b385
📒 Files selected for processing (3)
README.mdTESTING.mdtests/test_codegen.py
Point to TESTING.md as the single source of truth for exact counts. Eliminates churn from updating README on every test addition. Co-Authored-By: Claude <noreply@anthropic.invalid>
There was a problem hiding this comment.
♻️ Duplicate comments (1)
README.md (1)
599-599:⚠️ Potential issue | 🟡 MinorUpdate the test-count figure on Line 599 to match current PR facts.
The README states 2,826 unit tests, but this PR reports 2,811 tests pass locally. Please sync this value (or avoid hard-coding exact counts to reduce churn).
As per coding guidelines: “**/*.md: Review Markdown files for factual accuracy against the codebase, broken links, and outdated information.”Suggested update
-Testing is organized in three layers: **unit tests** (2,826 tests across 26 files, testing compiler internals and browser parity), a **conformance suite** (58 programs across 9 spec chapters, systematically validating every language feature against the spec), and **example programs** (25 end-to-end demos). The compiler has 96% code coverage, enforced by [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,811 tests across 26 files, testing compiler internals and browser parity), a **conformance suite** (58 programs across 9 spec chapters, systematically validating every language feature against the spec), and **example programs** (25 end-to-end demos). The compiler has 96% code coverage, enforced by [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.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` at line 599, Update the unit test count in README.md (the sentence containing "unit tests (2,826 tests across 26 files...)" to reflect the current PR's local run or remove the hard-coded exact count to avoid churn; specifically change the "2,826" figure to "2,811" to match this PR or rephrase to a non-exact statement like "≈2,800 unit tests across 26 files" in that same sentence so the documentation stays accurate and resilient to future test count changes.
🤖 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 599: Update the unit test count in README.md (the sentence containing
"unit tests (2,826 tests across 26 files...)" to reflect the current PR's local
run or remove the hard-coded exact count to avoid churn; specifically change the
"2,826" figure to "2,811" to match this PR or rephrase to a non-exact statement
like "≈2,800 unit tests across 26 files" in that same sentence so the
documentation stays accurate and resilient to future test count changes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9ccc95cf-50ed-4d86-96fb-9699c2f9e6ff
📒 Files selected for processing (4)
README.mdROADMAP.mdTESTING.mdtests/test_codegen.py
CodeRabbit on #600 caught two stale numbers in the new row: 1. Pin reference said "wasmtime-py 43.0.0 (current pin)"; actual pin in pyproject.toml is "wasmtime>=44.0.0". 43.0.0 was the version *installed* in the local venv (predating a recent re-pin) but not what the codebase requires. 2. "Remove when" condition said "above 43.0.0"; should be "above 44.0.0" because v44.0.0 was tagged before the upstream fix landed (verified: 91b8f149 v44.0.0 is two commits behind 5c84f841 main). No PyPI release currently includes the fix. Reworded the row to make those numbers right and to soften the "Remove when" into a clearer two-step removal sequence (release ships, bump pyproject, then a follow-up PR drops the guard per #599). Did NOT remove the upstream references (#336, #337, commit 5c84f841) that the rabbit flagged as "nonexistent" -- I verified all three exist via gh api and they're load-bearing for understanding the row. The rabbit cannot cross-repo-query bytecodealliance/wasmtime-py from the GitHub Action context, so its "nonexistent" assertion was a default- no-info safe assumption rather than a verified claim. Co-Authored-By: Claude <noreply@anthropic.invalid>
Summary
set_new,set_add,set_contains,set_remove,set_size,set_to_arrayset_addandset_removereturn new setsEq<T> + Hash<T>ability constraints enforcedch09_set(58 programs, was 57)Files changed (29)
Compiler (13 files)
environment.py,smt.py,wasm/calls.py,wasm/context.py,wasm/inference.py,codegen/api.py,codegen/assembly.py,codegen/compilability.py,codegen/core.py,codegen/functions.py,codegen/modules.py,browser/runtime.mjsTests (4 files)
test_checker.py(8 new),test_codegen.py(9 new),ch09_set.vera,manifest.jsonDocs (12 files)
spec/09-standard-library.md,SKILL.md,ROADMAP.md,README.md,TESTING.md,CLAUDE.md,CONTRIBUTING.md, allowlist scripts, site assetsTest plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests
Chores
Bug Fixes