Skip to content

Chapter 8 module conformance + fix module-qualified-call soundness bug (#679, #814)#816

Merged
aallan merged 18 commits into
mainfrom
feat/679-ch08-conformance
Jun 28, 2026
Merged

Chapter 8 module conformance + fix module-qualified-call soundness bug (#679, #814)#816
aallan merged 18 commits into
mainfrom
feat/679-ch08-conformance

Conversation

@aallan

@aallan aallan commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #679. Closes #814.

Chapter 8 (modules) was the only spec chapter with no chNN_*.vera conformance coverage. It now has eight programs, one per section. Writing them to the distinguishing spec behaviour surfaced — and this PR fixes — a real module-resolution soundness bug (#814).

Program § level kind
ch08_module_decl 8.2 run module declaration
ch08_import_wildcard 8.3.1 run wildcard import (bare calls)
ch08_import_selective 8.3.2 run selective import
ch08_visibility_public 8.4 run public is importable
ch08_visibility_private 8.4 check negativeexpected_error: E150
ch08_shadowing 8.5.2 run local shadows a bare call
ch08_module_qualified_call 8.5.3 run m::f bypasses a local shadow
ch08_circular_import 8.6.3 check negativeexpected_error: E011

Two substrates the issue assumed but that didn't exist (both added here)

#679 said the negative test should "assert the expected E-code … mirroring the pattern used for other negative conformance tests." Neither held, so acceptance needed two enabling pieces:

  1. Stable E-codes for module-resolution diagnostics (the every-diagnostic-has-an-E-code invariant in DESIGN.md): E011 circular import, E012 cannot resolve import, E013 error parsing an imported module (resolver.py), E150 cannot import a private declaration (checker/modules.py). Unit-tested in test_resolver.py.
  2. Negative-test support in the existing harness (no parallel runner existed): a manifest entry may carry expected_error: <code>; check_conformance.py and test_conformance.py then assert the program fails check with that code (via --json) instead of passing.

The soundness bug the coverage surfaced (#814, fixed here)

ch08_module_qualified_call and ch08_shadowing were initially watered down to dodge #814. Written to the distinguishing spec behaviour they fail — so, per the test-first discipline, they (and matching normal-harness tests) were made to fail first, then the bug was fixed:

  • Bug A — a module-qualified call resolved to a local shadow at codegen (spec §8.5.3). Codegen desugared ModuleCall to a bare FnCall, dropping the module path, so m::f invoked a same-named local — while the verifier resolved it correctly via its per-module registry. An unsound verifier↔runtime desync: vera verify proved a postcondition against the module's contract that the runtime then violated (verify OK, run traps). Fix: shadowed module functions are emitted under a distinct, collision-free mod$… WASM name ($ is illegal in Vera identifiers, like the monomorphizer's name$T); a (module path, name) → target table routes qualified calls to it, bare calls still resolve to the local (§8.5.2). A bare intra-module call inside a qualified-reached mod$ body is likewise redirected to the module's sibling (the residual the review found). Generic (forall) module fns are excluded — cross-module generic monomorphisation is separately unimplemented (Imported generics are not monomorphized by the importer (cross-module generic call crashes at run time) #774).

Pinned by a verifier↔codegen differential (fails if either side regresses), a codegen run-value test, an intra-module-sibling test, and a wildcard/coexistence test.

Corrected diagnosis (measured, not assumed)

#814 was filed listing two module bugs. Re-investigating against the actual resolution paths corrected this:

  • "Bug B" (bare-call import shadowing) does not exist. With a non-builtin name, a bare call to a locally-shadowed import already resolves to the local in both the verifier and codegen (§8.5.2). ch08_shadowing now proves this sharply (it passes). The failure originally attributed to Bug B was a fixture artifact: abs is special-cased as a built-in by name in the verifier.
  • That abs artifact surfaced a separate, unrelated soundness bug — the verifier models abs/min/max by name and ignores a shadowing user definition (verify proves a false postcondition, run traps). Filed as Redefining any built-in function should be a checker error (one canonical form; silently unsound for abs/min/max) #815 and recorded in KNOWN_ISSUES; out of scope here (different subsystem + a design fork).

Exit checklist

To sanity-check before merge

  1. The mod$… mangling collision-safety claim ($ illegal in Vera identifiers) and the intra-module-sibling redirection (C2).
  2. The scope call on Redefining any built-in function should be a checker error (one canonical form; silently unsound for abs/min/max) #815 — that the builtin-shadowing soundness bug is genuinely separable (different subsystem; design fork between a checker reject and a verifier defer-to-user).
  3. The expected_error harness addition is minimal and integrated into the existing manifest/runner.

Closes #679.
Closes #814.

Summary by CodeRabbit

  • New Features
    • Expanded the conformance suite to 101 programs, with support for negative expectations via expected_error.
    • Added stable module-resolution/visibility diagnostics: E011 / E012 / E013 / E150.
  • Bug Fixes
    • Fixed module-qualified calls so verifier and code generation agree when locals shadow imported names, including collision-free module WASM naming and correct result handling.
  • Tests / Documentation
    • Added Chapter 8 module regression coverage, updated conformance/testing docs and counts, refined diagnostic spec_ref text, and bumped the package version to 0.0.184.

T and others added 2 commits June 28, 2026 08:28
…arness + module E-codes

The last spec chapter without chNN_*.vera coverage now has eight programs, one
per section: module_decl (8.2), import_wildcard (8.3.1), import_selective
(8.3.2), visibility_public (8.4), visibility_private (8.4, negative), shadowing
(8.5.2), module_qualified_call (8.5.3), circular_import (8.6.3, negative).
Companions reuse tests/conformance/vera/math.vera (extended with a private
helper) plus a cycle pair.

The issue assumed two substrates that did not exist; both are added here:
- Module-resolution diagnostics now carry stable E-codes (the every-diagnostic-
  has-a-code invariant from DESIGN.md): E011 circular import, E012 cannot
  resolve import, E013 error parsing an imported module (resolver.py), and E150
  cannot import a private declaration (checker/modules.py).
- Negative-test support in the conformance harness: a manifest entry may declare
  `expected_error: <code>`, and check_conformance.py / test_conformance.py then
  assert the program FAILS check with that code (E011 / E150) instead of
  passing.

Writing the programs surfaced two pre-existing module name-resolution bugs,
filed as #814 (out of this coverage PR's scope): module-qualified calls do not
bypass a local shadow at codegen, and the verifier and codegen disagree on
bare-call shadowing. The shadowing and qualified-call programs are written to
the correctly-implemented spec surface and avoid those paths.

Conformance 93 -> 101. Release prep for v0.0.184.

Closes #679.

Co-Authored-By: Claude <[email protected]>
Per spec §8.5.3 a qualified call `m::f` resolves to the module's function
even when a local `f` shadows the bare name. Codegen desugared ModuleCall to
a bare FnCall, dropping the module path, so `m::f` invoked the shadowing
local — while the verifier resolved it correctly via its per-module registry.
An unsound verifier<->runtime desync: `vera verify` proved a postcondition
against the module's contract that the runtime then violated.

Shadowed module functions are now emitted under a distinct, collision-free
`mod$...` WASM name (`$` is illegal in Vera identifiers, like the
monomorphizer's `name$T`); a (module path, name) -> target table routes
qualified calls to it, while bare calls still resolve to the local (§8.5.2,
unchanged). A bare intra-module call inside a qualified-reached `mod$` body
is likewise redirected to the module's sibling (the C2 residual the
verifier<->codegen review found). Generic (`forall`) module fns are excluded
(cross-module generic monomorphisation is separately unimplemented, #774).

Investigating corrected the #814 diagnosis: the reported bare-call "Bug B"
does not exist (§8.5.2 works for non-builtin names); the `abs` symptom was
the verifier modelling the built-in by name, filed separately as #815.

Tests: sharpened ch08_module_qualified_call + ch08_shadowing, a codegen
run-value test, an intra-module-sibling (C2) test, a wildcard/coexistence
test, a §8.5.2 verifier regression test, and a verifier<->codegen
differential. Conformance 101, full suite green, mypy + ruff clean.

Closes #814.

Co-Authored-By: Claude <[email protected]>
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Chapter 8 conformance programs, negative-test harness support, stable module diagnostics, and module-qualified call codegen and verifier coverage for shadowing. Version, release, and project documentation counts are updated accordingly.

Changes

Chapter 8 conformance and stable diagnostics

Layer / File(s) Summary
Error code registry and emission
vera/errors.py, vera/resolver.py, vera/checker/modules.py, vera/checker/calls.py, vera/checker/registration.py
Adds E011–E013 and E150, and wires those codes plus updated visibility spec_ref text onto resolver and checker diagnostics.
Negative conformance handling and resolver tests
scripts/check_conformance.py, tests/test_conformance.py, tests/test_resolver.py
Adds manifest-driven negative-case handling by running vera check --json, extracting error_codes, and asserting the expected failure outcome; resolver tests assert E011, E012, and E013.
Chapter 8 manifest and release docs
tests/conformance/manifest.json, pyproject.toml, vera/__init__.py, AGENTS.md, CLAUDE.md, FAQ.md, TESTING.md, ROADMAP.md, README.md, CHANGELOG.md, HISTORY.md, KNOWN_ISSUES.md, CONTRIBUTING.md
Adds Chapter 8 conformance entries and updates version metadata, release notes, suite counts, roadmap text, FAQ, testing docs, and issue notes for the new release.

Module-qualified call codegen under local shadowing

Layer / File(s) Summary
WasmContext and call translation
vera/wasm/context.py, vera/wasm/calls.py
Adds module-qualified target and intra-module rename maps to WasmContext, resolves ast.ModuleCall through the qualified-target map, and redirects bare calls inside emitted module bodies through intra-module renames.
Cross-module qualified-name wiring
vera/codegen/modules.py, vera/codegen/functions.py, vera/codegen/core.py
Collects local shadowed names, assigns mod$...$... WASM names, records qualified-call targets, and passes per-module rename maps into function compilation.
Shadowed function emission
vera/codegen/core.py
Adds new per-module state and emits shadowed FnDecls in a separate pass using dataclasses.replace and module-specific renames.
Codegen and verifier shadowing regressions
tests/test_codegen_modules.py, tests/test_verifier.py
Adds codegen tests for qualified-call shadowing behaviour and verifier tests for imported postconditions and local-shadow contracts.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

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 clearly summarises the Chapter 8 conformance expansion and module-qualified-call fix, and references both linked issues.
Linked Issues check ✅ Passed The PR adds the Chapter 8 conformance programs, negative check coverage, stable module-resolution E-codes, and the shadowing/qualified-call fix.
Out of Scope Changes check ✅ Passed The changes stay focused on the Chapter 8 conformance work, module-resolution fix, and supporting docs/tests; no unrelated code paths were added.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/679-ch08-conformance

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

@codecov

codecov Bot commented Jun 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.00000% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.06%. Comparing base (daf1cb9) to head (b826cba).

Files with missing lines Patch % Lines
vera/resolver.py 73.33% 4 Missing ⚠️
vera/codegen/modules.py 95.83% 2 Missing ⚠️
vera/codegen/core.py 93.75% 1 Missing ⚠️
vera/wasm/context.py 93.75% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #816      +/-   ##
==========================================
+ Coverage   92.04%   92.06%   +0.01%     
==========================================
  Files          89       89              
  Lines       26934    27020      +86     
  Branches      321      321              
==========================================
+ Hits        24791    24875      +84     
- Misses       2135     2137       +2     
  Partials        8        8              
Flag Coverage Δ
javascript 65.33% <ø> (ø)
python 95.09% <92.00%> (+<0.01%) ⬆️

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

☔ View full report in Codecov by Harness.
📢 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: 3

🤖 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 `@TESTING.md`:
- Around line 9-11: The conformance counts in TESTING.md are stale and
internally inconsistent with tests/test_conformance.py. Update every remaining
summary in the conformance section, including the “93 small, focused programs”
wording, the old four check-level program list, and the total skip count, so
they match the current 6 check-level and 8 verify-level entries and the implied
20 skips. Make sure all related occurrences in the file are changed together so
the section is factually consistent end to end.

In `@tests/test_resolver.py`:
- Around line 459-486: These resolver tests are still filtering diagnostics by
description text instead of validating the stable error_code contract. Update
the assertions in the test methods around the _errors helper to check the
returned codes directly for the relevant fixtures, and avoid relying on
substrings like “Circular import”, “Cannot resolve import”, or “Error parsing
imported module”. Keep the existing E011, E012, and E013 expectations tied to
the corresponding test cases so the checks remain resilient to wording changes
in vera/resolver.py.

In `@vera/codegen/modules.py`:
- Around line 214-223: The mangled module-target path in modules.py is only
copying _fn_sigs, but _translate_call() also depends on _fn_nat_params for the
rewritten target. Update the shadowed-module handling around
_module_qualified_wasm_name and _module_intra_renames to also propagate the bare
function’s _fn_nat_params entry to the mangled name, so intra-module calls still
get the `@Int` -> `@Nat` runtime guard.
🪄 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: 4999e269-dee3-4cf2-9259-836ee382a3c2

📥 Commits

Reviewing files that changed from the base of the PR and between daf1cb9 and 2f5c733.

⛔ Files ignored due to path filters (16)
  • 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/**
  • tests/conformance/ch08_circular_import.vera is excluded by !**/*.vera
  • tests/conformance/ch08_import_selective.vera is excluded by !**/*.vera
  • tests/conformance/ch08_import_wildcard.vera is excluded by !**/*.vera
  • tests/conformance/ch08_module_decl.vera is excluded by !**/*.vera
  • tests/conformance/ch08_module_qualified_call.vera is excluded by !**/*.vera
  • tests/conformance/ch08_shadowing.vera is excluded by !**/*.vera
  • tests/conformance/ch08_visibility_private.vera is excluded by !**/*.vera
  • tests/conformance/ch08_visibility_public.vera is excluded by !**/*.vera
  • tests/conformance/vera/cycle_a.vera is excluded by !**/*.vera
  • tests/conformance/vera/cycle_b.vera is excluded by !**/*.vera
  • tests/conformance/vera/math.vera is excluded by !**/*.vera
  • uv.lock is excluded by !**/*.lock, !uv.lock
📒 Files selected for processing (25)
  • AGENTS.md
  • CHANGELOG.md
  • CLAUDE.md
  • FAQ.md
  • HISTORY.md
  • KNOWN_ISSUES.md
  • README.md
  • ROADMAP.md
  • TESTING.md
  • pyproject.toml
  • scripts/check_conformance.py
  • tests/conformance/manifest.json
  • tests/test_codegen_modules.py
  • tests/test_conformance.py
  • tests/test_resolver.py
  • tests/test_verifier.py
  • vera/__init__.py
  • vera/checker/modules.py
  • vera/codegen/core.py
  • vera/codegen/functions.py
  • vera/codegen/modules.py
  • vera/errors.py
  • vera/resolver.py
  • vera/wasm/calls.py
  • vera/wasm/context.py

Comment thread TESTING.md Outdated
Comment thread tests/test_resolver.py Outdated
Comment thread vera/codegen/modules.py Outdated
- TESTING.md: finish the conformance-count sweep the coverage commit left
  stale — 93 -> 101 programs, the check-level list (now six, incl. the two
  ch08 negatives), and the skipped-test count 16 -> 20 with the ch08 rows.
- test_resolver.py: assert the resolver E-codes (E011/E012/E013) directly
  instead of filtering diagnostics by description substring, so a wording
  tweak in resolver.py can't make the tests brittle.
- codegen/modules.py: mirror the @Nat-parameter guard bitmap onto the
  mangled mod$ name, so a qualified call to a shadowed module fn with a
  @nat parameter still emits the call-site narrowing guard.

Refs #814, #679.

Co-Authored-By: Claude <[email protected]>

@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

Caution

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

⚠️ Outside diff range comments (1)
vera/codegen/modules.py (1)

198-203: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Skip forall declarations before queuing imported bodies.

Line 198 queues the imported declaration, and Lines 199-201 queue its where-functions, before Line 202 excludes generics. That means cross-module generic declarations still enter _imported_fn_decls, despite this block documenting them as unimplemented and excluded. Move the guard before the append.

Proposed fix
-                self._imported_fn_decls.append(tld.decl)
-                if tld.decl.where_fns:
-                    for wfn in tld.decl.where_fns:
-                        self._imported_fn_decls.append(wfn)
                 if tld.decl.forall_vars:
                     continue
+                self._imported_fn_decls.append(tld.decl)
+                if tld.decl.where_fns:
+                    for wfn in tld.decl.where_fns:
+                        self._imported_fn_decls.append(wfn)
🤖 Prompt for 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.

In `@vera/codegen/modules.py` around lines 198 - 203, The imported-declaration
queue in the module import path is adding generic `forall` declarations before
they are filtered, so cross-module generics still get stored in
`_imported_fn_decls`. Update the logic in the import handling block around
`tld.decl`, `where_fns`, and `forall_vars` so the `forall` guard runs first, and
only append the declaration and any `where_fns` when the declaration is not
generic.
🤖 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 `@TESTING.md`:
- Line 107: Update the conformance-suite description in TESTING.md to remove the
stale “imports nothing” statement, since the suite now includes import/module
cases added in Chapter 8. Revise the sentence near the conformance suite
overview so it still accurately describes `tests/conformance/` while keeping the
special case for `ch07_cross_module_contracts.vera` and its library fixture.
- Line 134: Update the TESTING.md program classification so the negative-test
attribution is correct: in the section describing check-level programs, make
clear that ch08_circular_import and ch08_visibility_private are the two
expected_error negative tests, and that ch09_http and ch09_inference are only
environment-gated check-level programs. Keep the rest of the run/check/verify
breakdown consistent with the existing program names in this paragraph.

---

Outside diff comments:
In `@vera/codegen/modules.py`:
- Around line 198-203: The imported-declaration queue in the module import path
is adding generic `forall` declarations before they are filtered, so
cross-module generics still get stored in `_imported_fn_decls`. Update the logic
in the import handling block around `tld.decl`, `where_fns`, and `forall_vars`
so the `forall` guard runs first, and only append the declaration and any
`where_fns` when the declaration is not generic.
🪄 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: 8b8a352e-cad9-4fe2-a8e7-e2258cdf031a

📥 Commits

Reviewing files that changed from the base of the PR and between 2f5c733 and 35ebe57.

📒 Files selected for processing (3)
  • TESTING.md
  • tests/test_resolver.py
  • vera/codegen/modules.py

Comment thread TESTING.md Outdated
Comment thread TESTING.md Outdated
- TESTING.md: drop the stale "imports nothing" claim (the Chapter 8 cases
  use `import`); fix the negative-test attribution — the expected_error
  negatives are ch08_circular_import / ch08_visibility_private, while the
  ch09_http / ch09_inference check-level programs are environment-gated.
- codegen/modules.py: move the `forall` guard before the imported-decl
  append so a generic never enters `_imported_fn_decls` (matches the
  "excluded throughout" comment and avoids queuing an uncompilable generic
  template for Pass 2.5; cross-module generics remain #774).

Refs #814, #679.

Co-Authored-By: Claude <[email protected]>
@aallan

aallan commented Jun 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Addressed the outside-diff finding (vera/codegen/modules.py, the forall guard placement) in 786ac50 — moved if tld.decl.forall_vars: continue before the _imported_fn_decls.append, so a generic imported declaration (and its where-fns) never enters the Pass 2.5 compilation list. This matches the "generics excluded" comment and avoids queuing an uncompilable generic template; cross-module generic monomorphisation remains tracked separately by #774.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Tip

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

@aallan Thanks — that fix sounds correct. Moving the forall_vars guard ahead of _imported_fn_decls.append in vera/codegen/modules.py is the right place to enforce the “generics excluded” invariant, and it avoids queuing uncompilable imported generic templates or their where_fns into Pass 2.5.


✏️ Learnings added
Learnt from: aallan
URL: https://github.com/aallan/vera/pull/816

Timestamp: 2026-06-28T10:24:16.194Z
Learning: In `vera/codegen/modules.py`, imported generic functions (`tld.decl.forall_vars`) must be skipped before appending to `_imported_fn_decls`, so neither the generic declaration nor its `where_fns` enter Pass 2.5 compilation. Cross-module generic monomorphisation is still unsupported and tracked separately in issue `#774`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

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

Caution

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

⚠️ Outside diff range comments (1)
vera/codegen/modules.py (1)

223-225: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Apply module renames to Pass 2.5 imports too.

_module_intra_renames is only passed when Pass 2.6 emits mod$… bodies, but non-shadowed imported functions are still compiled in Pass 2.5 without that map. If m.g() calls sibling/helper m.f() and the importing module defines local f, the emitted bare g body can call the local f instead of the module one. Carry the module path with _imported_fn_decls and compile Pass 2.5 entries with self._module_intra_renames.get(path, {}) as well, including shadowed where_fns.

As per path instructions, vera/**/*.py is compiler source and should be reviewed for correctness, type safety, and consistency with existing patterns.

🤖 Prompt for 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.

In `@vera/codegen/modules.py` around lines 223 - 225, Pass 2.5 imported function
compilation is missing the module rename map, so bare bodies can resolve sibling
calls to local shadowed names instead of module-scoped ones. Update
`_imported_fn_decls` to carry the originating module path and, in `modules.py`,
have the Pass 2.5 emit path compile with `self._module_intra_renames.get(path,
{})` just like Pass 2.6 does for `mod$…` bodies. Also make sure shadowed
`where_fns` use the same module-specific rename lookup so imported functions
consistently pick the module’s `f` over any local `f`.

Source: Path instructions

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

Outside diff comments:
In `@vera/codegen/modules.py`:
- Around line 223-225: Pass 2.5 imported function compilation is missing the
module rename map, so bare bodies can resolve sibling calls to local shadowed
names instead of module-scoped ones. Update `_imported_fn_decls` to carry the
originating module path and, in `modules.py`, have the Pass 2.5 emit path
compile with `self._module_intra_renames.get(path, {})` just like Pass 2.6 does
for `mod$…` bodies. Also make sure shadowed `where_fns` use the same
module-specific rename lookup so imported functions consistently pick the
module’s `f` over any local `f`.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d9d80d32-1e7d-4410-b725-f6450a5618e0

📥 Commits

Reviewing files that changed from the base of the PR and between 35ebe57 and 786ac50.

📒 Files selected for processing (2)
  • TESTING.md
  • vera/codegen/modules.py

…ror)

A NON-shadowed imported function compiled in Pass 2.5 calls its siblings by
bare name; when a sibling is shadowed by a local, that call resolved to the
local shadow instead of the module's version (verify OK, run traps) — the
mirror of the C2 residual already fixed for the mod$ (Pass 2.6) bodies.

_imported_fn_decls now carries the originating module path, and Pass 2.5
compiles each imported body (and its where-fns) with that module's
_module_intra_renames map, so a bare sibling call reaches the module's
version. Regression test added: a non-shadowed imported `outer` whose body
calls a locally-shadowed `inner` returns the module's 100, not the local 7.

Refs #814, #679.

Co-Authored-By: Claude <[email protected]>
@aallan

aallan commented Jun 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Fixed the Pass 2.5 outside-diff finding in 2175e58_imported_fn_decls now carries the originating module path, and Pass 2.5 compiles each imported body (and its where_fns) with self._module_intra_renames.get(path, {}), exactly as Pass 2.6 does for mod$… bodies. So a non-shadowed imported function's bare sibling call resolves to the module's version rather than a local shadow of that name. Reproduced first (verify OK / run trap), and pinned by a regression test (test_imported_body_reaches_module_sibling_over_local_shadow): a non-shadowed imported outer whose body calls a locally-shadowed inner now returns the module's 100, not the local 7.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Tip

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

@aallan Thanks — that follow-up fix sounds right.

Carrying the originating module path with _imported_fn_decls and applying self._module_intra_renames.get(path, {}) during Pass 2.5 closes the same class of soundness hole for non-shadowed imported bodies that Pass 2.6 already handled for mod$… bodies. In particular, it preserves the intended module-local binding for bare sibling calls inside imported function bodies instead of accidentally routing them through an importer-local shadow.

The reproduced failure mode and the regression test test_imported_body_reaches_module_sibling_over_local_shadow make this much more robust. Nice catch.


✏️ Learnings added
Learnt from: aallan
URL: https://github.com/aallan/vera/pull/816

Timestamp: 2026-06-28T10:47:23.792Z
Learning: In Vera codegen, imported function bodies compiled during Pass 2.5 must retain their originating module path and be compiled with `self._module_intra_renames.get(path, {})` so bare sibling calls inside non-shadowed imported functions resolve to the imported module's own declarations rather than an importer-local shadow. This behaviour is covered by the regression test `tests/test_codegen_modules.py::test_imported_body_reaches_module_sibling_over_local_shadow`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@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

Caution

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

⚠️ Outside diff range comments (1)
vera/codegen/modules.py (1)

222-231: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Mirror _fn_ret_type_exprs for mangled module targets.

The mangled target gets _fn_sigs and _fn_nat_params, but not the full Vera return-type expression. Qualified calls to a shadowed module function that returns String/Array<T> can then miss the return-shape inference paths used by indexing/interpolation once the call target resolves to mod$....

Proposed fix
 self._fn_sigs.setdefault(mangled, mangled_sig)
+self._fn_ret_type_exprs.setdefault(
+    mangled, temp._fn_ret_type_exprs[fn_name],
+)
 self._shadowed_module_fns.append((mod.path, mangled, tld.decl))
🤖 Prompt for 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.

In `@vera/codegen/modules.py` around lines 222 - 231, The mangled module target is
already mirrored in _fn_sigs and _fn_nat_params, but _fn_ret_type_exprs is
missing, so qualified calls resolving through a mangled name lose return-shape
inference. Update the mangling path in modules.py where the shadowed module
function is recorded to also copy the original entry from
temp._fn_ret_type_exprs onto the mangled symbol, alongside the existing _fn_sigs
and _fn_nat_params handling.
🤖 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 `@vera/codegen/modules.py`:
- Around line 204-207: The imported where-function declarations are only being
appended, but they are not going through the same shadow/mangle registration as
the parent declaration, so local names can still shadow imported helpers during
Pass 2.5. Refactor the append plus shadow-registration logic in modules.py into
a helper and invoke it for both tld.decl and each non-generic tld.decl.where_fns
entry; ensure helper functions still populate _shadowed_module_fns and
_module_intra_renames, while only top-level public/in-filter declarations update
_module_qualified_targets.

---

Outside diff comments:
In `@vera/codegen/modules.py`:
- Around line 222-231: The mangled module target is already mirrored in _fn_sigs
and _fn_nat_params, but _fn_ret_type_exprs is missing, so qualified calls
resolving through a mangled name lose return-shape inference. Update the
mangling path in modules.py where the shadowed module function is recorded to
also copy the original entry from temp._fn_ret_type_exprs onto the mangled
symbol, alongside the existing _fn_sigs and _fn_nat_params handling.
🪄 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: 4f5be30c-2f38-40ba-8ea1-9af99a2c1d72

📥 Commits

Reviewing files that changed from the base of the PR and between 786ac50 and 2175e58.

📒 Files selected for processing (5)
  • ROADMAP.md
  • TESTING.md
  • tests/test_codegen_modules.py
  • vera/codegen/core.py
  • vera/codegen/modules.py

Comment thread vera/codegen/modules.py Outdated
Two further mirrors of the C2 shadow-resolution class, from CR's re-review:

- Imported `where`-fns were appended to the Pass 2.5 list but did NOT go
  through the shadow registration (only the parent decl did), so an imported
  `outer`'s call to a locally-shadowed `where` helper resolved to the local
  shadow (verify OK, run traps). Factored the registration into
  `_register_shadowed_import` and apply it to the parent decl AND each
  non-generic where-fn (where-fns are private, never qualified-callable, so
  no `_module_qualified_targets` entry). Reproduced first; regression test
  added.
- The mangled module target mirrored `_fn_sigs` + `_fn_nat_params` but not
  `_fn_ret_type_exprs`, so a qualified / intra-module call to a shadowed
  module fn returning `String` / `Array<T>` would lose index / interpolation
  element-type inference. Mirror it alongside the other side-tables.

Refs #814, #679.

Co-Authored-By: Claude <[email protected]>
@aallan

aallan commented Jun 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Addressed the _fn_ret_type_exprs outside-diff finding in 0d1edf0_register_shadowed_import now mirrors the return-type expression onto the mangled name alongside _fn_sigs and _fn_nat_params, so a qualified / intra-module call resolving to a mod$… target keeps the index / interpolation element-type inference for a shadowed fn returning String / Array<T>. Confirmed valid by code inspection (the mirror was genuinely absent); same defensive side-table pattern as the _fn_nat_params mirror, so no dedicated test (the failure mode is a graceful inference fallback, not a trap).

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Tip

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

@aallan Thanks — that fix sounds correct.

Mirroring _fn_ret_type_exprs in vera/codegen/modules.py::_register_shadowed_import alongside _fn_sigs and _fn_nat_params is the right consistency fix for the mangled mod$… target path. Without that mirror, qualified or intra-module calls that get rewritten to the mangled name can lose the return-type expression metadata needed for downstream element-type inference, especially for String / Array<T>-shaped results.

Given your note that the side-table entry was genuinely missing and that the failure mode is an inference fallback rather than a soundness/runtime failure, I agree a dedicated regression test is optional here.


✏️ Learnings added
Learnt from: aallan
URL: https://github.com/aallan/vera/pull/816

Timestamp: 2026-06-28T11:19:09.445Z
Learning: In Vera Python codegen, `vera/codegen/modules.py::_register_shadowed_import` must mirror `_fn_ret_type_exprs` onto mangled `mod$...` names for shadowed imported functions, alongside `_fn_sigs` and `_fn_nat_params`, so qualified or intra-module calls rewritten to the mangled target preserve return-type-expression-based inference such as `String` indexing and `Array<T>` element types.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

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

Caution

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

⚠️ Outside diff range comments (2)
vera/codegen/modules.py (1)

53-60: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Treat local where functions as shadows too.

local_fn_names only includes top-level local functions, but local where helpers are also emitted into the flat WAT namespace. If an importer has a local where helper and imported outer calls the module’s where helper, this path will not mangle/register the imported helper, so Pass 2.5 can emit or resolve $helper under the wrong/local name. Recursively collect local where_fns as shadows, and ensure the bare-name Pass 2.5 skip uses the same set.

Suggested direction
-        local_fn_names: set[str] = {
-            tld.decl.name for tld in program.declarations
-            if isinstance(tld.decl, ast.FnDecl)
-        }
+        local_fn_names: set[str] = set()
+
+        def collect_local_fn_names(decl: ast.FnDecl) -> None:
+            local_fn_names.add(decl.name)
+            for wfn in decl.where_fns or ():
+                collect_local_fn_names(wfn)
+
+        for tld in program.declarations:
+            if isinstance(tld.decl, ast.FnDecl):
+                collect_local_fn_names(tld.decl)

Also thread this same set into the Pass 2.5 bare-name skip, otherwise the imported helper can still be emitted as $helper.

Also applies to: 221-228

🤖 Prompt for 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.

In `@vera/codegen/modules.py` around lines 53 - 60, Treat local where functions as
shadows in the same way as top-level local functions: update the local_fn_names
collection in vera/codegen/modules.py to recursively include names from
where_fns (and any nested where helpers) so they are recognized as locally
shadowing the importer’s namespace. Then make sure the Pass 2.5 bare-name skip
uses that same shadow set wherever the imported helper is registered/emitted, so
a bare $helper is not generated or resolved under the wrong name while m::helper
still points to the module body.
TESTING.md (1)

530-530: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the conformance-count row in Validation Scripts.

check_conformance.py now validates 101 conformance programs, so this row is stale at 93 and no longer matches the manifest-driven count.

Proposed fix
-| `check_conformance.py` | All 93 conformance programs pass their declared level (parse/check/verify/run) |
+| `check_conformance.py` | All 101 conformance programs pass their declared level (parse/check/verify/run) |
🤖 Prompt for 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.

In `@TESTING.md` at line 530, The conformance-count row in the Validation Scripts
section is stale and should reflect the current manifest-driven total. Update
the `check_conformance.py` entry in `TESTING.md` so it reports 101 conformance
programs instead of 93, keeping the declared level description unchanged.

Source: Path instructions

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

Outside diff comments:
In `@TESTING.md`:
- Line 530: The conformance-count row in the Validation Scripts section is stale
and should reflect the current manifest-driven total. Update the
`check_conformance.py` entry in `TESTING.md` so it reports 101 conformance
programs instead of 93, keeping the declared level description unchanged.

In `@vera/codegen/modules.py`:
- Around line 53-60: Treat local where functions as shadows in the same way as
top-level local functions: update the local_fn_names collection in
vera/codegen/modules.py to recursively include names from where_fns (and any
nested where helpers) so they are recognized as locally shadowing the importer’s
namespace. Then make sure the Pass 2.5 bare-name skip uses that same shadow set
wherever the imported helper is registered/emitted, so a bare $helper is not
generated or resolved under the wrong name while m::helper still points to the
module body.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 733d0e05-50ff-4c81-853f-82e6774e97fb

📥 Commits

Reviewing files that changed from the base of the PR and between 2175e58 and 0d1edf0.

📒 Files selected for processing (4)
  • ROADMAP.md
  • TESTING.md
  • tests/test_codegen_modules.py
  • vera/codegen/modules.py

…ounts

- codegen: a LOCAL `where`-fn shadowing an imported name produced a duplicate
  bare `$name` WASM function — the imported body was emitted bare because
  `local_fn_names` collected only top-level names, so wasmtime rejected the
  module (check OK / verify OK / run fails on invalid WASM).
  `_collect_local_fn_names` now gathers names recursively (top-level + where-fn
  helpers, nested), and Pass 2.5 skips emitting an imported fn under a bare
  name shadowed by ANY local (top-level or where-fn) — it is reached via its
  `mod$…` emission instead. Reproduced first; regression test added.
- TESTING.md: the Validation Scripts and CI-gate tables still said "93
  conformance programs"; updated both to 101.

Refs #814, #679.

Co-Authored-By: Claude <[email protected]>
@aallan

aallan commented Jun 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Both outside-diff findings fixed in 04823cb:

  • TESTING.md conformance count — the Validation-Scripts and CI-gate tables both still said "93 conformance programs"; updated both to 101 (the conformance-section prose was already corrected earlier).
  • vera/codegen/modules.py local where-fn shadowslocal_fn_names now collects names recursively (top-level + where-fn helpers, nested) via a new _collect_local_fn_names, and Pass 2.5 skips bare-emitting any imported fn shadowed by ANY local (top-level or where-fn). So a local where helper helper plus an imported helper no longer produce a duplicate $helper — the imported one is reached via its mod$… emission instead. Reproduced first (check OK / verify OK / run failed on a duplicate-function WASM module), and pinned by test_local_where_fn_shadows_imported_name.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Tip

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

@aallan Thanks — both fixes sound correct.

  • Updating the remaining TESTING.md references from 93 to 101 closes the documentation drift cleanly.
  • The vera/codegen/modules.py change is the important correctness fix: making local_fn_names recursive over top-level and nested where helpers is the right way to define “shadowed by a local”, and skipping bare emission in Pass 2.5 for any such imported function avoids duplicate bare WASM symbols while still preserving access through the mod$… path.

The reproduced failure mode plus test_local_where_fn_shadows_imported_name make this one much more convincing.


✏️ Learnings added
Learnt from: aallan
URL: https://github.com/aallan/vera/pull/816

Timestamp: 2026-06-28T11:46:21.571Z
Learning: In `vera/codegen/modules.py`, local-shadow detection for imported functions must include names collected recursively from top-level declarations and nested `where` functions. Pass 2.5 should not bare-emit an imported function if any local function or `where` helper shadows that name; the imported declaration must instead remain reachable through its mangled `mod$...` target to avoid duplicate bare WASM function symbols. This behaviour is covered by `tests/test_codegen_modules.py::test_local_where_fn_shadows_imported_name`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@aallan

aallan commented Jun 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews resumed.

@aallan

aallan commented Jun 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

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

Caution

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

⚠️ Outside diff range comments (1)
tests/test_codegen_modules.py (1)

356-365: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the type-check result before verifying.

typecheck(...) is currently called only for side effects, so a check-stage module-resolution regression could be missed while verify(...) still runs.

Proposed fix
-            typecheck(prog, main_src, resolved_modules=[mod])
+            check_result = typecheck(prog, main_src, resolved_modules=[mod])
+            check_errors = [
+                d for d in check_result.diagnostics
+                if d.severity == "error"
+            ]
+            assert check_errors == [], [e.description for e in check_errors]
             vres = verify(prog, main_src, resolved_modules=[mod])

As per path instructions, tests/**/*.py should flag actual bugs in test logic and missing edge cases for new compiler features.

🤖 Prompt for 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.

In `@tests/test_codegen_modules.py` around lines 356 - 365, The test in the
`typecheck`/`verify` flow is only invoking `typecheck(...)` for side effects, so
a module-resolution regression could slip through unnoticed. Update the test to
explicitly assert the `typecheck` result before calling `verify(...)`, using the
existing `prog`, `main_src`, and `resolved_modules` setup, so failures in the
check stage are caught independently of verification.

Source: Path instructions

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

Outside diff comments:
In `@tests/test_codegen_modules.py`:
- Around line 356-365: The test in the `typecheck`/`verify` flow is only
invoking `typecheck(...)` for side effects, so a module-resolution regression
could slip through unnoticed. Update the test to explicitly assert the
`typecheck` result before calling `verify(...)`, using the existing `prog`,
`main_src`, and `resolved_modules` setup, so failures in the check stage are
caught independently of verification.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ee929be7-c2de-4c9d-9ad5-cc908465f0a5

📥 Commits

Reviewing files that changed from the base of the PR and between 2175e58 and 04823cb.

📒 Files selected for processing (5)
  • ROADMAP.md
  • TESTING.md
  • tests/test_codegen_modules.py
  • vera/codegen/core.py
  • vera/codegen/modules.py

CodeRabbit re-review: the differential test called typecheck() only for side
effects, so a check-stage module-resolution regression could slip through
while verify() still ran. Capture and assert the typecheck diagnostics
(typecheck returns the diagnostics list directly, not a result object) before
verifying — so the differential now pins all three stages.

Refs #814, #679.

Co-Authored-By: Claude <[email protected]>
…ce rules

Round-8 reworded the conformance *description* lines but missed the gate-rule
lines that also say "must pass their declared level", which is inaccurate now
that negative fixtures (expected_error) must FAIL check. Updated all three —
CLAUDE.md "What not to break", AGENTS.md command comment, and AGENTS.md rule —
to distinguish positive entries (pass their level) from the negative fixtures
(fail check with their expected_error E-code). Regenerated docs/llms-full.txt
(bundles AGENTS.md) via build_site.py.

Refs #814, #679.

Co-Authored-By: Claude <[email protected]>
@aallan

aallan commented Jun 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Fixed in 8d39116 — reworded the remaining "must pass" conformance rules (CLAUDE.md "What not to break", AGENTS.md command comment + rule) to distinguish positive entries (pass their declared level) from the negative fixtures ch08_circular_import / ch08_visibility_private (which must fail check with their expected_error E-code). The round-8 fix had only caught the conformance-suite description lines, not these gate-rule lines.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Tip

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

@aallan Thanks — that closes the remaining doc/invariant gap cleanly.

Rewording the CLAUDE.md and AGENTS.md gate-rule lines to distinguish:

  • positive conformance entries, which must pass their declared level, from
  • negative fixtures ch08_circular_import / ch08_visibility_private, which must fail check with their manifest expected_error

is the right fix. Your note about the earlier pass only updating the descriptive prose, not the normative “must pass” rules, also explains why this kept resurfacing.


✏️ Learnings added
Learnt from: aallan
URL: https://github.com/aallan/vera/pull/816

Timestamp: 2026-06-28T13:01:10.850Z
Learning: In Vera's conformance-suite documentation, normative gate-rule text in `CLAUDE.md` and `AGENTS.md` must distinguish positive entries from negative Chapter 8 fixtures. Positive conformance programs must pass their declared level, while `tests/conformance/manifest.json` entries such as `ch08_circular_import` and `ch08_visibility_private` are negative fixtures that must fail `check` with their declared `expected_error` E-code rather than "pass".

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

CodeRabbit re-review: the E150 "cannot import a private declaration" diagnostic
(checker/modules.py — both the fn and data import paths) cited 'Chapter 5,
Section 5.8 "Function Visibility"', a section that does not exist (Chapter 5 has
no §5.8) and the wrong concept. E150 fires in the import-resolution path, so it
now points to the module-system rule, 'Chapter 8, Section 8.4 "Visibility"',
keeping the message / rationale / fix unchanged.

Refs #814, #679.

Co-Authored-By: Claude <[email protected]>
@aallan

aallan commented Jun 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Fixed the outside-diff finding in cd6a0d6 — E150 ("cannot import a private declaration") now cites Chapter 8, Section 8.4 "Visibility" instead of Chapter 5, Section 5.8 "Function Visibility" (which doesn't exist — Chapter 5 has no §5.8). Applied to both the fn and data import paths; message, rationale, and fix text unchanged. Verified E150 still fires on ch08_visibility_private with the corrected ref.

@aallan

aallan commented Jun 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Tip

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

@aallan Thanks — that sounds correct.

Updating E150 to cite Chapter 8, Section 8.4 "Visibility" is the right fix here, especially since the previous Chapter 5, Section 5.8 reference does not exist. Applying it consistently to both the function and data import paths preserves the diagnostic behaviour while fixing the spec citation, and your check against ch08_visibility_private is good confirmation that the error still fires as intended.


✏️ Learnings added
Learnt from: aallan
URL: https://github.com/aallan/vera/pull/816

Timestamp: 2026-06-28T13:11:05.689Z
Learning: In `vera/checker/modules.py`, diagnostic `E150` for importing a private declaration should cite `Chapter 8, Section 8.4 "Visibility"` in both the function-import and data-import paths. The previous reference to `Chapter 5, Section 5.8 "Function Visibility"` was invalid because that section does not exist.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

530-530: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align the hook wording with expected_error semantics.

Line 530 and Line 630 still say check_conformance.py makes all 101 conformance programmes “pass” their declared level. That is imprecise for the Chapter 8 negatives: those entries are successful when check fails with the declared E-code.

♻️ Suggested fix
-| `check_conformance.py` | All 101 conformance programs pass their declared level (parse/check/verify/run) |
+| `check_conformance.py` | All 101 conformance entries satisfy their declared outcome (parse/check/verify/run) |

-| `check_conformance.py` | All 101 conformance programs pass their declared level |
+| `check_conformance.py` | All 101 conformance entries satisfy their declared outcome |

Also applies to: 630-630

🤖 Prompt for 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.

In `@TESTING.md` at line 530, The wording for `check_conformance.py` is too broad
for the Chapter 8 negative cases, since success there means `check` fails with
the declared `expected_error` rather than “passing” the level. Update the hook
description text in `TESTING.md` for the affected `check_conformance.py` entries
so it explicitly reflects `expected_error` semantics, keeping the wording
accurate for both normal conformance programs and negative error-expected cases.

Source: Path instructions

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

Duplicate comments:
In `@TESTING.md`:
- Line 530: The wording for `check_conformance.py` is too broad for the Chapter
8 negative cases, since success there means `check` fails with the declared
`expected_error` rather than “passing” the level. Update the hook description
text in `TESTING.md` for the affected `check_conformance.py` entries so it
explicitly reflects `expected_error` semantics, keeping the wording accurate for
both normal conformance programs and negative error-expected cases.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: cfbe6a63-a68c-45f6-8c48-611ed0ba483a

📥 Commits

Reviewing files that changed from the base of the PR and between 6aa335d and cd6a0d6.

⛔ Files ignored due to path filters (1)
  • docs/llms-full.txt is excluded by !docs/**
📒 Files selected for processing (8)
  • AGENTS.md
  • CLAUDE.md
  • README.md
  • ROADMAP.md
  • TESTING.md
  • tests/test_codegen_modules.py
  • vera/checker/modules.py
  • vera/wasm/context.py

@aallan

aallan commented Jun 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Fixed the TESTING.md duplicate (lines 530 + 630) in cd6a0d6, plus an exhaustive sweep of the same wording class: the Validation-Scripts and CI-gate tables and CONTRIBUTING.md's gate list now read "All 101 conformance entries hold at their declared level — positives pass; negatives fail check with their expected_error E-code", matching the CLAUDE.md / AGENTS.md phrasing. No "all conformance programs pass" occurrences remain.

The two recurring check_conformance.py / test_conformance.py expected_error-guard inline comments are already addressed (guards at check_conformance.py:60 and test_conformance.py:76) and verified — injecting a verify-level expected_error entry fails fast with "expected_error is only valid at level 'check'". They appear to be re-anchored re-posts of the resolved round-7 finding.

@aallan

aallan commented Jun 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Tip

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

@aallan Thanks — that all makes sense.

The doc sweep sounds complete now: updating the remaining TESTING.md duplicates and aligning CONTRIBUTING.md with the CLAUDE.md / AGENTS.md rule text closes the wording drift cleanly. The revised invariant is the right one: conformance entries hold at their declared level, with positives passing and negative fixtures failing check with their declared expected_error.

And agreed on the recurring expected_error guard comments: given the explicit guard in scripts/check_conformance.py and the matching assertion in tests/test_conformance.py, plus your verify-level injection proof, those re-flags are just re-anchored false positives rather than live issues.

…ning tables

The exhaustive sweep's last occurrences of the "all conformance programs pass
their declared level" wording — TESTING.md's Validation-Scripts (line 530) and
CI-gate (line 630) tables, and CONTRIBUTING.md's gate list — predated the
negative-test support and didn't distinguish positives (pass) from the
`expected_error` negatives (succeed by failing `check` with their E-code).
Reworded all three to match the CLAUDE.md / AGENTS.md phrasing. (Round-5 fixed
the count 93->101 on the TESTING lines but not the verb.)

Refs #814, #679.

Co-Authored-By: Claude <[email protected]>
@aallan

aallan commented Jun 28, 2026

Copy link
Copy Markdown
Owner Author

Correction to my comment above: the conformance-wording reword (TESTING.md lines 530 + 630 and CONTRIBUTING.md) actually landed in ad5df1f, not cd6a0d6 — a stale .git/index.lock failed the first commit, so the earlier SHA reference was the un-advanced head. The fix itself is as described: "All 101 conformance entries hold at their declared level — positives pass; negatives fail check with their expected_error E-code", and no "all conformance programs pass" occurrences remain.

@aallan

aallan commented Jun 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

…odeRabbit)

The resolver's three module-resolution error paths emitted inconsistent
field sets: E011 (circular import) lacked fix+spec_ref, E012 (cannot
resolve) lacked spec_ref, E013 (parse error) carried only
description+error_code. Because Diagnostic.to_dict() omits empty fields,
`vera check --json` exposed module-resolution failures with a JSON schema
that drifted from every other diagnostic (49 checker sites set spec_ref;
the hand-written E001/E007 carry rationale+fix+spec_ref). Added the
missing rationale/fix/spec_ref fields, each citing the relevant Chapter 8
section (§8.6.3 circular import detection, §8.6.1 path mapping, §8.6.5
resolution errors), with instructional fix text.

Test-first: test_resolver_diagnostics_carry_full_contract asserts all
three paths carry rationale+fix+spec_ref and cite Chapter 8; confirmed
RED (E011 fix='') before the change, GREEN after. Verified end-to-end via
`vera check --json` on the ch08_circular_import fixture.

Doc counts: 5333->5334 tests; test_resolver.py 18->19 tests, 486->550
lines.

Co-Authored-By: Claude <[email protected]>
@aallan

aallan commented Jun 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Fixed in c3676a5 (resolver.py outside-diff finding — "Complete the resolver diagnostics' JSON contract"). Completed the diagnostic contract for all three module-resolution error paths: E011 (circular import) now carries fix + spec_ref (§8.6.3), E012 (cannot resolve) adds spec_ref (§8.6.1), and E013 (parse error) adds rationale + fix + spec_ref (§8.6.5) — so vera check --json exposes module-resolution failures with the same field set as every other diagnostic (Diagnostic.to_dict() omits empty fields, which is what caused the schema drift you flagged). Pinned by test_resolver_diagnostics_carry_full_contract, confirmed RED first (E011 fix='') then GREEN, and verified end-to-end via vera check --json on the ch08_circular_import fixture.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Tip

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

@aallan Thanks — that sounds like the right fix.

Completing the resolver-side diagnostic contract in vera/resolver.py for all three module-resolution paths closes the JSON-schema gap cleanly:

  • E011 now includes fix + spec_ref
  • E012 now includes spec_ref
  • E013 now includes rationale + fix + spec_ref

Given that Diagnostic.to_dict() omits empty fields, filling those previously-missing fields is exactly what was needed to make vera check --json consistent with the rest of the diagnostic surface. The RED-first check on E011, the dedicated regression test test_resolver_diagnostics_carry_full_contract, and the end-to-end validation on ch08_circular_import are all strong confirmation.


✏️ Learnings added
Learnt from: aallan
URL: https://github.com/aallan/vera/pull/816

Timestamp: 2026-06-28T13:54:42.714Z
Learning: In `vera/resolver.py`, module-resolution diagnostics exposed through `vera check --json` must carry the same complete non-empty field contract as other diagnostics. Specifically, circular import `E011` should include `fix` and `spec_ref`, unresolved import `E012` should include `spec_ref`, and imported-module parse error `E013` should include `rationale`, `fix`, and `spec_ref`. This matters because `Diagnostic.to_dict()` omits empty fields, so missing values cause observable JSON schema drift. The behaviour is covered by `tests/test_resolver.py::test_resolver_diagnostics_carry_full_contract`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

T and others added 2 commits June 28, 2026 15:38
…resolver except (review-toolkit)

Our pr-review-toolkit review of #816 surfaced two untested codegen paths
and one over-broad except. All three addressed test-first, each
mutation-validated (RED before the fix, GREEN after):

1. i32_pair ModuleCall drop (vera/wasm/context.py): a String/Array-
   returning module-qualified call in statement position must drop BOTH
   i32 stack values. The branch was untested; mutating it yields invalid
   WASM ("values remaining on stack"). Added
   test_pair_returning_qualified_call_in_statement_position, using a local
   where-shadow so the mod$ mangled-target path is exercised.

2. @Nat-param guard mirror (vera/codegen/modules.py): the call-site
   value>=0 narrowing guard must be mirrored onto a shadowed module fn's
   mod$ target. Untested; dropping the mirror passes a negative value
   unguarded (verified: m::f(0 - 1) returns -1 with no trap). Added
   test_nat_param_guard_mirrored_on_shadowed_qualified_call (traps with
   the mirror, runs unsound without).

3. resolver.py except narrowing: a blanket `except Exception` relabeled
   any internal compiler bug (e.g. an AttributeError in transform) as
   E013 "Error parsing imported module", blaming the user's file.
   Narrowed to (ParseError, TransformError, OSError, UnicodeDecodeError)
   so real bugs propagate as crashes. Added
   test_internal_error_not_masked_as_E013 (RED before: "DID NOT RAISE").

Doc counts: 5334->5337 tests; test_codegen_modules 31->33 / 1077->1151
lines, test_resolver 19->20 / 550->585 lines.

Co-Authored-By: Claude <[email protected]>
…4 (review follow-up)

The E232 diagnostic (calling a private function via a module-qualified
call `m::f`) and the missing-visibility error both cited 'Chapter 5,
Section 5.8 "Function Visibility"', which no longer exists — module
visibility rules live in spec/08-modules.md §8.4.  This matches the E150
(private import) correction already in this PR; E232 is its
qualified-call sibling, and §8.4 line 81 ("Every top-level fn/data must
have an explicit visibility modifier; omitting is a compile error")
covers the missing-visibility error too.

Repointed both sites (vera/checker/calls.py, vera/checker/registration.py)
to 'Chapter 8, Section 8.4 "Visibility"'.  Strengthened the existing E232
and missing-visibility tests to assert the spec_ref (the E232 test was
confirmed RED on §5.8 first, GREEN after).  No new tests; test_checker.py
line count 6455->6467.

Co-Authored-By: Claude <[email protected]>

@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

Caution

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

⚠️ Outside diff range comments (1)
vera/resolver.py (1)

103-125: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Populate the full diagnostic payload for resolver errors.

These three paths still build Diagnostic(...) with only line/column, so Diagnostic.to_dict() will omit location.file and source_line. vera check --json therefore emits a different schema for E011/E012/E013 than the rest of the compiler, which breaks the documented machine-consumer contract this PR is standardising. Please thread the importing file and offending import line into these diagnostics instead of emitting bare records. As per path instructions, every Diagnostic should include description, location, source_line, rationale, fix, spec_ref and a stable error_code.

Also applies to: 133-155, 192-214

🤖 Prompt for 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.

In `@vera/resolver.py` around lines 103 - 125, Populate the full Diagnostic
payload in resolver errors so E011/E012/E013 match the standard JSON schema. In
the resolver paths that build Diagnostic instances for circular import and
related checks, thread through the importing file and offending import line so
Diagnostic.to_dict() can include location.file and source_line, not just
line/column. Update the relevant diagnostic construction sites in resolver.py to
always set description, location, source_line, rationale, fix, spec_ref, and
error_code consistently.

Source: Path instructions

🤖 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_resolver.py`:
- Around line 545-548: The full diagnostic contract test currently verifies
rationale, fix, and spec_ref for E011/E012/E013 but does not assert source_line,
so it can miss regressions where the offending import line is dropped from JSON.
Update the test in the diagnostic assertions block to include source_line
alongside the existing fields, using the same diag objects (e011, e012, e013) so
the public resolver contract stays fully pinned.

---

Outside diff comments:
In `@vera/resolver.py`:
- Around line 103-125: Populate the full Diagnostic payload in resolver errors
so E011/E012/E013 match the standard JSON schema. In the resolver paths that
build Diagnostic instances for circular import and related checks, thread
through the importing file and offending import line so Diagnostic.to_dict() can
include location.file and source_line, not just line/column. Update the relevant
diagnostic construction sites in resolver.py to always set description,
location, source_line, rationale, fix, spec_ref, and error_code consistently.
🪄 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: 61dd7c89-ab5e-4009-ac14-b7838d40cb55

📥 Commits

Reviewing files that changed from the base of the PR and between cd6a0d6 and 871fda3.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • CONTRIBUTING.md
  • README.md
  • ROADMAP.md
  • TESTING.md
  • tests/test_checker.py
  • tests/test_codegen_modules.py
  • tests/test_resolver.py
  • vera/checker/calls.py
  • vera/checker/registration.py
  • vera/resolver.py

Comment thread tests/test_resolver.py
…eRabbit)

CodeRabbit follow-up on the diagnostic-contract work: E011/E012/E013 set
rationale/fix/spec_ref but not location.file or source_line, so
`vera check --json` exposed module-resolution failures with a schema
missing two fields that every checker diagnostic carries
(Diagnostic.to_dict() drops empty fields, so a bare diagnostic drifts
from the documented schema).

- _location_from_node now threads the importing file into location.file.
- New _source_line helper reads the offending import line from the
  importing file (best-effort: an unreadable file yields "" rather than
  raising, since the source line is contextual, not essential). Mirrors
  the checker's _source_line.
- Both threaded into all three resolver diagnostic sites (E011/E012/E013).

Test-first: extended test_resolver_diagnostics_carry_full_contract to
assert location.file and source_line (and that source_line names the
offending import) for all three paths; confirmed RED (file=None) before
the fix, GREEN after. Verified end-to-end via `vera check --json` on the
ch08_circular_import fixture (location.file + source_line now present).

Doc counts: test_resolver.py 585->594 lines (no new tests).

Co-Authored-By: Claude <[email protected]>
@aallan

aallan commented Jun 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai The outside-diff finding on vera/resolver.py (populate location.file + source_line so E011/E012/E013 match the standard JSON schema) was already addressed in b826cba: _location_from_node now threads the importing file into location.file, a new _source_line helper reads the offending import line, and both are wired into all three diagnostic sites. Diagnostic.to_dict() now emits the full payload — description, location (with file), source_line, rationale, fix, spec_ref, error_code. Verified via vera check --json on ch08_circular_import: location.file and source_line: 'import vera.cycle_a;' are both present.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Tip

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

@aallan You're right — that concern no longer applies.

If vera/resolver.py::_location_from_node is already threading the importing file into location.file, and vera/resolver.py::_source_line is wired into the E011 / E012 / E013 paths, then the JSON contract is already complete for those resolver diagnostics. Your vera check --json verification on ch08_circular_import, with both location.file and source_line present, is the decisive confirmation.

I'll treat that review comment as withdrawn.


✏️ Learnings added
Learnt from: aallan
URL: https://github.com/aallan/vera/pull/816

Timestamp: 2026-06-28T16:01:06.546Z
Learning: In `vera/resolver.py`, module-resolution diagnostics `E011`, `E012`, and `E013` should populate the standard JSON fields `location.file` and `source_line` in addition to description/rationale/fix/spec_ref/error_code. This is implemented by threading the importing file through `vera/resolver.py::_location_from_node` and using `vera/resolver.py::_source_line` to capture the offending import line, and is observable via `vera check --json` on the Chapter 8 circular-import fixture.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@aallan aallan merged commit 245980c into main Jun 28, 2026
28 checks passed
@aallan aallan deleted the feat/679-ch08-conformance branch June 28, 2026 16:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant