Fix #660 + investigate #661 + release v0.0.148#664
Conversation
Closes #660. Investigates and documents #661 (not reachable today). ## #660 — Type-checker accepts wrong-arity parameterised aliases Pre-fix `vera/checker/resolution.py::_resolve_type` silently truncated `zip(alias.type_params, te.type_args)` on length mismatch, leaving alias-local type-vars unsubstituted. Downstream codegen leaked literal alias-local names into mono suffixes (`option_map$Int_B` instead of `option_map$Int_Int`) and the call site referenced a non-existent function-table entry → `unknown table 0: table index out of bounds` at runtime. Surfaced by the #659 multi-agent review (finding F3). ### Fix In `_resolve_type`'s type-alias branch, before substitution, check `len(te.type_args) == len(alias.type_params)`. On mismatch, emit `[E133]` ("Type alias arity mismatch") with: - The alias name + expected/supplied counts - A rationale paragraph explaining the codegen consequence - A `Fix:` paragraph suggesting the missing or extra type arguments (with the alias's declared param names quoted) - Spec reference: Chapter 2, Section 2.4 "Type Aliases" Returns `UnknownType()` so downstream checking continues without cascading errors. ### Defensive comments removed The two defensive comments left in `vera/codegen/monomorphize.py::_resolve_arg_fn_shape` and `vera/wasm/calls.py::_resolve_arg_fn_shape_wasm` in PR #659 (documenting the latent gap) are now trimmed to one-line "arity enforced upstream by checker" cross-references. Their verbose explanation is no longer needed since the gap is closed. ### New tests `tests/test_checker.py::TestResolutionCoverage` gains three tests: 1. `test_alias_arity_mismatch_too_few_e133` — `@Pair<Int>` against `Pair<A, B>` rejected with E133 2. `test_alias_arity_mismatch_too_many_e133` — `@Single<Int, Bool>` against `Single<T>` rejected with E133 (symmetric case) 3. `test_alias_zero_args_when_zero_expected_ok` — non-parameterised alias accepts no type-args (pins the no-false-positive case) ## #661 — investigated; not reachable today; invariant pinned The agent finding flagged a hypothetical scenario: if two modules both declare `forall<T> fn shared_name(...)`, the template-warning suppression set keys on the bare base name and could over-suppress. Investigation found this isn't reachable today because: 1. Pass 2.5 in `compile_program` (lines 519-530) explicitly skips imported FnDecls whose names are already in `fn_visibility` (= local declarations). An imported forall decl with the same name as a local one is dropped before its template warning could be emitted. 2. `forall_decl_names` is built from `program.declarations` only, never from imports. Only local forall decls are eligible for suppression. At most one template warning per base name lands in `self.diagnostics`, so bare-name matching cannot cross-suppress. ### Added an explanatory comment + invariant-pinning tests The `compile_program` suppression block now has a comment explicitly naming the two preconditions that keep bare-name keying safe, and the trigger conditions that would invalidate the invariant (loosened Pass 2.5 dedup, or mono pipeline starting to carry module attribution). `tests/test_codegen_modules.py::TestCrossModuleNameCollision661` adds two tests: 1. `test_cross_module_forall_name_shadow_compiles_and_runs` — the name-shadow fixture compiles and runs (returns 42). 2. `test_suppression_does_not_cross_modules` — no [E602] warning for `shared_name` despite the cross-module shadow. If Pass 2.5's dedup or `forall_decl_names`' scope ever changes, these tests flag it. ## Release prep (v0.0.148) Bundled per the ABSOLUTE RULE: - `vera/__init__.py` + `pyproject.toml` + `uv.lock`: 0.0.147 → 0.0.148 - `CHANGELOG.md`: `[0.0.148] - 2026-05-12` with `### Fixed` (#660) and `### Internal` (#661) entries + link refs - `HISTORY.md` Stage 12 row: v0.0.148 + roll-up counter 147 → 148 - `docs/index.html`: version tag - `README.md` / `TESTING.md` / `ROADMAP.md`: count refreshes (3,820 tests, 148 releases) - `vera/errors.py`: new `E133` entry between `E131` and `E140` ## Verification - mypy clean (59 source files) - pytest: 3,806 passed, 14 skipped (was 3,801 + 5 new tests) - All 86 conformance programs pass - All 34 examples pass `vera check` + `vera verify` - e602 gate: 116 files clean, 5 allowlist matched, 0 stale - version-sync, doc-counts: consistent Refs #604 #659 #660 #661 Co-Authored-By: Claude <noreply@anthropic.invalid>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds an upstream type-alias arity check (E133) in resolution, registers the diagnostic, adds unit and cross-module regression tests, updates explanatory codegen/monomorphize/wasm comments, and bumps release/version metadata to v0.0.148 with updated docs and metrics. ChangesE133 Implementation and Release
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRsSuggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #664 +/- ##
=======================================
Coverage 91.05% 91.05%
=======================================
Files 60 60
Lines 23201 23207 +6
Branches 259 259
=======================================
+ Hits 21125 21131 +6
Misses 2069 2069
Partials 7 7
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/test_checker.py`:
- Around line 5449-5477: The new arity-mismatch tests only match the error
message text; update both test_alias_arity_mismatch_too_few_e133 and
test_alias_arity_mismatch_too_many_e133 to also assert the diagnostic code is
E133: have the test capture the diagnostic returned by the _check_err helper (or
adjust _check_err to return the diagnostic) and add an assertion that
diagnostic.error_code == "E133" (or error_code == "E133") immediately after the
existing message assertion so the tests pin the error code contract.
In `@tests/test_codegen_modules.py`:
- Around line 468-479: The temporary-file fixtures create
NamedTemporaryFile(..., delete=False) without explicit encoding and never remove
the files; update each temp file usage (the NamedTemporaryFile calls that write
`source` and return RM with `file_path=Path(fpath)`) to open with
encoding="utf-8" and wrap the write/parse/transform/return sequence in a
try/finally that calls Path(fpath).unlink(missing_ok=True) to ensure cleanup;
keep the same variables (f, fpath, source, parse_file, transform, RM) so the
logic is unchanged but files are removed on exit.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 99601fbc-0803-4be3-917a-76d43814b659
⛔ Files ignored due to path filters (5)
docs/index.htmlis excluded by!docs/**docs/index.mdis excluded by!docs/**docs/llms-full.txtis excluded by!docs/**docs/llms.txtis excluded by!docs/**uv.lockis excluded by!**/*.lock,!uv.lock
📒 Files selected for processing (14)
CHANGELOG.mdHISTORY.mdREADME.mdROADMAP.mdTESTING.mdpyproject.tomltests/test_checker.pytests/test_codegen_modules.pyvera/__init__.pyvera/checker/resolution.pyvera/codegen/core.pyvera/codegen/monomorphize.pyvera/errors.pyvera/wasm/calls.py
PR #663 (v0.0.147) closed #628 but the matching row in KNOWN_ISSUES.md's Bugs table was missed in that PR's release prep. Removed now while #664 is still open. Other surfaces verified clean for #628 / #660 / #661: - `ROADMAP.md`: no burn-down rows for these (already swept) - `vera/README.md`: no mentions - `spec/*.md`: no mentions - `examples/*.vera`: no workaround code - `tests/conformance/*.vera`: no workaround code - `tests/test_codegen_modules.py` / `tests/test_checker.py`: references are the new regression tests for #628 / #660 / #661 — intentional and kept Limitation count: 34 → 33. Refs #628 #660 #661 #664 Co-Authored-By: Claude <noreply@anthropic.invalid>
CR-1 (`tests/test_checker.py:5477`) — assert error code directly The two new arity-mismatch tests for `#660` only matched the error message text via `_check_err`'s substring check. Now also capture the diagnostic list and assert at least one has `error_code == "E133"`. Pins the code contract independently of the text — a future refactor that re-routes through a sibling diagnostic with matching text but a different code would still be caught. CR-2 (`tests/test_codegen_modules.py:479`) — tempfile hygiene All three new tempfile sites in `TestCrossModuleNameCollision661` (the `_resolved` static helper + two inline test-body sites) gain: - `encoding="utf-8"` explicit on `NamedTemporaryFile` (Windows- portability per project memory: `PYTHONUTF8=1` is the CI backstop; explicit encoding is portable to local Windows shells without it). - `try/finally Path(fpath).unlink(missing_ok=True)` cleanup after parse + transform. Safe because `vera/codegen/api.py::compile` works off the in-memory `source` string + AST, not by re-reading the file path after parse. Verified by inspecting `compile_program`'s consumers of `self.file` (used only as a diagnostic label, never re-read). Older tempfile sites in `TestCrossModuleCodegen` (lines 37, 188, 211) keep the prior `delete=False`-no-cleanup pattern — those pre-date this PR and the fix-for-consistency would be scope creep here. Worth a separate hygiene PR. Validation - pytest tests/test_checker.py::TestResolutionCoverage -k alias -v → 4 passed - pytest tests/test_codegen_modules.py::TestCrossModuleNameCollision661 -v → 2 passed Refs #660 #661 #664 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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/test_codegen_modules.py`:
- Around line 592-609: The test currently only inspects filtered warnings and
can pass even if compilation failed; update the assertion after calling
compile(...) to explicitly assert compilation succeeded by checking result.ok is
True (and/or that there are no errors in result.diagnostics) before continuing
to inspect warnings; add this explicit check immediately after the compile(...)
call (before computing warnings/shared_warnings) so the test fails if compile
produces errors rather than silently passing.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: ed7a59f3-df1f-4135-b6d9-e5c19686dcaa
📒 Files selected for processing (3)
TESTING.mdtests/test_checker.pytests/test_codegen_modules.py
CR-3 (`tests/test_codegen_modules.py:592`) — guard against silent pass-on-failure. `test_suppression_does_not_cross_modules` inspected only the filtered `shared_warnings` list. If `compile()` errored out, the warning list would be empty and the `assert not shared_warnings` would silently pass — defeating the test's intent (which is to verify the suppression filter doesn't drop a legitimate cross- module warning). Fix: assert `result.ok` immediately after the `compile(...)` call, before computing the warning subsets. Mirrors the explicit error check already present in the sibling `test_cross_module_forall_name_shadow_compiles_and_runs` test (lines 536-540). Validation - pytest tests/test_codegen_modules.py::TestCrossModuleNameCollision661 -v → 2 passed Refs #661 #664 Co-Authored-By: Claude <noreply@anthropic.invalid>
|
Tip For best results, initiate chat on the files or code changes.
|
Summary
@Pair<Int>againstPair<A, B>) with[E133]("Type alias arity mismatch") instead of silently truncatingzip()and producing a runtimeunknown table 0: table index out of boundstrap.forall_decl_namestogether prevent cross-module suppression collisions). Adds an explanatory comment and pinning tests so a future regression in either invariant flags itself.Closes #660 #661.
#660 fix
vera/checker/resolution.py::_resolve_type— added an arity check before thezip(alias.type_params, te.type_args)substitution. On mismatch, emit[E133]with the alias name, expected/supplied counts, a rationale paragraph explaining the codegen consequence, and aFix:paragraph suggesting the missing or extra type arguments. Spec ref Chapter 2 §2.4.The two defensive comments left in
vera/codegen/monomorphize.py::_resolve_arg_fn_shapeandvera/wasm/calls.py::_resolve_arg_fn_shape_wasm(from PR #659's review documenting the latent gap) are trimmed to one-line "arity enforced upstream by checker" cross-references — the verbose explanation is no longer needed.#661 investigation
Original framing: "if two modules both declare
forall<T> fn shared_name(...), the suppression set keys on the bare base name and could over-suppress." Investigation found this isn't reachable today because:compile_program(lines 519-530) explicitly skips imported FnDecls whose names are already infn_visibility(= local declarations). An imported forall decl with the same name as a local one is dropped before its template warning could be emitted.forall_decl_namesis built fromprogram.declarationsonly, never from imports. Only local forall decls are eligible for suppression.At most one template warning per base name lands in
self.diagnostics, so bare-name matching cannot cross-suppress.Added an explanatory comment at the suppression site naming both invariants and the trigger conditions that would invalidate them. Added two pinning tests in
TestCrossModuleNameCollision661.Regression tests
tests/test_checker.py::TestResolutionCoverage— 3 new:test_alias_arity_mismatch_too_few_e133—@Pair<Int>againstPair<A, B>rejectedtest_alias_arity_mismatch_too_many_e133—@Single<Int, Bool>againstSingle<T>rejectedtest_alias_zero_args_when_zero_expected_ok— pin the no-false-positive casetests/test_codegen_modules.py::TestCrossModuleNameCollision661— 2 new:test_cross_module_forall_name_shadow_compiles_and_runs— name-shadow fixture compiles + runstest_suppression_does_not_cross_modules— no [E602] forshared_namedespite shadowRelease prep (v0.0.148)
vera/__init__.py+pyproject.toml+uv.lock: 0.0.147 → 0.0.148CHANGELOG.md:[0.0.148] - 2026-05-12heading + link refs (Fixed entry for Type checker accepts wrong-arity SlotRef of parameterised FnType alias #660, Internal entry for compiled_mono_bases cross-module name-collision in template-warning suppression #661)HISTORY.mdStage 12 row + roll-up counter 147 → 148docs/index.html: version tagREADME.md/TESTING.md/ROADMAP.md: count refreshesvera/errors.py: newE133entry betweenE131andE140Test plan
vera check+vera verify🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests
Chores