release: v0.0.173 — @Nat narrowing at projection & instantiation sites (#747, #749)#756
Conversation
…747 step 1) Infrastructure for #747: record per-expression semantic Type (synthesised result + the instantiated 'expected' target each value was checked against) in span-keyed side-tables during type-check (typecheck_with_artifacts), and thread them into ContractVerifier via verify() + the LSP VerificationSession. Adds _resolved_type_of / _target_type_of helpers. No behaviour change yet — the @nat narrowing walker consumes the tables in the following steps. Validation confirmed the 'expected' table already carries the instantiated @nat target at generic construction/call sites (Some(@Int.0) building Option<Nat> records the argument's target as Nat), so the deferred sites resolve with a single lookup rather than re-deriving substitutions. Co-Authored-By: Claude <[email protected]>
…747 step 2) Generalises the @nat >= 0 binding-site obligation to the three generic instantiation shapes #552 deferred: generic constructor fields (`Some(@Int.0)` building `Option<Nat>`), generic effect-op formals (`E<Nat>.wait(@Int.0)`), and generic function formals fixed to @nat by a sibling argument (`pick(@Nat.0, @Int.0)`). The verifier's `_nat_binding_target()` keeps the concrete-formal check (#552, table-independent) and, when the formal is a TypeVar, consults the checker's recorded *instantiated target* for the argument (`_target_type_of`, step 1). Two checker changes make every argument carry that target: `_check_op_call` resolves the effect type mapping before synthesising args (so op args are checked against their instantiated formal), and the generic-function path records the post-inference formal as each arg's target (the initial synth uses `expected=None` for generic calls, and `Int <: Nat` skips the re-synth). Tests: the deferred `_not_obligated_yet` pins become obligated + discharged pairs, plus a generic-function-formal pair; `_verify` now mirrors the CLI verify path (collects and threads the side-tables). `ch09_async`'s `async_helper` gains `requires(@Int.0 >= 0)`: `async` infers `Future<Nat>` from the Nat-typed `@Int.0 * 2`, a real narrowing now caught (the checker `Int * Nat = Nat` interaction is tracked as a follow-up). `verify()` now collects the side-tables itself when a caller omits them, so a bare `verify(program, source)` matches the table-threading CLI / LSP paths and the warm/cold differential oracle stays consistent. TESTING.md / ROADMAP.md test counts synced (4,414 -> 4,418). Co-Authored-By: Claude <[email protected]>
…site 1) Generalises the @nat >= 0 obligation to ADT sub-pattern bindings — `match opt { Some(@Nat.0) -> }` on `Option<Int>` binds the @int payload as @nat. A literal-constructor scrutinee (`match Some(@Int.0) { ... }`) binds the constructor's own AST argument, obligated directly; an opaque scrutinee binds an uninterpreted Z3 field accessor, obligated as a term (new `_check_nat_binding_obligation_term`). Field types come from `_instantiated_field_types`, mirroring the checker's `_check_ctor_pattern` (declared field types substituted against the scrutinee's resolved ADT type args). Only a genuine narrowing fires — an already-@nat field is skipped (its accessor carries no >= 0 fact, so a spurious obligation would fail). Site 2 (non-literal tuple destructure) stays deferred: the SMT layer models a tuple as a scalar (its Z3 sort is `Int`, not a projectable datatype), so a per-component `>= 0` obligation cannot be expressed. Closing it needs SMT tuple-datatype support — a separate prerequisite. Co-Authored-By: Claude <[email protected]>
…747 site 2) Closes the last deferred projection site: `let Tuple<@nat, @nat> = f()` where `f` returns `Tuple<Int, Int>` now obligates each @int component `>= 0`. The blocker was the SMT layer modelling a tuple as a scalar `Int` (not projectable), so a per-component obligation could not be expressed. `smt.py` now synthesises a single-constructor Z3 datatype on demand for each concrete `Tuple<...>` instance (`_get_or_create_tuple_sort`) — one field per component — so the components project through accessors. The verifier's `_obligate_destructure_narrowings` reads the RHS's resolved tuple type from the checker side-table and obligates only a *genuine* narrowing — a component whose source type is not already @nat — exactly the ADT-sub-pattern guard, since the projected accessor term carries no `>= 0` fact and an already-@nat source would fail the proof spuriously. A source the SMT layer cannot project into components (e.g. an `if`-expression over tuples, which it does not model as a datatype) surfaces one unguarded Tier-3 obligation + E504 rather than dropping the narrowing silently — the non-let untranslatable-value honesty path. The former deferral tripwire becomes a trio: a function-call source obligated and failing (E503), an already-@nat source not obligated (the soundness guard), and the if-expr source surfaced as Tier-3 E504. All 90 conformance programs and 35 examples still pass. Also refreshes the walker docstring and the generic-instantiation site comments, which still read "deferred to #747" after the earlier target-table commits made those sites fire — they now describe the implemented behaviour. Co-Authored-By: Claude <[email protected]>
…site 4) The last #747 narrowing site: a constructor imported from another module with an @nat field — concrete (`WrapN(Nat)`) or a generic field instantiated to @nat (`Wrap(@Int.0)` building `Box<Nat>`) — now obligates its @int argument `>= 0`. The verifier harvested only imported *functions* into its env, so `_lookup_constructor_info` returned None for an imported ctor and the ConstructorCall walker's `if ci is not None` guard skipped the obligation silently. `_register_modules` now also harvests each resolved module's data constructors into a flat `_module_constructors` fallback registry, consulted last by `_lookup_constructor_info` (after the builtin and local data-type lookups). The obligation falls on the local @int argument, so no imported-ADT SMT sort is needed — only the field types; over-harvesting is inert because the fallback is reached only for a name already resolved as a constructor. A concrete @nat field obligates directly; a generic field's instantiated @nat target still comes from the checker side-table (`_nat_binding_target`), exactly as the local generic-field site. Tests cover the concrete obligated + discharged pair and the generic obligated case across a two-module fixture. Co-Authored-By: Claude <[email protected]>
#552 emitted the runtime `value >= 0` guard only at `let @nat = <Int>`. #747 extends `_emit_nat_bind_guard` to five more @nat binding sites so an unverified compile traps on a negative @nat rather than silently storing it: * tuple destructure — guard each @nat component load * top-level match bind — `match <Int> { @nat -> }` * ADT sub-pattern bind — `match opt { Some(@nat) -> }` on Option<Int> * concrete ctor field — `WrapN(@Int.0)` where `WrapN(Nat)` * call argument — `f(@Int.0)` into a concrete @nat formal Projection sites (destructure, match/sub-pattern) guard on the @nat *target* slot — the bound value is a synthesized `i64.load`, sound because the guard only ever traps on a genuinely negative i64, never on a valid @nat in [0, 2^63). Value-flow sites (ctor field, call arg) guard the argument expression when the formal/field is a concrete @nat and `_narrows_into_nat(arg)` holds; an @int target is exempt. @nat is an i64 scalar, never a heap pointer, so each guard (tee/check/get, no alloc) is GC-neutral at every site. Plumbing: `ConstructorLayout` gains `nat_fields`; a `_fn_nat_params` registry (per-parameter concrete-@nat flags) is threaded codegen -> context alongside `_fn_ret_type_exprs`. Generic formals/fields fixed to @nat at the call site erase to i64 in codegen, so they stay statically-only (verifier-obligated). Two residuals stay a follow-up: the effect-op-argument runtime guard (`_effect_ops` carries no formal types — the rarest site, statically obligated and E504-flagged when Tier-3) and the dedicated @Nat-guard trap kind (#749 item 4 — needs the contract-fail host channel wired to a mid-expression guard). The guards trap correctly meanwhile, localized by the #516 source frame. Co-Authored-By: Claude <[email protected]>
…lization Closes the #749 review follow-up test debt (items 1-3) and refreshes the docs now that #747's static obligations cover every narrowing shape: * IndexExpr and InterpolatedString container-recursion pins — a narrowing nested in `arr[takes_nat(@Int.0)]` or `"\(takes_nat(@Int.0))"` is obligated, pinning the two walker branches a regression could drop. * `_fresh_slot_var` nat-alias unit test — a `type Count = Nat` slot resolves to a real Z3 nat var (same sort as a direct @nat), while an unknown ADT type yields None. * `_narrows_into_nat` verifier/codegen parity test — a value-expression corpus run through both hand-mirrored predicates, asserting they agree so a builtin added to one and not the other can't silently desync the static obligation from the runtime guard. `ch04_nat_binding.vera` gains a generic-constructor-field narrowing (`Some(@Int.0)` building `Option<Nat>`, discharged via requires) and an already-@nat sub-pattern bind (no spurious obligation). Docs: the #747 KNOWN_ISSUES / spec / README rows are rewritten — every narrowing shape is now statically obligated, leaving only the effect-operation-argument runtime guard and the dedicated trap-kind message as a residual, tracked as #754. Also files the residual (#754) and the `Int <op> Nat`-types-as-`Nat` checker imprecision (#755) surfaced by this work, and folds in the #753 pygls/Python-3.16 row (KNOWN_ISSUES). Co-Authored-By: Claude <[email protected]>
#747, #749) Bumps 0.0.172 -> 0.0.173 and cuts the CHANGELOG. The release bundles the already-accumulated [Unreleased] entries (the ruff lint gate #733 and the pytest 9.1.0 bump #742) with the #747 @Nat-narrowing generalization and the #749 review-debt fold-in shipped across this branch. Version bumped across vera/__init__.py, pyproject.toml, README, both docs landing pages, and uv.lock; HISTORY gains the v0.0.173 row and its active-days count ticks to 77; the regenerated llms.txt / llms-full.txt pick up the new version. Co-Authored-By: Claude <[email protected]>
|
Warning Review limit reached
More reviews will be available in 27 minutes and 32 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: ⛔ Files ignored due to path filters (7)
📒 Files selected for processing (34)
📝 WalkthroughWalkthroughReleases v0.0.173 of the Vera compiler by extending the Changes
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related issues
Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #756 +/- ##
==========================================
+ Coverage 91.60% 91.63% +0.02%
==========================================
Files 70 70
Lines 25218 25459 +241
Branches 321 321
==========================================
+ Hits 23101 23329 +228
- Misses 2109 2122 +13
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: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
vera/verifier.py (1)
2063-2072:⚠️ Potential issue | 🟠 Major | ⚡ Quick winKeep Tier-3 classification in sync with the new runtime guards.
This still treats every non-
letsite as unguarded E504, but the current guard plumbing covers call arguments, constructor fields, tuple destructures, match bindings, and ADT sub-pattern extraction; only effect-operation arguments are deferred. Otherwise runtime-checked obligations are reported as “not runtime-guarded” and excluded from the summary.Proposed fix
- if site == "let binding": + runtime_guarded_sites = { + "let binding", + "call argument", + "constructor field", + "tuple destructure", + "match binding", + "ADT sub-pattern bind", + } + if site in runtime_guarded_sites: self.summary.tier3_runtime += 1 self._record_obligation(decl.name, "nat_bind", value_node, status) else:🤖 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/verifier.py` around lines 2063 - 2072, The current code treats all non-let-binding sites as tier3_unguarded E504, but the guard plumbing now covers additional runtime-guarded sites: call arguments, constructor fields, tuple destructures, match bindings, and ADT sub-pattern extraction. Update the conditional logic in the else block to explicitly check for these runtime-guarded sites and increment tier3_runtime with a tier3_runtime status (similar to the let binding case), rather than treating them as unguarded E504. Only effect-operation arguments should remain as the unguarded E504 case. You will need to modify the condition structure to check for specific site values corresponding to call arguments, constructor fields, tuple destructures, match bindings, and ADT sub-pattern extraction before falling back to the unguarded behavior.vera/codegen/registration.py (1)
168-172:⚠️ Potential issue | 🟠 Major | ⚡ Quick winBuilt-in
MdHeadingis missingnat_fieldsmetadata, so itsNatfield is unguarded at runtime.You added
nat_fieldsgeneration for user ADTs (Line 255 onward), but built-ins are pre-seeded in_register_builtin_adts.MdHeadinghas a concrete Nat field at Line 170 and currently carries nonat_fields, so constructor-site runtime narrowing guard logic won’t trigger there.Suggested fix
self._adt_layouts["MdBlock"] = { ... "MdHeading": ConstructorLayout( tag=1, field_offsets=((8, "i64"), (16, "i32_pair")), total_size=24, + nat_fields=(True, False), ), ... }Also applies to: 255-279
🤖 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/registration.py` around lines 168 - 172, The MdHeading built-in type defined in the _register_builtin_adts function is missing the nat_fields metadata parameter in its ConstructorLayout definition. The MdHeading type has a Nat field (i32_pair at offset 16) that requires runtime narrowing guard logic, but without nat_fields metadata this guard won't be applied. Add the nat_fields parameter to the MdHeading ConstructorLayout to specify which fields contain Nat values, similar to how nat_fields generation is handled for user ADTs starting at line 255. This ensures that Nat fields are properly guarded at runtime for built-in types just as they are for user-defined types.
🤖 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 `@spec/11-compilation.md`:
- Line 56: The paragraph at the specified location mentions that the
effect-operation argument guard gap and the use of generic trap reporting
instead of a dedicated trap kind are deferred to issue `#754`, but section 11.17
(the limitations summary section) currently concludes with "No open
limitations." You need to update section 11.17 to either add a list of these
residual limitations (specifically the effect-operation binding site guard gap
and the generic trap reporting limitation) or modify the closing statement to
acknowledge that there are still open limitations tracked in `#754` rather than
claiming no limitations remain.
In `@TESTING.md`:
- Line 62: In the TESTING.md file on line 62, within the test_verifier.py row,
locate the `#749` section that mentions "IndexExpr/InterpolatedString
container-recursion pins" and replace the term "container-recursion" with
"walker-recursion" to accurately reflect how PR `#749` describes these pins. This
correction ensures the coverage note accurately represents the recursion type
being tested rather than using misleading terminology.
In `@tests/test_verifier.py`:
- Around line 2418-2436: The test test_imported_ctor_generic_field_nat_obligated
currently only verifies the violated case where a generic constructor field
instantiation fails the obligation. Add a companion test method that checks the
discharged/satisfied case by using a requires clause that actually satisfies the
obligation (such as requires(`@Int.0` >= 0) instead of requires(true)), and verify
that the obligations list contains zero violated entries in this case. This
ensures regression coverage for imported generic-field instantiation so it is
not always treated as violated.
In `@vera/codegen/registration.py`:
- Around line 39-46: The code in the `_fn_nat_params` assignment only recognizes
direct `NamedType("Nat")` instances but does not resolve type aliases or
refinements that ultimately resolve to `Nat`. For each parameter in
`decl.params`, resolve the parameter type through any aliases or refinements
first before checking if it equals `Nat`, then use that resolved type in the
tuple comprehension instead of just checking the raw parameter directly. Apply
the same fix to the similar code handling `nat_fields` around line 269.
In `@vera/verifier.py`:
- Around line 1951-1953: When scrutinee_z3 is None in the conditional block
where lit_args is also None, the current code silently returns and loses the
narrowing obligation for `@Nat` field bindings. Instead of returning early, record
a Tier-3 nat-bind outcome at the match or sub-pattern site to ensure the
runtime-guarded narrowing is captured in diagnostics and the verification
summary. This ensures that unprojectable sub-pattern narrowings are properly
tracked rather than being dropped.
- Around line 533-535: The constructor harvesting loop (iterating through
adt.constructors.items() and adding to _module_constructors with setdefault) is
collecting all constructors from all modules without filtering for visibility or
import status, causing the wrong constructor variant to be used when multiple
modules define constructors with the same name. Apply the same
public/import-name filtering logic that is used for functions when adding
constructors in this loop, or alternatively change the dictionary key structure
from a bare ctor_name to include the resolved module and declaration context so
that lookups can correctly disambiguate between constructors with identical
names from different modules. Look for how function filtering is implemented
elsewhere in the verifier to apply the same pattern to constructor filtering.
- Around line 2032-2035: The `_record_nat_bind_tier3()` method expects the
caller to have already incremented `summary.total` by one before the call, as it
subtracts one for unguarded sites. The direct call to `_record_nat_bind_tier3()`
in the tuple destructure case (when sort is None or idx is None) is missing this
prerequisite increment. Add an increment to `summary.total` by one immediately
before calling `_record_nat_bind_tier3()` to ensure the method's internal logic
correctly nets out the obligation count instead of corrupting the summary.
- Around line 1687-1694: The call to `_obligate_subpattern_narrowings()` in the
constructor pattern arm is being executed before the pattern condition
(pat_cond) is added to the SMT solver's path conditions, which allows Z3 to
consider invalid cases and produce false E503 failures. Ensure that the
`_obligate_subpattern_narrowings()` call is wrapped within the same
constructor-pattern guard condition that is used to protect the arm body proof,
so that pat_cond is already present in smt._path_conditions when the sub-pattern
field obligations are proven.
---
Outside diff comments:
In `@vera/codegen/registration.py`:
- Around line 168-172: The MdHeading built-in type defined in the
_register_builtin_adts function is missing the nat_fields metadata parameter in
its ConstructorLayout definition. The MdHeading type has a Nat field (i32_pair
at offset 16) that requires runtime narrowing guard logic, but without
nat_fields metadata this guard won't be applied. Add the nat_fields parameter to
the MdHeading ConstructorLayout to specify which fields contain Nat values,
similar to how nat_fields generation is handled for user ADTs starting at line
255. This ensures that Nat fields are properly guarded at runtime for built-in
types just as they are for user-defined types.
In `@vera/verifier.py`:
- Around line 2063-2072: The current code treats all non-let-binding sites as
tier3_unguarded E504, but the guard plumbing now covers additional
runtime-guarded sites: call arguments, constructor fields, tuple destructures,
match bindings, and ADT sub-pattern extraction. Update the conditional logic in
the else block to explicitly check for these runtime-guarded sites and increment
tier3_runtime with a tier3_runtime status (similar to the let binding case),
rather than treating them as unguarded E504. Only effect-operation arguments
should remain as the unguarded E504 case. You will need to modify the condition
structure to check for specific site values corresponding to call arguments,
constructor fields, tuple destructures, match bindings, and ADT sub-pattern
extraction before falling back to the unguarded behavior.
🪄 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: 983b4f39-8877-459e-9070-5766cc6feee0
⛔ Files ignored due to path filters (7)
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/**tests/conformance/ch04_nat_binding.verais excluded by!**/*.veratests/conformance/ch09_async.verais excluded by!**/*.verauv.lockis excluded by!**/*.lock,!uv.lock
📒 Files selected for processing (29)
CHANGELOG.mdHISTORY.mdKNOWN_ISSUES.mdREADME.mdROADMAP.mdTESTING.mdpyproject.tomlspec/11-compilation.mdtests/test_codegen.pytests/test_verifier.pyvera/README.mdvera/__init__.pyvera/checker/__init__.pyvera/checker/calls.pyvera/checker/core.pyvera/checker/expressions.pyvera/cli.pyvera/codegen/api.pyvera/codegen/closures.pyvera/codegen/core.pyvera/codegen/functions.pyvera/codegen/registration.pyvera/obligations/session.pyvera/smt.pyvera/verifier.pyvera/wasm/calls.pyvera/wasm/context.pyvera/wasm/data.pyvera/wasm/operators.py
The multi-agent /review-pr (PR #756) found no critical code bugs; this applies the actionable findings. Comment accuracy — the Step-5 codegen guards post-dated the Step-3 verifier docstrings, leaving stale claims the comment-analyzer caught: - `_walk_for_nat_binding_obligations`, `_check_nat_binding_obligation_term`, and `_obligate_destructure_narrowings` no longer say "codegen guards only the let site" / "no runtime guard yet" — codegen now guards five sites, so the E504 is a static-unverifiability signal, not a no-guard claim. - `_emit_nat_bind_guard` cites the live tracker #754 (was "#749 item 4"). - `_module_constructors` bare-name first-writer-wins keying and `ConstructorLayout.nat_fields`'s parallel-length invariant are documented. - `_obligate_subpattern_narrowings` notes nested sub-patterns aren't recursed (tracked as #754). Tests: - the cross-module imported-ctor runtime guard (#747 site 4) was emitted but untested — add a guard-presence + negative-traps pair. - add negative-traps run-tests for the tuple-destructure and ADT sub-pattern guards (previously only call-arg had one), proving guard *semantics*, not just emission. - the `_narrows_into_nat` parity test now asserts the soundness-relevant implication (codegen guards must be a superset of verifier obligations) rather than equality, and gains FnCall corpus entries — which surfaced that codegen over-guards `string_length` (a sound, harmless over-guard). Two further residuals tracked as #754/#755 comments (nested sub-pattern narrowing; the `Int <op> Nat`-typed-as-`Nat` destructure interaction). Co-Authored-By: Claude <[email protected]>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
vera/verifier.py (2)
97-108:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPopulate missing checker side-tables independently.
Line 97 only collects artifacts when both tables are omitted. If a caller supplies
expr_typesbut leavesexpr_target_types=None(or vice versa), the missing table becomes{}inContractVerifier, silently disabling either projection/source-type obligations or generic-instantiation@Nattargets.Proposed fix
- if expr_types is None and expr_target_types is None: + if expr_types is None or expr_target_types is None: # `#747`: when the caller didn't supply the checker's semantic-type # side-tables, collect them here so a bare verify() matches the CLI # (cmd_verify) and LSP (VerificationSession) paths — both of which # thread them — keeping the warm/cold differential oracle and any # external caller consistent. Lazy import avoids a module cycle. from vera.checker import typecheck_with_artifacts _diags, _arts = typecheck_with_artifacts( program, source, file=file, resolved_modules=resolved_modules, ) - expr_types = _arts.expr_semantic_types - expr_target_types = _arts.expr_target_types + if expr_types is None: + expr_types = _arts.expr_semantic_types + if expr_target_types is None: + expr_target_types = _arts.expr_target_types🤖 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/verifier.py` around lines 97 - 108, The condition at line 97 only collects artifacts when both expr_types and expr_target_types are None, but this causes issues when a caller supplies only one of these tables and leaves the other as None, resulting in the missing table being silently set to an empty dictionary in ContractVerifier. Change the condition to check if either expr_types is None OR expr_target_types is None, then after calling typecheck_with_artifacts, conditionally populate only the missing tables: if expr_types is None, set it to _arts.expr_semantic_types, and if expr_target_types is None, set it to _arts.expr_target_types.
2076-2084:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winStop saying guarded projection sites are unguarded.
The new comments at Lines 1458-1460 and 2029-2030 say codegen still guards projection/destructure sites at runtime, but this warning still tells users the narrowing “is not runtime-guarded” and “neither statically proven nor runtime-checked”. Split the message by site, or use wording that describes the static E504 gap without contradicting the runtime guard coverage.
Also applies to: 2108-2120
🤖 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/verifier.py` around lines 2076 - 2084, The docstring and warning message for the Tier-3 nat_bind outcome handling contains contradictory information. While comments at lines 1458-1460 and 2029-2030 indicate that codegen guards projection/destructure sites at runtime, the current message in this section states the narrowing "is not runtime-guarded" and "neither statically proven nor runtime-checked", which contradicts the runtime guard coverage. Revise the warning message to either split the explanation by site type (distinguishing guarded let sites from unguarded non-let sites with appropriate messaging for each) or reword it to accurately describe the static E504 gap without falsely claiming these sites lack runtime guards when they actually do have them for certain site types.
🤖 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/verifier.py`:
- Around line 97-108: The condition at line 97 only collects artifacts when both
expr_types and expr_target_types are None, but this causes issues when a caller
supplies only one of these tables and leaves the other as None, resulting in the
missing table being silently set to an empty dictionary in ContractVerifier.
Change the condition to check if either expr_types is None OR expr_target_types
is None, then after calling typecheck_with_artifacts, conditionally populate
only the missing tables: if expr_types is None, set it to
_arts.expr_semantic_types, and if expr_target_types is None, set it to
_arts.expr_target_types.
- Around line 2076-2084: The docstring and warning message for the Tier-3
nat_bind outcome handling contains contradictory information. While comments at
lines 1458-1460 and 2029-2030 indicate that codegen guards
projection/destructure sites at runtime, the current message in this section
states the narrowing "is not runtime-guarded" and "neither statically proven nor
runtime-checked", which contradicts the runtime guard coverage. Revise the
warning message to either split the explanation by site type (distinguishing
guarded let sites from unguarded non-let sites with appropriate messaging for
each) or reword it to accurately describe the static E504 gap without falsely
claiming these sites lack runtime guards when they actually do have them for
certain site types.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 241f30c9-84ba-4e13-9fd6-e7b51f9af7dd
📒 Files selected for processing (7)
ROADMAP.mdTESTING.mdtests/test_codegen.pytests/test_verifier.pyvera/codegen/api.pyvera/verifier.pyvera/wasm/operators.py
Eight findings from the initial CodeRabbit review, all valid: - **`summary.total` accounting** — `_obligate_destructure_narrowings`'s Tier-3 branch called `_record_nat_bind_tier3` (which nets out a non-let site by subtracting one) without the matching `+1`, corrupting the obligation count. The same `+1` is applied to the new sub-pattern Tier-3 path below. - **imported-ctor harvest** now filters to PUBLIC data types + the import-name filter (mirroring the function harvest), so a private or unimported same-named constructor can't shadow the resolved one with the wrong field types. - **match-arm sub-pattern obligation** is now proven under the arm's discriminant condition (`is-<Ctor>(scrutinee)`) rather than before it, so Z3 can't witness a negative accessor in a branch that never reads it (a potential false E503). - an **opaque, untranslatable sub-pattern scrutinee** now records a Tier-3 obligation + E504 instead of silently dropping the narrowing. - **runtime-guard metadata** (`_fn_nat_params`, `ConstructorLayout.nat_fields`) now resolves `@Nat` through `type X = Nat` aliases and refinement bases, so an alias/refinement-typed @nat formal or field is guarded too. Docs/tests: §11.17 now lists the #754 residual (was "No open limitations"); TESTING.md says "walker-recursion"; added the discharged companion for the imported generic-constructor narrowing. Co-Authored-By: Claude <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
ROADMAP.md (1)
23-23:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winTrim the stale
#747status text.This row still says the guard backs only the
letsite and that imported-constructor lookup is still needed. That is now out of date; the only remaining runtime gap is the effect-operation argument, tracked in#754. Please rewrite the row to reflect the current state.♻️ Suggested fix
-| [`#747`](https://github.com/aallan/vera/issues/747) | `@Nat` narrowing at projection binding sites | `let`/argument/effect-operation-argument/constructor-field/match-bind/literal-destructure narrowing is now obligation-checked (`#552`); ADT sub-pattern binds, non-literal destructures, generic constructor fields, effect-operation formals, and function formals instantiated to `@Nat`, and imported ADT constructors still need scrutinee/call-site type inference or imported-module constructor lookup to obligate, and the Tier-3 runtime guard backs only the let site. | +| [`#747`](https://github.com/aallan/vera/issues/747) | `@Nat` narrowing at projection binding sites | `let`/argument/effect-operation-argument/constructor-field/match-bind/literal-destructure narrowing is now obligation-checked (`#552`); ADT sub-pattern binds, non-literal destructures, generic constructor fields, effect-operation formals, and function formals instantiated to `@Nat`, and imported ADT constructors are now covered. The only remaining runtime gap is the effect-operation argument, tracked in `#754`. |Based on the PR summary and the updated spec text.
🤖 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 `@ROADMAP.md` at line 23, Update the row for issue `#747` in ROADMAP.md to remove stale information. The current text incorrectly states that the Tier-3 runtime guard backs only the `let` site and that imported-constructor lookup is still needed. Replace this outdated content to accurately reflect that these have been addressed, and specify that the only remaining runtime gap is now the effect-operation argument, which is tracked in issue `#754`. Ensure the updated text aligns with the current PR summary and the latest spec documentation.
🤖 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_verifier.py`:
- Around line 2465-2467: The test is currently only asserting that no `nat_bind`
obligations are violated, but it does not verify that a `nat_bind` obligation
with verified status actually exists. Add an additional assertion that
explicitly checks for the presence of at least one obligation where the kind is
"nat_bind" and the status is "verified". This should be done alongside the
existing assertion that checks for no violated nat_bind obligations to ensure
the regression test properly validates that the discharged path both emits and
verifies the nat_bind obligation as intended.
In `@vera/verifier.py`:
- Around line 2014-2016: The calls to _record_nat_bind_tier3() for ADT
sub-pattern bind and tuple-destructure sites (found in the methods handling
these patterns around the referenced lines) incorrectly treat these
codegen-guarded projection sites as unguarded, causing false E504 errors. Modify
these _record_nat_bind_tier3() calls to skip recording effect-operation
arguments through this path, or add a parameter to indicate these sites are
codegen-guarded rather than unguarded. Ensure that effect-operation arguments
are not downgraded to tier3_unguarded at these guarded projection sites, keeping
them on the unguarded path until the deferred runtime guard implementation is
complete.
- Around line 537-549: The condition at line 547 that checks if ctor_name is in
name_filter does not account for selective imports of data type names
themselves. When a data type name like List is imported via selective import,
its constructors should automatically be available. Modify the condition from
checking only if ctor_name is in name_filter to also check if adt_name (the
parent data type) is in name_filter, so that importing a data type declaration
via its name makes all of its constructors available in _module_constructors
through the setdefault call.
---
Outside diff comments:
In `@ROADMAP.md`:
- Line 23: Update the row for issue `#747` in ROADMAP.md to remove stale
information. The current text incorrectly states that the Tier-3 runtime guard
backs only the `let` site and that imported-constructor lookup is still needed.
Replace this outdated content to accurately reflect that these have been
addressed, and specify that the only remaining runtime gap is now the
effect-operation argument, which is tracked in issue `#754`. Ensure the updated
text aligns with the current PR summary and the latest spec documentation.
🪄 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: 4ca36987-27b0-45a0-a72b-1a92f373618b
📒 Files selected for processing (6)
ROADMAP.mdTESTING.mdspec/11-compilation.mdtests/test_verifier.pyvera/codegen/registration.pyvera/verifier.py
The `compute` async example narrowed an `@Int` into `async`'s generic formal instantiated to `@Nat` — a narrowing #747 newly obligates and #755 spuriously induces (the checker types `@Int.n * k` as `@Nat`). Switching its parameters to `@Nat` keeps the async/await demonstration intact while sidestepping the #755 mis-typing, so `check_spec_examples` (CI lint, not run in pre-commit) passes. Co-Authored-By: Claude <[email protected]>
Three findings from CodeRabbit's re-review of the round-1 fixes: - tier3 accounting — `_record_nat_bind_tier3` treated every non-`let` site as unguarded (E504), but #747's codegen guards the tuple-destructure, top-level match-bind, and ADT-sub-pattern sites too. A `_GUARDED_NAT_BIND_SITES` set now routes those to `tier3_runtime` (a guarded runtime check) instead of a false E504; only the effect-op argument and generic value-flow sites stay unguarded. The if-expr-destructure test asserts the guarded outcome. - imported-ctor filter — the round-1 public/import-name filter was too strict: `import m(List)` names the data type, not its `Cons`/`Nil` ctors, so they were skipped. A ctor is now harvested when the import names it OR its data type (or on a wildcard import). - the imported generic-ctor discharged test now asserts the `nat_bind` obligation is present and `verified`, not merely "not violated". Also deletes the now-completed #747 ROADMAP row (the residual is tracked in #754 / KNOWN_ISSUES; the HISTORY row landed with the release). Co-Authored-By: Claude <[email protected]>
|
@coderabbitai Outside-diff (ROADMAP.md): fixed in e207a7d — the stale #747 row is deleted entirely (this PR completes #747; the HISTORY row landed with the release and the effect-op-arg / trap-kind residual is tracked in #754). |
|
Tip For best results, initiate chat on the files or code changes.
|
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 `@vera/verifier.py`:
- Around line 126-136: The _record_nat_bind_tier3() function currently
classifies all Tier-3 narrowings for "call argument" and "constructor field"
sites the same way, treating both concrete and generic-instantiated forms as
unguarded and reporting them as E504. However, concrete `@Nat` function formals
and constructor fields are actually runtime-guarded by codegen and should be
distinguished from the generic-instantiated value-flow cases that remain
unguarded. Modify _record_nat_bind_tier3() to accept and pass a runtime_guarded
parameter that should be True for concrete forms and False for
generic-instantiated forms and effect-operation arguments, ensuring the correct
distinction is preserved when reporting narrowing issues.
- Around line 2090-2099: The current code records only one Tier-3 obligation for
unprojectable tuple component narrowing, but when a tuple contains multiple `@Nat`
components, each should be counted separately to match the behavior of the
literal and projectable paths. Modify the code to iterate through the components
in the narrowing constraints and record or count a Tier-3 obligation for each
`@Int` -> `@Nat` narrowing found, rather than recording just once. This ensures that
let Tuple<`@Nat`, `@Nat`> = opaque_tuple() correctly counts both Nat components as
guarded runtime obligations instead of undercounting.
🪄 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: 4b5b7628-cce7-4e30-9cd3-d60c87366bcf
📒 Files selected for processing (4)
ROADMAP.mdTESTING.mdtests/test_verifier.pyvera/verifier.py
💤 Files with no reviewable changes (1)
- ROADMAP.md
…(ruff S112) The accessor guard in `_obligate_destructure_narrowings` was unreachable (`i` is filtered into `narrowing` against `source_args`, whose length matches the tuple sort's fields) and inconsistent with the unguarded sub-pattern accessor. Removing it clears the lone `ruff --select S` (S112) violation, which CI's lint job runs beyond the default rules — latent until the §9.5.4 spec fix let the lint job reach the security check. Co-Authored-By: Claude <[email protected]>
…lassification CodeRabbit (PR #756) flagged two issues in _record_nat_bind_tier3: 1. Classifying guarded-vs-unguarded by the coarse `site` string reported every Tier-3 "call argument" / "constructor field" narrowing as an unguarded E504 -- including the *concrete* @nat forms that codegen DOES runtime-guard (wasm/calls.py, wasm/data.py). Only the generic-instantiated value-flow cases (whose @nat erases to i64) and effect-op arguments (unguarded, #754) should warn. 2. The untranslatable tuple-destructure fallback recorded a single Tier-3 obligation even when the destructure binds multiple @nat components, so `let Tuple<@nat, @nat> = opaque()` undercounted. Fix: _record_nat_bind_tier3 now takes an explicit `guarded: bool` the caller supplies from the formal/field type (_is_nat_type), replacing the _GUARDED_NAT_BIND_SITES site-string set. Call-arg and ctor-field pass guarded=self._is_nat_type(formal/field_ty) -- concrete guarded, generic not; effect-op-arg passes guarded=False; the let/destructure/match/sub-pattern sites keep the guarded default. The destructure fallback records one Tier-3 per @nat component, matching the projectable path. Updates the E504 warning/rationale text (references #754 for the residual unguarded sites, lists the actual guarded site set), spec chapter 6 and TESTING.md to match, and the three affected tests: - test_if_expr_destructure_tier3_runtime: 2 per-component obligations - test_non_let_tier3_narrowing_warns_unguarded: rewritten onto the effect-op argument, the genuinely-unguarded site (the old nat_to_int(array_length()) call-arg is now codegen-guarded by this PR's Step 5) - test_overall_tier_counts: 256/28/284, 0 tier3_unguarded (string_utilities' concrete call-arg narrowings are now correctly codegen-guarded tier3_runtime) Co-Authored-By: Claude <[email protected]>
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 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 `@CHANGELOG.md`:
- Line 13: The arithmetic in the ruff findings summary is incorrect. Locate the
text "~176 findings had accumulated silently" in the CHANGELOG entry and change
the approximate total from "~176" to "~175" to accurately reflect the sum of the
134 auto-fixed findings and 41 hand-triaged findings that are explicitly
mentioned in the same entry.
In `@HISTORY.md`:
- Line 3: The roll-up footer line in HISTORY.md is out of sync with the header
statement. The header indicates 77 active development days, but the footer still
reports 76 active days and 173 tagged releases. Update the footer line to change
the active development days count from 76 to 77 to match the header.
Additionally, verify that the tagged releases count of 173 in this footer
matches the releases figure in README.md to ensure the two files remain
synchronized per coding guidelines.
In `@TESTING.md`:
- Line 62: In the test_verifier.py row within the `@Nat` binding-site narrowing
obligation section, the phrase describing which narrowings surface the E504
unguarded warning currently mentions both effect-op-arg and generic-instantiated
narrowings. Since `#747` now covers generic-instantiated `@Nat` formals, remove the
reference to generic-instantiated narrowings from the residual warning note,
leaving only the effect-op-arg scope that is tracked in the `#754` PR objective.
In `@tests/test_codegen.py`:
- Around line 16533-16547: The `_body` method uses plain substring matching with
`wat.find(f"(func ${fn}")` which can incorrectly match similarly-prefixed WAT
symbols (e.g., matching `$gcall_helper` when searching for `$gcall`). Replace
the substring find calls with a boundary-safe regex pattern that matches the
function name with word boundaries to ensure you only extract the correct
function body. Use regex to search for the opening function definition pattern
and closing boundary in a way that prevents false matches on similarly-named
symbols.
In `@tests/test_verifier.py`:
- Around line 2431-2445: The test method
test_imported_ctor_concrete_nat_field_discharged is not pinning down that the
nat_bind obligation is actually emitted and verified. Currently it only asserts
the absence of violated nat_bind obligations and error diagnostics, which means
the test would pass even if no nat_bind obligations were emitted at all. Add an
assertion to verify that at least one nat_bind obligation with status equal to
"verified" exists in result.obligations, similar to how the generic discharged
companion test handles this, to ensure the test catches real compiler behavior
rather than just the absence of failures.
In `@vera/codegen/registration.py`:
- Around line 277-303: The _type_resolves_to_nat method resolves alias bodies by
name only without applying type argument substitution, so generic aliases like
type Id<T> = T used as Id<Nat> resolve to T instead of Nat. When retrieving the
alias from self._type_aliases.get(te.name) and before assigning it to te for the
next loop iteration, check if te (the NamedType) has type arguments, and if so,
apply those type argument substitutions to the alias body before using it. This
ensures that generic type aliases with concrete type arguments are properly
resolved to their instantiated types rather than their unsubstituted template
forms.
In `@vera/verifier.py`:
- Around line 97-108: The condition checking whether to recompute artifacts uses
AND operator to verify that both expr_types and expr_target_types are None, but
it should use OR to check if either one is missing. Change the condition from
expr_types is None and expr_target_types is None to expr_types is None or
expr_target_types is None. Additionally, after calling typecheck_with_artifacts,
selectively assign only the missing tables by adding conditional checks: assign
expr_types from _arts.expr_semantic_types only if expr_types is None, and assign
expr_target_types from _arts.expr_target_types only if expr_target_types is
None. This ensures that partially-provided tables are completed consistently and
`#747` narrowing checks fire correctly.
In `@vera/wasm/data.py`:
- Around line 218-225: The runtime guard detection at lines 224, 454, and 569
uses a simple string comparison `type_name == "Nat"` which fails to detect Nat
aliases and refinement-wrapped Nat types, leading to missed guards. Replace the
direct `type_name == "Nat"` checks with an alias-aware Nat detection method that
properly resolves aliases and refinement types, similar to how constructor and
call metadata paths already handle Nat detection. Apply this change consistently
across all three guard emission sites where `_emit_nat_bind_guard` is called.
🪄 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: 14128e2a-75e0-4372-ae40-2ec30573a60b
⛔ Files ignored due to path filters (7)
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/**tests/conformance/ch04_nat_binding.verais excluded by!**/*.veratests/conformance/ch09_async.verais excluded by!**/*.verauv.lockis excluded by!**/*.lock,!uv.lock
📒 Files selected for processing (31)
CHANGELOG.mdHISTORY.mdKNOWN_ISSUES.mdREADME.mdROADMAP.mdTESTING.mdpyproject.tomlspec/06-contracts.mdspec/09-standard-library.mdspec/11-compilation.mdtests/test_codegen.pytests/test_verifier.pyvera/README.mdvera/__init__.pyvera/checker/__init__.pyvera/checker/calls.pyvera/checker/core.pyvera/checker/expressions.pyvera/cli.pyvera/codegen/api.pyvera/codegen/closures.pyvera/codegen/core.pyvera/codegen/functions.pyvera/codegen/registration.pyvera/obligations/session.pyvera/smt.pyvera/verifier.pyvera/wasm/calls.pyvera/wasm/context.pyvera/wasm/data.pyvera/wasm/operators.py
…e-table, test pins, doc counts) CodeRabbit's full review of PR #756 surfaced 8 findings; this commit addresses all (7 fixes + 1 evidence-based reword). Soundness (Major): - registration.py `_type_resolves_to_nat`: apply alias type-argument substitution so a generic alias instantiated to Nat (`type Id<T> = T` used as `Id<Nat>`) resolves to Nat rather than the bare type-param `T`, closing a missed runtime guard for generic-alias Nat ctor fields / formals. - wasm/data.py + wasm/context.py: the four bind-site runtime guards (let, top-level match, ADT sub-pattern, tuple-destructure component) gated on `type_name == "Nat"` now resolve through `_resolve_base_type_name`, so a `type Age = Nat` alias / refined-Nat target is guarded too (was silently skipped on a verification-less compile). Provable no-op for non-aliases. - verifier.py `verify()`: recompute the checker side-tables when EITHER is missing (was `and`), so a caller supplying only one table no longer leaves the other empty and silently under-fires the #747 generic-instantiation checks. Tests: - test_codegen.py `_body`: boundary-safe regex so `$gcall` does not slice `$gcall_helper`'s body. Add two alias-guard regression pins (Nat-alias let-bind, generic-alias ctor field). - test_verifier.py: pin the concrete imported-ctor discharged test to `status == ["verified"]` (was satisfiable by no obligation emitted at all). Docs: - CHANGELOG: ruff total ~176 -> ~175 (134 auto-fixed + 41 hand-triaged). - HISTORY footer: 173 -> 174 tagged releases, 76 -> 77 active days (sync with the intro header and the README status line). - TESTING: reworded the Nat E504-residual note. Kept generic-instantiated — empirically it DOES reach E504 (a generic formal erases to i64, so codegen cannot guard it), so removing it as suggested would make the doc inaccurate; fixed only the muddled shared parenthetical. Synced test/line counts. - ROADMAP: test count 4441 -> 4443. Co-Authored-By: Claude <[email protected]>
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
vera/verifier.py (1)
1705-1749:⚠️ Potential issue | 🟠 Major | ⚡ Quick winShadow pattern bindings when the scrutinee is not translatable.
If
smt.translate_expr(expr.scrutinee)returnsNone,arm_envstays as the outerslot_env, but Vera pattern bindings are still in scope. Obligations inside the arm can then read a stale outer@T.0or miss the new binding entirely. Use fresh pattern slots in this fallback path, matching theLetDestructstale-binding guard.Proposed direction
scrutinee_z3 = smt.translate_expr(expr.scrutinee, slot_env) for arm in expr.arms: arm_env = slot_env pat_cond = None if scrutinee_z3 is not None: bound = smt._bind_pattern( scrutinee_z3, arm.pattern, slot_env, ) if bound is not None: arm_env = bound pat_cond = smt._pattern_condition( scrutinee_z3, arm.pattern, ) + else: + arm_env = self._bind_fresh_pattern_slots( + smt, arm.pattern, slot_env, + )def _bind_fresh_pattern_slots( self, smt: SmtContext, pattern: ast.Pattern, env: SlotEnv, ) -> SlotEnv: if isinstance(pattern, ast.BindingPattern): slot_name = smt._type_expr_to_slot_name(pattern.type_expr) slot_val = self._fresh_slot_var(smt, pattern.type_expr) if slot_name is not None and slot_val is not None: return env.push(slot_name, slot_val) return env if isinstance(pattern, ast.ConstructorPattern): cur = env for sub_pattern in pattern.sub_patterns: cur = self._bind_fresh_pattern_slots(smt, sub_pattern, cur) return cur return env🤖 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/verifier.py` around lines 1705 - 1749, When the scrutinee cannot be translated to Z3 (scrutinee_z3 is None), pattern bindings in match arms are not properly shadowed, causing stale or missing bindings in downstream obligations. Add the proposed _bind_fresh_pattern_slots method to the Verifier class to recursively create fresh slot variables for BindingPattern and ConstructorPattern cases. In the match expression handling where arm_env is initialized to slot_env, check if scrutinee_z3 is None and call _bind_fresh_pattern_slots to update arm_env with fresh pattern slots instead of leaving it as the outer slot_env, ensuring pattern bindings are properly shadowed similar to how LetDestruct handles this case.ROADMAP.md (1)
73-73:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winTrim the
#749row to the remaining work.The row still lists the
IndexExpr/InterpolatedStringpins and the_fresh_slot_var/_narrows_into_nattests as future work, but the PR summary says those items were folded in and the residual trap-kind work is tracked in#754. Please delete the completed items or drop the row if nothing remains.
As per coding guidelines, completed items should be deleted fromROADMAP.mdonce they land, with the residual tracked separately.🤖 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 `@ROADMAP.md` at line 73, In the ROADMAP.md file, locate the `#749` row and remove the completed items from it: the container IndexExpr/InterpolatedString test pins, the _fresh_slot_var alias unit-test, and the _narrows_into_nat differential test. Either trim the row to only include the dedicated trap-kind work if it warrants separate tracking, or delete the entire `#749` row if the remaining work is already captured in the related `#754` issue, following the guideline that completed roadmap items should be removed once they land with residual work tracked separately.Source: Coding guidelines
🤖 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 `@ROADMAP.md`:
- Line 11: There is a discrepancy between the test count reported in ROADMAP.md
(line 11 shows 4,443 tests) and the test count in README.md's project-status
section (which shows 4,436 tests). Verify the actual current test count from the
live test suite, then update both ROADMAP.md and README.md to reflect the same
correct count, ensuring consistency across all documentation references to test
totals.
In `@spec/06-contracts.md`:
- Line 248: The final sentence describing which cases surface E504 warnings is
ambiguous about unguarded narrowing sites. The sentence currently mixes
effect-operation arguments and generic formals/fields in a way that suggests the
latter only applies when `@Nat` erases to i64, but both are actually separate
residual classes that surface E504. Revise the sentence to explicitly call out
two distinct unguarded classes: effect-operation arguments as one site-kind
residual, and generic-instantiated formals/fields as a separate residual, making
clear both surface E504 warnings independently rather than treating them as
conditionally related.
In `@spec/11-compilation.md`:
- Line 596: The section summary in §11.17 currently states that only the
effect-operation argument is an uncovered binding site for the `@Nat` >= 0
narrowing invariant, but the detailed coverage in §11.2.1 identifies an
additional residual: generic-instantiated `@Nat` formals and fields on the E504
path are also left uncovered. Update the summary in §11.17 to reflect both
residuals tracked in issue `#754` rather than just the effect-operation argument
site-kind gap, ensuring the summary accurately represents all coverage gaps
documented in the detailed sections.
In `@tests/test_codegen.py`:
- Around line 16552-16571: The guard-shape assertions in
test_param_destructure_nat_components_guarded and
test_subpattern_nat_bind_guarded are incomplete. Currently each test only
asserts the presence of "i64.lt_s" in the compiled WAT output, but this can miss
regressions where the comparison is emitted without the trap edge. Strengthen
each assertion by adding an additional check for "unreachable" in the same WAT
body, ensuring both the comparison instruction and the unreachable trap
instruction are present together, which verifies the complete guard
implementation.
- Around line 16572-16579: The test_toplevel_match_nat_bind_guarded test only
verifies the structural WAT output by checking for the presence of the i64.lt_s
instruction, but does not test runtime trapping behavior. Add a runtime
negative-input test case that calls the gmatch function with an invalid input (a
negative integer that violates the `@Nat` guard constraint) and verifies that the
function traps or raises an exception at runtime. This ensures that the
narrowing and guard semantics are actually enforced during execution, not just
present in the compiled code structure.
In `@vera/codegen/registration.py`:
- Around line 271-275: The nat_fields tuple in manually registered concrete-Nat
built-ins like MdHeading is not being populated and remains as an empty default
tuple, which prevents the proper validation guards from being applied during
constructor emission. Identify where manual built-in layouts are being
registered (look for explicit ConstructorLayout instantiations outside of
_compute_constructor_layout), and for those layouts that have concrete Nat
fields (such as MdHeading with its first Nat field), populate the nat_fields
parameter with a tuple containing the indices of the Nat-type fields instead of
leaving it empty, ensuring the i64.lt_s/unreachable guard is properly generated
to prevent invalid negative values from being stored as Nat.
In `@vera/README.md`:
- Line 690: In the README.md file within the "Verification gaps that downgrade
silently" table row, the issue references `#754` and `#730` do not exist in the
aallan/vera repository. Remove these non-existent issue references or replace
them with verified tracking issue numbers if they exist. Verify that `#555`
remains correctly referenced as it is documented in KNOWN_ISSUES.md and tracks
the generic forall body verification gaps mentioned in the row description.
In `@vera/verifier.py`:
- Around line 1468-1481: The comments at the projection site handling (around
the ADT sub-pattern bind and non-literal tuple destructure discussion) describe
outdated behavior where non-let Tier-3 paths were not runtime-backed. Update
these comments to reflect that the current implementation records guarded tuple
destructure, match-bind, and ADT sub-pattern fallbacks as tier3_runtime by
passing guarded=True. Clarify that these paths are now treated as
runtime-backed/guarded in the current accounting scheme, rather than as
unguarded paths that become E504 errors. Apply the same updates to the related
comments also mentioned in the affected line ranges to maintain consistency
across all similar documentation.
---
Outside diff comments:
In `@ROADMAP.md`:
- Line 73: In the ROADMAP.md file, locate the `#749` row and remove the completed
items from it: the container IndexExpr/InterpolatedString test pins, the
_fresh_slot_var alias unit-test, and the _narrows_into_nat differential test.
Either trim the row to only include the dedicated trap-kind work if it warrants
separate tracking, or delete the entire `#749` row if the remaining work is
already captured in the related `#754` issue, following the guideline that
completed roadmap items should be removed once they land with residual work
tracked separately.
In `@vera/verifier.py`:
- Around line 1705-1749: When the scrutinee cannot be translated to Z3
(scrutinee_z3 is None), pattern bindings in match arms are not properly
shadowed, causing stale or missing bindings in downstream obligations. Add the
proposed _bind_fresh_pattern_slots method to the Verifier class to recursively
create fresh slot variables for BindingPattern and ConstructorPattern cases. In
the match expression handling where arm_env is initialized to slot_env, check if
scrutinee_z3 is None and call _bind_fresh_pattern_slots to update arm_env with
fresh pattern slots instead of leaving it as the outer slot_env, ensuring
pattern bindings are properly shadowed similar to how LetDestruct handles this
case.
🪄 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: 352abdc0-57b2-4965-ae7a-82b878d105c0
⛔ Files ignored due to path filters (7)
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/**tests/conformance/ch04_nat_binding.verais excluded by!**/*.veratests/conformance/ch09_async.verais excluded by!**/*.verauv.lockis excluded by!**/*.lock,!uv.lock
📒 Files selected for processing (31)
CHANGELOG.mdHISTORY.mdKNOWN_ISSUES.mdREADME.mdROADMAP.mdTESTING.mdpyproject.tomlspec/06-contracts.mdspec/09-standard-library.mdspec/11-compilation.mdtests/test_codegen.pytests/test_verifier.pyvera/README.mdvera/__init__.pyvera/checker/__init__.pyvera/checker/calls.pyvera/checker/core.pyvera/checker/expressions.pyvera/cli.pyvera/codegen/api.pyvera/codegen/closures.pyvera/codegen/core.pyvera/codegen/functions.pyvera/codegen/registration.pyvera/obligations/session.pyvera/smt.pyvera/verifier.pyvera/wasm/calls.pyvera/wasm/context.pyvera/wasm/data.pyvera/wasm/operators.py
…ng edit) The round-15 docstring-accuracy fix to test_array_nat_element_subtraction_obligated added one line after check_doc_counts had already been run, so the committed TESTING.md line count lagged by one and the CI doc-counts gate failed. Count only. Co-Authored-By: Claude <[email protected]>
|
@coderabbitai full review |
✅ Action performedFull review finished. |
|
@coderabbitai resume |
|
@coderabbitai full review |
✅ Action performedReviews resumed. |
✅ Action performedFull review finished. |
…ng guard (review of #756) Independent multi-agent review of this PR surfaced that the "every narrowing shape is statically obligated, so verified programs are sound" wording is overstated: a `vera verify`-clean program can still store a negative `@Nat` at two narrowing *positions* the obligation walker never reaches — - a function return slot: fn f(@int -> @nat) ensures(true) { @Int.0 } -> verifies clean, f(0-5) returns -5 - a @nat component built in value position: fn g(@int -> @tuple<Nat,Nat>) { Tuple(@Int.0, 5) } -> verifies clean, no obligation on the component Both confirmed firsthand and both pre-existing (present on v0.0.172) — not a regression in #747/#756 — but this PR is where the universal-soundness wording landed. Filed #758 for the obligation gap; this commit makes the docs honest: - Scope the six overstated claims to "every narrowing BINDING SITE", with an explicit carve-out pointing at #758: CHANGELOG.md, KNOWN_ISSUES.md, vera/README.md, spec/06-contracts.md, spec/11-compilation.md (x2). The narrow claim that the two deferred *runtime-guard* residuals (#754, #757) are statically obligated is true and is retained. - KNOWN_ISSUES: the "builtin @nat parameters" guard claim was incomplete — nat_to_int/nat_to_string take @nat, are E503-obligated, but bypass the _fn_nat_params guard loop (nat_to_int(0-9) returns -9 unguarded). Scoped the guarded set to the string/markup builtins and listed the conversion builtins among the unguarded-but-obligated residuals. Also closes a test gap the review found: md_has_heading's @nat `level` guard (round-14 #757 fold-in) was emitted but untested — its @MdBlock/@Bool signature kept it out of the @String-returning parametrized trap test. Added a dedicated negative-level trap test. No compiler logic changed — docs + one test. Doc counts synced (4459). Co-Authored-By: Claude <[email protected]>
Independent multi-agent review (post-CR)Ran a 5-agent review (code / tests / comments / silent-failures / type-design) over the full 1. Overstated soundness wording (the headline). Three agents + a firsthand repro showed the "every narrowing shape is statically obligated, so verified programs are sound" claim is too strong — two narrowing positions are not obligated, so a
Both are pre-existing (reproduced on v0.0.172, base 2. Incomplete builtin-guard inventory. 3. Untested guard. No compiler logic changed in this commit — docs + one test; doc counts synced. |
|
@coderabbitai full review |
✅ Action performedFull review finished. |
The multi-agent review of #756 surfaced four small, non-correctness follow-ups. Filed as two issues and tracked in their natural homes: - #759 (refactor) -> ROADMAP Tier 2 (single source of truth): single-source the checker/verifier span-key constructor, enforce the nat_fields/field_offsets length invariant via a ConstructorLayout.__post_init__ assertion, and mirror the expected= side-table write into _check_ability_op_call. - #760 (testing) -> KNOWN_ISSUES Test coverage gaps: pin the #757 generic-ctor-field runtime deferral with an assertion, symmetric with #754. Co-Authored-By: Claude <[email protected]>
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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 `@KNOWN_ISSUES.md`:
- Line 23: The final two sentences of this KNOWN_ISSUES row conflate static
compile-time obligations with runtime-unguarded sites, making it unclear which
failures occur where. Reorganize the text to clearly separate two distinct
issues: first, identify which specific sites have static obligations that
produce E503/E504 compile errors, then separately describe which different sites
remain runtime-unguarded in unverified compiles and would trip a generic
unreachable rather than the proper requires check. This separation will prevent
overstating what fails at compile time versus what still traps at runtime.
In `@spec/06-contracts.md`:
- Line 248: The paragraph currently presents the generic function-formal calls
guard as universally protective, but this needs qualification. Add a caveat to
the sentence discussing "generic function-formal calls, which guard on the
monomorphised callee" to acknowledge that while concrete guarded call-arguments
work reliably, generic-instantiated `@Nat` formals can still erase to i64 and
surface E504 errors when the expression is untranslatable, similar to the caveat
already noted for generic-instantiated constructor fields staying unguarded.
In `@spec/11-compilation.md`:
- Line 56: The paragraph describing the Tier-3 runtime guard coverage is
incomplete. Locate the sentence that currently states "The Tier-3 runtime guard
backs every binding site except the effect-operation argument" and expand it to
also exclude generic-instantiated constructor fields from having runtime guards,
since the verifier/codegen path leaves these unguarded. Update the sentence to
mention both the effect-operation argument and constructor-field residuals as
sites where the runtime guard does not apply, ensuring consistency with section
11.17 which already reflects this split.
In `@TESTING.md`:
- Line 62: The current description in the test_verifier.py row conflates the
guarded cases with all generic function-formal call-arg handling, making it
appear that all such cases are protected by the monomorphised callee guard.
Revise the text to explicitly separate the concrete guarded cases from the
residual path where untranslatable generic-instantiated formals and fields can
still erase to i64 and surface E504 warnings. Keep the unguarded residual path
explicit in the documentation rather than collapsing it into the guarded
description, ensuring readers understand that generic instantiation does not
universally prevent E504 emissions.
🪄 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: 6c3babd1-2450-4f1d-8aa2-6d34bf1b4ddd
📒 Files selected for processing (12)
CHANGELOG.mdHISTORY.mdKNOWN_ISSUES.mdROADMAP.mdTESTING.mdspec/06-contracts.mdspec/11-compilation.mdtests/test_codegen.pytests/test_verifier.pyvera/README.mdvera/verifier.pyvera/wasm/calls_strings.py
Two of four CodeRabbit doc findings were valid: - KNOWN_ISSUES: separated the static-obligation axis (E503/E504 at binding sites) from the runtime-unguarded axis (which sites store a negative silently) — the row had interleaved them. - spec/11 §11.2: the "every binding site except the effect-operation argument" sentence omitted the generic-instantiated constructor field (also unguarded, #757); named both, consistent with §11.17, and fixed the ref (#757, not #754, for the ctor field). The other two findings (spec/06, TESTING.md — "generic function-formal calls can surface E504 when untranslatable") were declined as empirically false. Generic function-formal call-args are guarded=True (verifier.py:1541, guarded on the monomorphised callee pick$Nat), so an untranslatable arg falls to tier3_runtime with 0 E504; only the effect-op arg and the generic-ctor-field (guarded=False) reach E504. Verified: pick(@Nat.0, array_length(...)) -> 0 E504, vs the same arg in Some(...) -> 1 E504. Co-Authored-By: Claude <[email protected]>
|
@coderabbitai full review |
✅ Action performedFull review finished. |
…ee (PR #778) CodeRabbit outside-diff Critical (verifier.py match-arm walker): when a `match` scrutinee is untranslatable (an effect operation, `Source.next(())`), translate_expr returns None, so the arm kept the outer slot env. A binding pattern still introduces a slot, so a primitive op in the arm discharged against a STALE same-name outer: `match Source.next(()) { Some(@int) -> 1 / @Int.0 }` under `requires(@Int.0 != 0)` verified clean while the matched field can be 0 at runtime — a silent false-discharge, the worst #680 failure class. Fix: when the scrutinee is opaque, shadow the arm's pattern slots via _fresh_pattern_env (the nat-binding walker's existing helper, CR #756), extended with a `track` flag so the fresh vars are recorded in self._opaque_shadows — a bare fresh @int carries no `!= 0` invariant, so without the shadow tag the division would flip from false-discharge to a false E526. The op now falls to Tier-3 (runtime-guarded), mirroring the untranslatable-let shadow. Test (test-first, RED->GREEN proven against the pre-fix status): test_opaque_match_scrutinee_shadows_arm_bindings (verified -> tier3). Co-Authored-By: Claude <[email protected]>
Summary
Generalizes the
@Nat >= 0binding-site narrowing obligation (#552) to every projection and instantiation site, and folds in the #749 review-debt. Cuts release v0.0.173.After #552, the verifier obligated
@Natnarrowings only where it could read the target type directly (let, call args, effect-op args, concrete ctor fields, top-level match binds, literal-tuple destructures). This PR closes the residual six shapes plus the runtime-guard gap.Static obligations — all six #747 shapes
The enabler:
_synth_exprnow records each expression's semantic and instantiated-target type in span-keyed side-tables, threaded into the verifier, so the deferred sites resolve their@Nattarget with one lookup.match opt { Some(@Nat) -> }onOption<Int>let Tuple<@Nat, @Nat> = f(), via new Z3 tuple-datatype support in the SMT layer so components are projectable@Nat—Some(@Int.0)buildingOption<Nat>@Natat the call site@Natfields — a_module_constructorsregistry harvested in_register_modulesEvery narrowing shape now verifies statically, so a
vera verify-clean program is sound.Runtime guards
_emit_nat_bind_guardnow also fires at the tuple-destructure, top-level match-bind, ADT-sub-pattern, concrete-constructor-field, and call-argument sites (five of six), so an unverified compile traps on a negative@Natrather than storing it silently. Projection sites guard the@Nattarget slot; value-flow sites guard a concrete@Natformal/field when_narrows_into_nat(arg).@Natis an i64 scalar, so each guard is GC-neutral. Builtin@Natparameters (string_repeat,string_pad_start/_end,string_from_char_code,md_has_heading) are guarded too — folded in from #757;string_sliceis exempt, since it clamps its indices to[0, len]and a negative argument can't underflow.#749 fold-in
Review-debt test pins: IndexExpr / InterpolatedString walker-recursion
_verify_errpins, a_fresh_slot_varnat-alias unit test, and a_narrows_into_natverifier/codegen parity test over a value-expression corpus.#757 fold-in (builtin
@Nat-param guards)Round 14 folded in the builtin half of #757:
_emit_nat_bind_guardnow also fires on the builtin translators that consume a narrowing@Natargument unsafely (above). #757 stays open, re-scoped to its remaining half — the generic-instantiated constructor field (Wrap(@Int.0)building aBox<Nat>), which needs per-ctornat_fieldsmonomorphisation metadata that constructor layouts don't yet carry. Statically obligated either way, so verified programs stay sound.Deferred to #754 (transparent)
Two low-value residuals, neither a correctness gap (every shape is obligated statically and Tier-3-flagged):
_effect_opscarries no formal types)@Nat-guard trap kind (PR #748 review follow-ups: @Nat-narrowing test pins + dedicated guard trap kind #749 item 4 — needs the contract-fail host channel wired to a mid-expression guard)Follow-ups filed
Int <op> NatasNat(e.g.@Int.0 - 2mis-types as@Nat), the spurious-narrowing source surfaced by this workRelease
v0.0.172 → v0.0.173 across the six version sites + CHANGELOG cut + HISTORY row. Bundles the already-accumulated ruff-gate (#733) and pytest-9.1.0 (#742)
[Unreleased]entries.Closes #747
Closes #749
🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes v0.0.173
@Natruntime narrowing guards, with runtime traps for additional binding, destructuring, constructor-field, and generic/cross-module call cases.typecheck_with_artifactsplus verification artifacts for richer integration.@Natsituations now trap instead of being stored; clarified theInt <op> Nattype-check regression.ruffas a lint gate in pre-commit and CI; updatedpytestto 9.1.0.vera lspsupport: unsupported on Python 3.16+; refined limitations/spec wording.