Enable calling procedures in contracts#28
Open
keyboardDrummer wants to merge 136 commits into
Open
Conversation
keyboardDrummer-bot
force-pushed
the
disallow-transparent-statement-bodies
branch
from
April 23, 2026 14:37
3a0f094 to
df6ccdc
Compare
keyboardDrummer
force-pushed
the
disallow-transparent-statement-bodies
branch
from
April 29, 2026 08:44
2e860cf to
e3fe027
Compare
keyboardDrummer-bot
added a commit
that referenced
this pull request
May 1, 2026
Merges PR #31 (strata-org PRs strata-org#1076 and strata-org#1077) into this branch. Key conflict resolution decisions: - Removed AstNode.md field (from PR #28) in favor of PR #31's source-only approach - Adapted ContractPass, TransparencyPass, EliminateReturnStatements to use 2-field AstNode - Used diagnosticFromSource/identifierToCoreMd helpers from PR #31 - Kept PR #28's contract pass pipeline (transparencyPass, orderFunctionsAndProofs) - Kept PR #28's translateMethod for Python class methods - Kept PR #28's wildcard modifies filtering in HeapParameterization - Took PR #31's modifiesClausesToArgs (handles multiple wildcards correctly) - Took PR #31's buildSpecBody (adds postcondition support) - Took PR #31's instance procedure diagnostics - Kept PR #28's test expectations (contract pass changes error messages)
keyboardDrummer
force-pushed
the
disallow-transparent-statement-bodies
branch
from
May 1, 2026 12:22
fd2988e to
5fb8577
Compare
keyboardDrummer
changed the base branch from
disallow-transparent-statement-bodies
to
merge-1076-1077
May 1, 2026 12:23
keyboardDrummer
changed the base branch from
merge-1076-1077
to
issue-21-assign-variable-type
May 4, 2026 13:11
…rdDrummer/Strata into issue-924-contract-and-proof-pass
…strata-org#1381) EliminateValueInReturns only folded over program.staticProcedures, so a value-returning instance method with a body (using `return expr`) was never rewritten into `outParam := expr; return`. Updated the order of passes to run the `LiftInstanceProcedure` pass before `EliminateValueInReturns`. Add T9_ValueReturningInstanceMethods.lean: 9 positive cases (field/ computed/parameterized returns, conditional/early returns, modifies + return, bool return, shared method names, chained receiver, local var) and 2 negative cases pinning the no-output and multiple-output valued return diagnostics, proving the pass now reaches instance procedures. **Warning:** This repository will shortly undergo a split into several separate repositories. If you're creating a PR that crosses the boundaries between these repositories, you may want to hold off until the split is complete or be prepared to rework your PR into multiple PRs once the split is complete. The code that will be moved includes: - Strata/DDM/* - Strata/Languages/Boole/* - Strata/Languages/Python/* along with Tools/Python/* - Tools/BoogieToStrata
The last testing framework update regressed the error reporting to be tested-snippet-relative, but that is inconvenient. So whenever we flag an error now, it is relative to the entire file (easier to jump to) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
### Changes - Report resolution errors that occur after transformation passes during the compilation of Laurel to Core. This change triggers errors in existing tests that lead to the remainder of the changes in this PR. - Remove `.THeap` and `.TTypedField`. The usages have been replaced by `.UserDefinedType "Heap"` and `.UserDefinedType "Field"`, so that all references to the heap type use that form, preventing type equality errors. - Only re-resolve after HeapParam, TypeInference and ModifiesClauses passes all complete, since they are part of the same logical pass. In the future, we should refactor so they're actually one pass, but still three components, since TypeHierarchy and ModifiesClauses are features that built on top of Composite types. - Replace all reference types with `Composite` during the TypeInference pass. This also enables a slight simplification of LaurelToCoreTranslator. - Correct a source location usage in the lifting pass. Without this change, the reported location for an error would change, and this is detected during re-resolution. - In `Resolution.lean`, synth instead of void-check non-last block elements. This is required because some passes create non-void non-last block elements. In particular the `EliminateIncrDecr` pass creates them. This change also means that `Resolution.lean` no longer needs to special-case checking `| .StaticCall .. | .InstanceCall .. | .IncrDecr `. - Because of the previous change, `Resolution.lean` needs to support synthing for any StmtExpr, which it now does. - In the resolver, add special casing for calls to select/update/const. These are polymorphic procedures which we can't type check without special casing yet, since we don't support polymorphism yet. - Remove expecting the body of a function to correspond to its result type. This check failed for more complicated functions from `T3_ControlFlow` after they were lowered by some passes. Note that function will be removed entirely. In the test related to this removed check, I have changed the function into a procedure and the same diagnostic is reported then, although through a different mechanism. - Refactor TypeAliasElim so it uses a generic traversal to update types. This generic traversal was added a part of changes to TypeHierarchy ### Testing Added one test, but mostly this PR relies on the existing tests. --------- Co-authored-by: keyboardDrummer-bot <keyboardDrummer-bot@users.noreply.github.com>
… into issue-924-contract-and-proof-pass
laurel: wire old() to Core two-state semantics Modifies-clause frame conditions now flow through Core's `old`-prefixed identifiers via Core's native two-state semantics, replacing the previous synthetic `$heap_in` parameter. 1. HeapParameterization: heap-writing procedures take `$heap` as a true inout parameter (same name in inputs and outputs) rather than a `$heap_in` / `$heap` pair with a synthesized assignment prelude. 2. ModifiesClauses: frame condition references `old($heap)` via `StmtExpr.Old` instead of a separate `$heap_in` variable. 3. New PushOldInward Laurel-to-Laurel pass: distributes `StmtExpr.Old` through its sub-expressions until each `Old` immediately wraps a Local Var. Warns if `old(...)` does not mention any inout parameter. 4. LaurelToCoreTranslator: `Old (Var (Local n))` translates directly to `fvar (mkOld n)`. Inout parameters at call sites are detected and emit `.inoutArg` rather than paired `.inArg` + `.outArg`. Call-arg construction is shared between the two StaticCall sites via a new buildCallArgs helper. Tests: T9_OldHeapTwoState drives the feature end-to-end — `old` of heap field reads, arithmetic/unary/comparison sub-expressions, `old(old(...))`, `old` inside quantifiers and if-then-else, multiple modifies/ensures, modifies-wildcard, wrong-body negative cases, the no-inout warning, and a caller asserting the post-state. PushOldInward's normalization shape (every `Old` wraps an inout Local Var) is enforced at translate time in LaurelToCoreTranslator: the `.Old` arm emits a `StrataBug` diagnostic if the invariant is ever violated. --------- Co-authored-by: Jules <julesmt@amazon.com> Co-authored-by: keyboardDrummer-bot <keyboardDrummer-bot@users.noreply.github.com> Co-authored-by: Shilpi Goel <shigoel@gmail.com> Co-authored-by: Aaron Tomb <aarotomb@amazon.com> Co-authored-by: Michael Tautschnig <mt@debian.org> Co-authored-by: Kiro <kiro-agent@users.noreply.github.com> Co-authored-by: Juneyoung Lee <136006969+aqjune-aws@users.noreply.github.com> Co-authored-by: Mikaël Mayer <MikaelMayer@users.noreply.github.com> Co-authored-by: thanhnguyen-aws <ntson@amazon.com> Co-authored-by: Fabio Madge <fmadge@amazon.com> Co-authored-by: Joe Hendrix <joehx@amazon.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: June Lee <lebjuney@amazon.com> Co-authored-by: David Deng <daviddenghaotian@gmail.com> Co-authored-by: David Deng <htd@amazon.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Mikael Mayer <mimayere@amazon.com> Co-authored-by: Remy Willems <rwillems@amazon.com> Co-authored-by: keyboardDrummer-bot <keyboarddrummer.bot@gmail.com> Co-authored-by: Sagar Joshi <72283186+sagjoshi@users.noreply.github.com>
… into issue-924-contract-and-proof-pass
…trata-org#1401) With the new testing framework, we were correctly locating file-wide any uncaught diagnostic, but if an annotation was not matched (when we were over-expecting errors, or the error has changed), the annotation was located snippet-relative instead of file-relative. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…org#1391) Fixes strata-org#1390. **Problem:** `Program.eval` threads one `Env` through all procedures. A structured `exit` out of a labeled block doesn't pop its path-condition frames (`.block` exit pops `exprEnv.state`, not `pathConditions`; the exiting path bypasses `Env.merge`), so a procedure's preconditions/assumptions leak into later procedures. A contradictory leaked set then makes the next procedure prove false obligations vacuously — silent unsound pass. Laurel lowers non-final `return` to `exit "$body"`, so ordinary early-return code hits it. **Fix:** reset `pathConditions` to the pre-procedure state in the `.proc` fold (deferred obligations and fresh names carry forward). A `.block`-level reset was rejected — breaks fall-through. **Test:** `ProcedurePathConditionIsolation` — unsatisfiable-precondition `first` + structured `exit`, `second`'s `assert false` must fail. Red without the fix, green with it; full `StrataTest` green (539). Control matrix + scope in strata-org#1390.
Laurel had only a pre-test `while`. This adds a `doWhile` grammar op and
a body-tested `do … while` loop. Rather than a separate AST node, a
do-while is represented as a post-test `While` — the existing `While`
constructor gains a `postTest : Bool := false` field — and is lowered in
a new `EliminateDoWhile` Laurel-to-Laurel pass into the existing
pre-test loop:
```
do S while(G) invariant I
```
becomes
```
{ while(true) invariant I { S; if (!G) { exit L } } } L
```
`L` is a fresh, collision-free label (`$dowhile_exit_{n}`, `$`-prefixed
so it can't collide with user identifiers), so nested do-whiles and user
`break`/`continue` don't capture each other's exits. The body runs once
per iteration with the guard re-checked after it, and the real guard
reaches post-loop code via the structured `exit` — so the encoding is
sound, complete, and linear (single body in the IR, no
peeling/duplication).
Invariant placement is head-tested (checked before each body), matching
`while`. **Gotcha (documented in T23):** because the guard is re-tested
only after the body, the invariant must hold of the *pre-body* state, so
`do { x:=x+1 } while(x<3)` needs the bound `x <= 2`, not `x <= 3`.
**Why a flag + a pass:** `postTest` captures the while/do-while
distinction at the type level (per review feedback) rather than
duplicating the loop as a second constructor — so most passes match
`While` once and carry the flag through, with no `.DoWhile` arms.
`postTest := false` (pre-test) is the default, so every existing
`.While` construction is unchanged. The desugaring lives in
`EliminateDoWhile` rather than in `ConcreteToAbstractTreeTranslator`, so
it runs for every program reaching the pipeline — including one supplied
directly as abstract AST, bypassing the concrete translator. The parser
builds a post-test `While`; the serializer emits `while`/`doWhile` by
the flag; the pass (modeled on `EliminateIncrDecr`, with its own
fresh-label counter) does the desugar and runs first, so no later pass
observes `postTest = true`. No new Core construct or `LoopElim` change —
the desugar reuses the existing verified pre-test loop machinery.
Resolution handles `postTest` in `Check.while` directly (it runs before
the passes, the same arrangement as `IncrDecr`).
The per-program traversal over static and composite-instance procedures,
which both `EliminateDoWhile` and `EliminateIncrDecr` had hand-rolled,
is now `mapProgramProceduresM` in `MapStmtExpr` (per review feedback) —
so neither pass refers to unrelated features like instance procedures.
Tests in `T23_DoWhile` (end-to-end): basic, runs-at-least-once (guard
false at entry), nested, break-out via labelled block, no-invariant
(zero `invariant` clauses — asserts the negated guard, the only fact
provable without an invariant), and `falsePostRejected` (a false
postcondition is rejected, confirming the `while(true)` desugar isn't
vacuous). Plus `EliminateDoWhileTest` (pass-output shape, incl. distinct
fresh labels for nested loops) and a round-trip case in
`AbstractToConcreteTreeTranslatorTest` (serialization arg order).
**Earlier label-clash warning, now resolved.** Multi-procedure `T23`
used to surface a latent label clash (`⚠️ [addPathCondition] Label clash
detected for assume_invariant_0_0, …`) — `do-while` was just the first
construct to trip it, because path-condition labels weren't reset
between procedures. This branch has merged the base fix strata-org#1391
(path-condition isolation at the procedure boundary), so the warning is
gone (verified: zero clashes on the T23 build). Not caused by this PR.
**Strata half only.** To use do-while end-to-end, the jverify front-end
must emit the new op (regenerate the `laurel/` bindings and add a
`JCDoWhileLoop` case to `JavaToLaurelCompiler`, which currently rejects
do-while) — a separate follow-up. Implements the front-end desugar from
strata-org#1350.
…overage (strata-org#1382) Two field-access improvements on the single `fieldAccess` grammar op, plus test coverage that closes out strata-org#1371. Note: the chained-field-access *capability* (`a#b#c` parsing, resolution, and heap elimination) already landed via strata-org#1328. This PR adds the one missing ergonomic piece, hardens the chained-access machinery with the tests it was missing, and a small refactor. **Grammar — paren-free field `++`/`--`.** `fieldAccess` precedence is raised 90 → 95. At 90 it tied with the postfix `++`/`--` ops (also 90), forcing `(c#n)++`; at 95 it binds tighter, so `c#n++` parses paren-free as `(c#n)++`. (`leftassoc`, already present from strata-org#1328, is unchanged.) Note the new precedence is shared with `call` (also 95, via its `callee:89`) — the two must stay equal or `a#b(x)` parsing shifts; documented at the op. **Resolution — refactor only.** The duplicated type-scope field lookup in `targetTypeName` and `incrDecrTargetType` is factored into a shared `fieldTypeInScope` helper. No behavior change. (This helper is also a dependency of the stacked compound-assignment PR.) **Tests — fill the chained-access coverage gap.** strata-org#1328 shipped chained access with only a read-side smoke test (no assertions). This adds: - `T9_ChainedFieldAccess`: chained **write** (`o#inner#count := …`), read-after-inner-assign, must-alias, depth-3 (`a#mid#inner#count`), chained reads on both sides of `==`, and paren-free chained `o#inner#count++` (the one test that exercises this PR's grammar change), plus three **negative** tests (unconstrained read, two-object may-alias, write isolation) that pin the encoding as non-vacuous and frame-sound. - `T23b_IncrDecrField`: paren-free single-level field `++`/`--`. Full `lake build` and `lake test` pass.
## Summary
Adds support for **constrained types as composite fields** in Laurel —
e.g. a composite with a field of a refinement type:
```
constrained nat = x: int where x >= 0 witness 0
composite Counter { var count: nat }
```
Field writes now check the constraint (`c#count := -1` fails), and the
field is boxed as its base type on the heap.
## ConstrainedTypeElim changes made along the way
Supporting this cleanly required running `ConstrainedTypeElim`
**earlier** in the Laurel pipeline (right after `typeAliasElim`, before
`HeapParameterization`) so that constrained field types are lowered to
their base types before the heap-boxing pass needs them. Moving the pass
forward exposed a few places where it had implicitly relied on running
last, which this PR fixes:
1. **Composite field types** — `ConstrainedTypeElim` removed the
constrained type definitions but left `UserDefined "nat"` references
inside composite fields, which then failed to resolve in Core
translation. `elimCompositeType` now resolves constrained field types to
their base types (and runs elimination on instance procedures) before
the definitions are removed. The field-write constraint check
(previously enabled by `HeapParameterization` via a `Declare` temporary)
is now emitted directly from the `.Field` assignment target.
2. **Constrained-typed assignments in expression position** — `elimStmt`
only checked assignments that appear as statements, so `y := (x := -1) +
1` with `x : nat` was unchecked. `wrapExprAssigns` now traverses
expression positions and wraps such assignments as `{ x := v; assert
T$constraint(x); x }`, asserting on a read-back so the RHS is evaluated
exactly once (semantics-preserving).
3. **`LiftExpressionAssignments` assert ordering** — for the read-back
form to work, the assert must stay *after* the assignment through
lowering. `transformExpr` was prepending `.Assert`/`.Assume` in
expression-position blocks only after the assignment had already been
prepended, moving them ahead of it (so they checked the stale value).
`transformExpr` now lifts `.Assert`/`.Assume` during its traversal so
they keep their position relative to assignments lifted from the same
block. This is a general correctness fix for the lifting pass.
4. **Cleanup** — once `ConstrainedTypeElim` runs first,
`HeapParameterization` no longer needs its own constrained-type
resolution (the field types reaching it are already base types), so the
dead `resolveConstrainedType` helper and the shared
`resolveConstrainedTypeWith` helper in `LaurelAST` were removed. Also
included the offending type name in the `LaurelToCoreTranslator` "could
not be resolved" diagnostic.
## Pipeline placement
`constrainedTypeElimPass` is moved to the second position (after
`typeAliasElimPass`). It has no `comesBefore` ordering constraints and
nothing depends on it running late, so the load-time
`comesBeforeRespected` check still holds.
## Notes
- Bitvector-as-composite-field changes that previously sat on this
branch have been removed; they live in a separate branch/PR. This PR is
scoped to constrained types.
- Rebased onto the latest `main2` (which includes the strata-org#1222
pipeline-framework refactor).
## Testing
- Full `lake build` passes (495 jobs, including all `#guard_msgs`
elaboration-time tests and the load-time pipeline-ordering check).
- `T11_ConstrainedField` covers constrained composite fields
(valid/invalid writes + a documented read-side completeness gap).
- `T10_ConstrainedTypes` gains a `sideEffect` case exercising an
assignment-in-expression to a constrained local.
---------
Co-authored-by: Remy Willems <rwillems@amazon.com>
Co-authored-by: keyboardDrummer-bot <keyboardDrummer-bot@users.noreply.github.com>
… into issue-924-contract-and-proof-pass
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on:
Functional changes
EliminateReturnsInExpressionnow runs for procedures as wellImplementation
Add these passes:
returntoexitstatements, needed for the next pass.Follow-up work