refactor(#657): codegen INVARIANT guards → CodegenInvariantError [E699]; preserve [E615]-reachable PROPAGATE#833
Conversation
…rds raise CodegenInvariantError Follow-up to #626 (closed by #658). Converts the 2 INVARIANT_DEFENSIVE sites in `_compile_lifted_closure` (unsupported closure param / return WASM type — states the type-checker should have rejected) from `return None` to `raise CodegenInvariantError(msg, anon_fn)`, so a compiler bug surfaces as an [E699] "internal compiler error" instead of a silent skip. Updated the stale catch-handler comment ("no production code raises CodegenInvariantError yet"). Both sites stay `# pragma: no cover` — they guard type-check-impossible states, so no test input reaches them; the change is behaviour-preserving for all well-typed programs. Establishes the canonical Track-2 conversion pattern for the remaining files (operators.py 25, inference.py 10). mypy clean; 50 closure codegen tests pass; e602 gate clean; ruff clean. Co-Authored-By: Claude <[email protected]>
…t 19 non-forwarding guards Converts the 19 genuine INVARIANT_DEFENSIVE guards in operators.py (slot-ref shape, pipe RHS, float-op lookup, binary/unary dispatch fallthroughs, ADT-eq, result-ref, quantifier predicate shape, old/new/_extract_state_type_name lookups) from `return None` to `raise CodegenInvariantError` — a compiler bug now surfaces as [E699] rather than a silent skip. Deliberately PRESERVES 5 sites the #657 audit mis-classified as INVARIANT_DEFENSIVE that actually FORWARD translate_expr / translate_block results (binary operand, unary operand, assert condition, quantifier domain, quantifier body). Those results return None *reachably* via the #630 [E615] string-interpolation channel (translate_expr on a failing InterpolatedString records to _interp_inference_failures and returns None, dropped loudly at the _compile_fn boundary). So those `return None`s are load-bearing PROPAGATE, not dead — converting them would turn a graceful [E615] function-drop into an [E699] crash. See #657 comment for the full finding. All converted paths stay `# pragma: no cover` (type-check-impossible). Behaviour-preserving: 62 E615/interpolation tests pass, 1233 codegen+wasm tests pass, e602 gate clean, ruff + mypy clean. Co-Authored-By: Claude <[email protected]>
…99, release v0.0.191 Guardrail comments so the PROPAGATE mis-classification can't recur: - vera/skip.py: new "Reachable None via the [E615] channel" section in the #626 taxonomy docstring — translate_expr/translate_block still return None reachably (failed string interpolation, #630), so forwards of them are load-bearing PROPAGATE, not dead; only non-forwarding guards are CodegenInvariantError candidates. - vera/wasm/operators.py: a `# #657 / #630 [E615]` comment at each of the 5 preserved forwards (binary/unary operand, assert cond, quantifier domain/body) explaining why they stay `return None`, plus a note at the canonical source (_translate_interpolated_string's `if had_failure: return None`). Pin the Track-2 contract: - tests/test_codegen_invariant_e699.py: forces a CodegenInvariantError via a translate_block monkeypatch and asserts it surfaces as [E699] (severity error). - vera/codegen/functions.py: the _compile_fn CodegenInvariantError handler is now reachable/covered, so its `# pragma: no cover` + stale "no production code raises yet" comment are removed. Release v0.0.191 (Closes #657): version bump x6, CHANGELOG [0.0.191] (both the E699 conversion and the corrected PROPAGATE/E615 premise), HISTORY row + "By the numbers" footer (189 -> 191 tagged releases), ROADMAP #657 -> removed, TESTING row + counts (5,558 / 53 files). All gates green: full suite, e602, E615 (62), E699 (1), mypy, ruff, doc-counts, version-sync, site-assets, changelog. Co-Authored-By: Claude <[email protected]>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🔗 Linked repositories identifiedCodeRabbit considers these linked repositories for cross-repo context during reviews:
📝 WalkthroughWalkthroughCodegen guard paths now raise ChangesE699 invariant handling rollout
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related issues
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 8✅ Passed checks (8 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #833 +/- ##
==========================================
+ Coverage 92.05% 92.06% +0.01%
==========================================
Files 90 90
Lines 27307 27321 +14
Branches 324 324
==========================================
+ Hits 25137 25154 +17
+ Misses 2162 2159 -3
Partials 8 8
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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 `@tests/test_codegen_invariant_e699.py`:
- Around line 61-67: The test in test_codegen_invariant_e699.py only checks that
result.diagnostics contains E699, so it can still pass if the old
unsupported-construct diagnostic E602 is also emitted. Update the assertion
block around e699 to explicitly verify that no diagnostic in result.diagnostics
has error_code "E602", alongside the existing checks on severity and
description, so the new compiler path is confirmed to avoid the legacy fallback.
In `@vera/codegen/closures.py`:
- Around line 261-263: The unsupported WASM type path in
`_compile_lifted_closure()` is being converted into a normal closure failure,
which causes `_lift_pending_closures()` and `_compile_fn()` to fall through and
emit the generic skip warning. Update the handling around
`CodegenInvariantError` in `closures.py` so the invariant failure is preserved
for the E699 path, either by re-raising it or by returning a distinct failure
signal that `_lift_pending_closures()` and `_compile_fn()` can recognize and
avoid treating as a user-facing unsupported-expression skip. Apply the same fix
consistently in the related closure handling blocks referenced by
`_compile_lifted_closure`, `_lift_pending_closures`, and `_compile_fn`.
In `@vera/wasm/operators.py`:
- Around line 693-702: The quantifier predicate handling in the predicate
translation path only checks for zero parameters and then blindly uses
pred.params[0], which can hide malformed multi-parameter predicates. Update the
logic around expr.predicate in the quantifier codegen to assert exactly one
parameter is present before accessing it, and raise CodegenInvariantError for
both zero-parameter and multi-parameter cases. Keep the existing NamedType
validation on pred.params[0] and align the new invariant check with the
surrounding patterns in this code path.
🪄 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 Plus
Run ID: b22ab662-871e-44f6-97db-9e18413bd83f
⛔ Files ignored due to path filters (5)
docs/index.htmlis excluded by!docs/**docs/index.mdis excluded by!docs/**docs/llms-full.txtis excluded by!docs/**docs/llms.txtis excluded by!docs/**uv.lockis excluded by!**/*.lock,!uv.lock
📒 Files selected for processing (12)
CHANGELOG.mdHISTORY.mdREADME.mdROADMAP.mdTESTING.mdpyproject.tomltests/test_codegen_invariant_e699.pyvera/__init__.pyvera/codegen/closures.pyvera/codegen/functions.pyvera/skip.pyvera/wasm/operators.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
aallan/vera-bench(manual)
…y guard; test no-[E602] CodeRabbit (PR #833), 3 findings, all valid: - vera/codegen/closures.py + functions.py: a closure-body CodegenInvariantError was caught in _compile_lifted_closure (emitted [E699] AND returned None), so _compile_fn ALSO emitted a spurious [E602] "closure skipped" — mixing the compiler-bug and user-skip signals. The invariant now PROPAGATES to _compile_fn's handler (new try/except around _lift_pending_closures) for a single [E699]; the local handler is removed. Same shape as the [E615] lesson: propagate the signal, don't swallow it. - vera/wasm/operators.py: the quantifier-predicate guard tightened from `if not pred.params` to `if len(pred.params) != 1`, so a malformed multi-parameter predicate raises [E699] instead of silently using params[0]. - tests/test_codegen_invariant_e699.py: the E699 test now also asserts the path is NOT reported as [E602] (proving correct attribution); a second test pins the closure-lift -> [E699] propagation (covering the new _compile_fn handler). Doc counts reconciled (5,559 tests; checker E699 file 2/104). Full codegen suite + E615 + e602 gate + mypy + ruff green. Co-Authored-By: Claude <[email protected]>
|
@coderabbitai full review Pushed 3235391 addressing all three findings: (1) the E699 test now also asserts the path is not |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
git log --oneline -10
echo "---"
git show 3235391 --statLength of output: 2087 🏁 Script executed: #!/bin/bash
gh api repos/aallan/vera/commits/3235391 --jq '.files[].filename'
echo "---"
gh api repos/aallan/vera/commits/3235391 --jq '.files[] | select(.filename | test("closures.py|operators.py|functions.py|test_codegen_invariant_e699.py")) | .patch'Length of output: 6914 🏁 Script executed: #!/bin/bash
gh api repos/aallan/vera/contents/vera/codegen/closures.py?ref=3235391 --jq '.content' | base64 -d > closures.py
gh api repos/aallan/vera/contents/vera/codegen/functions.py?ref=3235391 --jq '.content' | base64 -d > functions.py
gh api repos/aallan/vera/contents/vera/wasm/operators.py?ref=3235391 --jq '.content' | base64 -d > operators.py
echo "=== closures.py: _compile_lifted_closure full ==="
awk '/def _compile_lifted_closure/,/^ def [a-zA-Z_]+\(/{print NR": "$0}' closures.py | sed -n '1,140p'
echo "=== _lift_pending_closures ==="
grep -n "_lift_pending_closures" closures.py functions.pyLength of output: 1376 🏁 Script executed: #!/bin/bash
echo "=== _lift_pending_closures (closures.py) ==="
sed -n '1,70p' closures.py
echo "=== _compile_lifted_closure full body ==="
sed -n '177,420p' closures.pyLength of output: 16513 🏁 Script executed: #!/bin/bash
sed -n '70,177p' closures.pyLength of output: 5499 🏁 Script executed: #!/bin/bash
sed -n '380,460p' functions.py
echo "=== quantifier fix context ==="
grep -n "quantifier predicate" operators.py
sed -n '675,715p' operators.pyLength of output: 6720
No further issues found — the reachable- ✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/test_codegen_invariant_e699.py`:
- Around line 77-104: The test is patching
ClosureLiftingMixin._lift_pending_closures directly, which bypasses
_compile_lifted_closure and cannot detect a regression where that method
re-catches CodegenInvariantError and reintroduces the old [E699]+[E602]
behavior. Update the test to exercise the lifted-closure body translation path
through _compile_lifted_closure (or the specific closure-body helper it calls),
then assert the diagnostic stream contains [E699] and does not contain the
closure-skipped [E602] so the intended single-signal behavior is pinned.
In `@vera/codegen/functions.py`:
- Around line 421-442: The `try`/`except` around
`self._lift_pending_closures(ctx)` in `vera/codegen/functions.py` handles the
invariant failure correctly, but it can exit before the closure-lifting rollback
footer runs, leaving state like `_next_closure_id` and registered closure
signatures desynchronised. Update this `CodegenInvariantError` path to
explicitly restore the same closure-lifting state that
`_lift_pending_closures()` would normally roll back before calling `_error()`
and returning `None`. Use the existing closure-lifting bookkeeping in
`Codegen`/`_lift_pending_closures` as the source of truth so later functions see
consistent closure IDs and table indices.
🪄 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 Plus
Run ID: 7631920d-9808-4214-b37e-de867dc285c9
📒 Files selected for processing (8)
CHANGELOG.mdREADME.mdROADMAP.mdTESTING.mdtests/test_codegen_invariant_e699.pyvera/codegen/closures.pyvera/codegen/functions.pyvera/wasm/operators.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
aallan/vera-bench(manual)
…bort; deepen the E699 test Two CodeRabbit findings on the review round, both valid: - vera/codegen/closures.py: my propagate-fix let a closure-body CodegenInvariantError abort _lift_pending_closures mid-worklist, skipping the commit/rollback footer — leaving self._next_closure_id advanced (module state desync for later functions' closure_id <-> table_index). Now the _compile_lifted_closure call is wrapped: on CodegenInvariantError it restores _next_closure_id (mirroring the any_failed rollback) and re-raises, so _compile_fn still surfaces a single [E699] with clean state. - tests/test_codegen_invariant_e699.py: the closure test now patches _compile_lifted_closure (not _lift_pending_closures), driving the real worklist + rollback + _compile_fn handler — so it guards the local-swallow regression too. Uses a real closure program. It asserts [E699]; it can't assert "no [E602]" because a full closure compile emits incidental [E602]/ [E604] warnings from the prelude regardless (single-signal property is inspection-verified). 50 closure tests + both E699 tests pass; mypy + ruff + e602 clean; doc counts 114 lines. Co-Authored-By: Claude <[email protected]>
|
@coderabbitai full review Pushed e715ab5 for the two review-round findings: (1) |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
git log --oneline -5
echo "---"
git show e715ab5 --stat
echo "=== patches ==="
gh api repos/aallan/vera/commits/e715ab5 --jq '.files[] | select(.filename | test("closures.py|test_codegen_invariant_e699.py")) | .patch'Length of output: 2248 🏁 Script executed: #!/bin/bash
gh api repos/aallan/vera/commits/e715ab5 --jq '.files[].filename'
echo "---"
gh api repos/aallan/vera/commits/e715ab5 --jq '.files[] | select(.filename | test("closures.py|test_codegen_invariant_e699.py")) | .patch'Length of output: 5355 🏁 Script executed: #!/bin/bash
gh api repos/aallan/vera/contents/vera/codegen/closures.py?ref=e715ab5 --jq '.content' | base64 -d > closures_new.py
sed -n '55,140p' closures_new.pyLength of output: 5062 🏁 Script executed: #!/bin/bash
sed -n '140,190p' closures_new.pyLength of output: 2523 Verified. Both fixes in e715ab5 hold up:
No further issues — this closes out the ✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/test_codegen_invariant_e699.py`:
- Around line 82-94: The test is patching the wrong layer by replacing
`_compile_lifted_closure` itself, which bypasses the real implementation and
would miss a reintroduced local `except CodegenInvariantError` in that method.
Update the test to trigger the failure through a helper that
`_compile_lifted_closure` calls, so the actual propagation path into
`_compile_fn` and the `_next_closure_id` rollback is exercised while still
asserting the `[E699]` signal.
🪄 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 Plus
Run ID: 50114003-6009-43e8-b923-7109b8fa8613
📒 Files selected for processing (3)
TESTING.mdtests/test_codegen_invariant_e699.pyvera/codegen/closures.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
aallan/vera-bench(manual)
…mpile_lifted_closure CodeRabbit: patching `_compile_lifted_closure` wholesale bypassed the real method, so a reintroduced local `except CodegenInvariantError` there would go undetected. The test now injects the failure at a callee — `translate_block`, keyed by AST-node identity so it fires ONLY on the closure body — so the real `_compile_lifted_closure` runs end-to-end, propagates through `_lift_pending_closures` (with the `_next_closure_id` rollback), and reaches `_compile_fn` for the `[E699]`. Added a generic `_find_anon_fn` AST walker to locate the closure body. Mutation-validated: simulating the re-added local swallow removes E699 (test goes RED), confirming it now guards that regression. Both E699 tests pass; mypy + ruff clean; doc counts (156 lines). Co-Authored-By: Claude <[email protected]>
|
@coderabbitai full review Pushed 61d697b: the closure E699 test now injects the failure at a callee ( |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
git log --oneline -5
echo "---"
git show 61d697b --stat
echo "=== patch ==="
gh api repos/aallan/vera/commits/61d697b --jq '.files[] | select(.filename | test("test_codegen_invariant_e699.py|closures.py")) | .patch'Length of output: 2246 🏁 Script executed: #!/bin/bash
gh api repos/aallan/vera/commits/61d697b --jq '.files[].filename'
echo "---"
gh api repos/aallan/vera/commits/61d697b --jq '.files[] | select(.filename | test("test_codegen_invariant_e699.py|closures.py")) | .patch'Length of output: 4434 🏁 Script executed: #!/bin/bash
git fetch origin pull/833/head:pr833 2>&1 | tail -5
git checkout pr833 2>&1 | tail -5
ast-grep run --pattern 'def translate_block($$$ARGS):' --lang python vera/wasm | head -30
grep -n "def translate_block" -r vera/wasm | headLength of output: 562 🏁 Script executed: #!/bin/bash
sed -n '540,570p' vera/wasm/context.pyLength of output: 1561 🏁 Script executed: #!/bin/bash
grep -n "class AnonFn" -A 15 vera/ast.py
echo "---"
sed -n '1,180p' tests/test_codegen_invariant_e699.pyLength of output: 6619 🏁 Script executed: #!/bin/bash
grep -n "except CodegenInvariantError" vera/codegen/closures.py vera/codegen/functions.pyLength of output: 660 This confirms the only two catch points are the rollback-and-reraise in
No further issues — this closes out the [resolve] |
What & why
Follow-up to #626 (closed by #658). The #626 audit classified every
return Noneinvera/codegen/**/vera/wasm/**; #658 converted the 104 user-actionable SILENT_SKIP sites toCodegenSkip→[E602]. This closes #657 by handling the remaining buckets — but the landed change is deliberately smaller than the audit's numeric targets, because implementing it surfaced a flaw in the audit's core premise.The audit assumed that after #658 every codegen leaf raises, making the PROPAGATE
if x is None: return Noneforwards dead (removable). That is false for the #630[E615]string-interpolation channel: when interpolation inference fails,_translate_interpolated_stringrecords the failing segments to_interp_inference_failuresand returnsNone, which propagates up and is dropped loudly as[E615]at the_compile_fnboundary. Sotranslate_expr/translate_blockstill returnNonereachably, and anyif x is None: return Nonethat forwards them is load-bearing PROPAGATE, not dead.Converting such a forward to
assert/raiseturns a graceful[E615]function-drop into a crash. The test suite caught this empirically: an over-eagerassertconversion firedAssertionErroracrossTestE615LoudInterpolationFallthrough630.Changes
raise CodegenInvariantError([E699]): 2 incodegen/closures.py, 19 inwasm/operators.py(slot-ref/pipe/float-op/dispatch-fallthrough/ADT-eq/result-ref/quantifier-shape/state-lookup). A compiler bug now surfaces as a structured[E699]"internal compiler error, file a bug", not a silent skip or a mis-attributed[E602]. Pinned by the newtests/test_codegen_invariant_e699.py(forces the raise via atranslate_blockmonkeypatch); the previously-# pragma: no coverhandler incodegen/functions.pyis now covered.[E615]): all the forwarding sites stayreturn None— satisfying Follow-up to #626: convert INVARIANT_DEFENSIVE sites + audit PROPAGATE cleanup #657's own acceptance criterion, "either preserved (still reachable) or removed (unreachable)." Fiveoperators.pysites the audit had tagged INVARIANT were in fact such forwards and are kept.inference.py's defensive sites are kept asOptional-by-contract (callers already raise[E602]).[E615]channel" section invera/skip.py's taxonomy docstring, a# #657 / #630 [E615]comment at each of the 5 preserved forwards, and a note at the canonicalNonesource (_translate_interpolated_string).Exit checklist
CodegenInvariantError→[E699](21 sites); reachable-via-E615 forwards preservedtests/test_codegen_invariant_e699.pypins the[E699]contract;functions.pyhandler un-pragma'dpytest·mypy·ruff·check_e602_clean· E615 suite (62) — green[0.0.191], HISTORY row + "By the numbers" footer, ROADMAP Follow-up to #626: convert INVARIANT_DEFENSIVE sites + audit PROPAGATE cleanup #657 removed, TESTING row, doc-counts (5,558 / 53)Closes #657
See the issue comment for the full re-scope rationale.
🤖 Generated with Claude Code
Summary by CodeRabbit