Skip to content

Fix #660 + investigate #661 + release v0.0.148#664

Merged
aallan merged 4 commits into
mainfrom
fix-660-661
May 12, 2026
Merged

Fix #660 + investigate #661 + release v0.0.148#664
aallan merged 4 commits into
mainfrom
fix-660-661

Conversation

@aallan

@aallan aallan commented May 12, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #660 #661.

#660 fix

vera/checker/resolution.py::_resolve_type — added an arity check before the zip(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 a Fix: paragraph suggesting the missing or extra type arguments. Spec ref Chapter 2 §2.4.

$ vera check repro.vera
Type alias `Pair` expects 2 type argument(s) but 1 supplied.

`Pair` is declared with `<A, B>`.  Each use of the alias must supply exactly 2
type argument(s) so the alias body can be fully instantiated.  Without complete
arguments, downstream code generation cannot bind the alias-local type variables
and may leak them into mangled symbol names — see #660.

Fix:
  Supply the missing type argument(s) at every use site of `Pair`, e.g.
  `Pair<Int, Int>`.

See: Chapter 2, Section 2.4 "Type Aliases"

The two defensive comments left in vera/codegen/monomorphize.py::_resolve_arg_fn_shape and vera/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:

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

  1. test_alias_arity_mismatch_too_few_e133@Pair<Int> against Pair<A, B> rejected
  2. test_alias_arity_mismatch_too_many_e133@Single<Int, Bool> against Single<T> rejected
  3. test_alias_zero_args_when_zero_expected_ok — pin the no-false-positive case

tests/test_codegen_modules.py::TestCrossModuleNameCollision661 — 2 new:

  1. test_cross_module_forall_name_shadow_compiles_and_runs — name-shadow fixture compiles + runs
  2. test_suppression_does_not_cross_modules — no [E602] for shared_name despite shadow

Release prep (v0.0.148)

Test plan

  • mypy clean
  • pytest passes (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
  • CI green

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Parameterised type aliases with incorrect argument counts are now rejected at type-check with error E133, showing expected vs supplied counts and suggested fixes.
  • Tests

    • Added tests for alias-arity mismatches and a cross‑module name‑collision regression to protect suppression/scoping invariants.
  • Chores

    • Bumped release to v0.0.148 and updated changelog, release links, README, HISTORY, ROADMAP, TESTING and KNOWN_ISSUES.

Review Change Stack

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>
@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 363f304e-9301-4d2b-8938-ad07364e9682

📥 Commits

Reviewing files that changed from the base of the PR and between 38be9c2 and a55ae40.

📒 Files selected for processing (2)
  • TESTING.md
  • tests/test_codegen_modules.py

📝 Walkthrough

Walkthrough

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

Changes

E133 Implementation and Release

Layer / File(s) Summary
E133 type-alias arity validation implementation
vera/checker/resolution.py, vera/errors.py, tests/test_checker.py
_resolve_named_type now enforces alias type-argument arity and emits E133 with expected/supplied counts; E133 added to ERROR_CODES. Tests cover too-few, too-many, and zero-arg non-parameterised cases.
Cross-module suppression invariant regression test
tests/test_codegen_modules.py
Adds TestCrossModuleNameCollision661 with helper _resolved and two tests verifying imported vs local forall<T> fn shared_name do not cause cross-module template-warning suppression and the fixture runs.
Codegen & monomorphize/wasm comment updates
vera/codegen/core.py, vera/codegen/monomorphize.py, vera/wasm/calls.py
Clarifying comments state that parameterised alias arity mismatches are rejected by the checker (E133) so codegen/monomorphize/wasm paths should not observe mismatched type_args lengths.
Release v0.0.148 version bumps and metadata updates
pyproject.toml, vera/__init__.py, CHANGELOG.md, HISTORY.md, README.md, ROADMAP.md, TESTING.md, KNOWN_ISSUES.md
Bumps package/module version to v0.0.148, adds changelog/HISTORY entry for v0.0.148 (E133), updates changelog compare links, increments test totals (3,815 → 3,820) and refreshes README/ROADMAP/TESTING metrics; removes a resolved known-issues entry.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Suggested labels

compiler, tests, docs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the three main objectives: fixing issue #660 (arity check), investigating/pinning #661 (cross-module suppression), and releasing v0.0.148.
Docstring Coverage ✅ Passed Docstring coverage is 90.91% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-660-661

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

@codecov

codecov Bot commented May 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.05%. Comparing base (97da640) to head (a55ae40).

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           
Flag Coverage Δ
javascript 57.36% <ø> (ø)
python 94.84% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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

📥 Commits

Reviewing files that changed from the base of the PR and between 97da640 and d301f4f.

⛔ Files ignored due to path filters (5)
  • docs/index.html is excluded by !docs/**
  • docs/index.md is excluded by !docs/**
  • docs/llms-full.txt is excluded by !docs/**
  • docs/llms.txt is excluded by !docs/**
  • uv.lock is excluded by !**/*.lock, !uv.lock
📒 Files selected for processing (14)
  • CHANGELOG.md
  • HISTORY.md
  • README.md
  • ROADMAP.md
  • TESTING.md
  • pyproject.toml
  • tests/test_checker.py
  • tests/test_codegen_modules.py
  • vera/__init__.py
  • vera/checker/resolution.py
  • vera/codegen/core.py
  • vera/codegen/monomorphize.py
  • vera/errors.py
  • vera/wasm/calls.py

Comment thread tests/test_checker.py
Comment thread tests/test_codegen_modules.py Outdated
aallan and others added 2 commits May 12, 2026 18:57
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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against 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

📥 Commits

Reviewing files that changed from the base of the PR and between 09daeb9 and 38be9c2.

📒 Files selected for processing (3)
  • TESTING.md
  • tests/test_checker.py
  • tests/test_codegen_modules.py

Comment thread tests/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>
@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown

Tip

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

@aallan — exactly right. Asserting result.ok first means the subsequent assert not shared_warnings carries actual weight: the compilation must have succeeded before the warning-set check is trusted. Without it, a compile failure would silently produce an empty diagnostic list, making the suppression assertion vacuously true. The fix is correct and the structure now mirrors the sibling test cleanly.

@aallan aallan merged commit bc365d2 into main May 12, 2026
23 checks passed
@aallan aallan deleted the fix-660-661 branch May 12, 2026 18:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Type checker accepts wrong-arity SlotRef of parameterised FnType alias

1 participant