Skip to content

Loop invariant asserts#588

Merged
andreistefanescu merged 4 commits intomainfrom
loop-invariant-asserts
Apr 20, 2026
Merged

Loop invariant asserts#588
andreistefanescu merged 4 commits intomainfrom
loop-invariant-asserts

Conversation

@andreistefanescu
Copy link
Copy Markdown
Contributor

No description provided.

andreistefanescu and others added 4 commits April 20, 2026 01:22
Add tests covering the pattern where a function-level `asserts` covers
per-iteration abort conditions in an implementation loop. The loop
invariant uses `ensures` to carry the partial information needed for
both the Check direction (discharge body assertion under asserts) and
the Assume direction (force loop to be unable to exit normally when
asserts is negated).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cover variations of the asserts-in-loop pattern:
* requires + asserts mix
* multiple per-iteration asserts in the body
* sequential pair of loops in one function
* loop with early break (no asserts needed)
* accumulator overflow with closed-form invariant
* asserts pinning down a return-value postcondition
* mutating state via &mut

Plus two failure cases exposing prover limitations:
* nested loops with two independent asserts (Assume direction does
  not propagate inner-loop contradiction outward)
* mutating quantified state where the asserts and the natural
  invariant collapse to the same predicate at iteration zero

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The mutating quantified case is solvable — apply the same `j < i` guard
trick used for immutable inputs, but to the cloned `old_v` snapshot.
The invariant `forall j < i, safe(old_v[j])` is vacuous at iteration 0,
accumulates per-iteration, and at loop exit covers the full range —
contradicting the negated asserts.

Keep the broken un-guarded version as a fail test showing what does
not work and why.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Express the function-level asserts as `forall k, outer_safe(k, n, m)`
and accumulate `forall k, k >= i || outer_safe(k, n, m)` in the outer
loop invariant — same vacuous-at-zero / collapses-at-exit pattern that
works for `positive_check` and `increment_all`.

The earlier nested.fail variant used a bounded asserts which doesn't
let the prover connect a single conjunctive assertion to the
per-iteration abort behaviour spread across two loops.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@andreistefanescu andreistefanescu merged commit 8fe1f5c into main Apr 20, 2026
19 of 23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant