Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
26b2c92
Introduce FunctionsAndProofs IR and rename OrderedLaurel to CoreWithL…
keyboardDrummer-bot Apr 15, 2026
239f453
Address review: computeSccDecls takes FunctionsAndProofsProgram, rena…
keyboardDrummer-bot Apr 15, 2026
baa875d
Add contract pass and improve proof pass
keyboardDrummer-bot Apr 15, 2026
fda4700
Contract pass: treat all procedures, add call-site rewriting, wire in…
keyboardDrummer-bot Apr 15, 2026
07963a9
Remove dead groupDatatypesByScc (replaced by where-local groupDatatyp…
keyboardDrummer-bot Apr 15, 2026
43abf85
Fix CI: disable contract pass pending test updates, fix call-site rew…
keyboardDrummer-bot Apr 15, 2026
bc1e7cd
Fixes
keyboardDrummer Apr 16, 2026
f25a1dc
Merge branch 'issue-924-contract-and-proof-pass' of github.com:strata…
keyboardDrummer Apr 16, 2026
d50d081
Add opaque keyword to Laurel grammar
keyboardDrummer-bot Apr 16, 2026
2644268
Add missing opaque
keyboardDrummer Apr 16, 2026
e092185
Fixes
keyboardDrummer Apr 16, 2026
d85c556
Fix
keyboardDrummer Apr 16, 2026
ed8ba72
Address tautschnig review: rename nonExternal, add doc comments and i…
keyboardDrummer-bot Apr 16, 2026
076784d
Merge main: resolve conflict in LaurelCompilationPipeline.lean
keyboardDrummer-bot Apr 16, 2026
bc9e754
Fixes
keyboardDrummer Apr 16, 2026
f625586
Update
keyboardDrummer Apr 16, 2026
8b82ee7
Fixes
keyboardDrummer Apr 16, 2026
8fdeb9a
Enable contract pass
keyboardDrummer Apr 16, 2026
21b0871
Fixes
keyboardDrummer Apr 16, 2026
6456248
Fixes
keyboardDrummer Apr 16, 2026
b6818ea
Add missing case for LiftImpExpr
keyboardDrummer Apr 16, 2026
7e607b6
Fixes
keyboardDrummer Apr 16, 2026
4254399
Fixes
keyboardDrummer Apr 16, 2026
b6c4350
Fixes
keyboardDrummer Apr 16, 2026
6f0ed18
Fixes
keyboardDrummer Apr 16, 2026
934ee43
Add EliminateReturnStatements pass
keyboardDrummer-bot Apr 16, 2026
727a742
Merge remote-tracking branch 'origin/eliminate-return-statements-pass…
keyboardDrummer Apr 16, 2026
f8cc45b
Remove obsolete testfile
keyboardDrummer Apr 16, 2026
015000a
Fix
keyboardDrummer Apr 16, 2026
e1f1148
Copy property summary from requires/ensures clauses to contract pass …
keyboardDrummer-bot Apr 16, 2026
128ade3
Fix test
keyboardDrummer Apr 16, 2026
19cdf81
Merge branch 'opaque-keyword-grammar' of github.com:strata-org/Strata…
keyboardDrummer Apr 16, 2026
372ee79
Test fixes
keyboardDrummer Apr 16, 2026
efce064
Test updates
keyboardDrummer Apr 16, 2026
e164f8f
update test
keyboardDrummer Apr 16, 2026
fa240ea
Add EliminateMultipleOutputs pass and pipeline integration
keyboardDrummer-bot Apr 16, 2026
f410650
Test update
keyboardDrummer Apr 16, 2026
47a9980
Add hacky fix
keyboardDrummer Apr 16, 2026
f95c07b
Fixes
keyboardDrummer Apr 16, 2026
0e5bb41
Update contract phase, but still an issue with this becoming a Core f…
keyboardDrummer Apr 16, 2026
031c8e2
Support multiple LHS names in Laurel LocalVariable
keyboardDrummer-bot Apr 16, 2026
561dfeb
Merge origin/main into opaque-keyword-grammar
keyboardDrummer-bot Apr 17, 2026
5a2fb82
Merge remote-tracking branch 'origin/support-multiple-lhs-local-varia…
keyboardDrummer-bot Apr 17, 2026
49a39e4
Refactor LocalVariable to use List Parameter instead of separate name…
keyboardDrummer-bot Apr 17, 2026
b4290d9
Merge main + PR #958, fix AstNode 3-field compat, use multi-target Lo…
keyboardDrummer-bot Apr 17, 2026
24d5d07
Fix mkFunctionCopy: non-functional procedures get opaque function copies
keyboardDrummer-bot Apr 17, 2026
a4b6696
Fix test files: remove incorrect opaque, fix annotations, add opaque …
keyboardDrummer-bot Apr 17, 2026
b766021
Merge remote-tracking branch 'origin/support-multiple-lhs-local-varia…
keyboardDrummer Apr 17, 2026
df4472f
Fixes
keyboardDrummer Apr 17, 2026
42c5b34
Fixes
keyboardDrummer Apr 17, 2026
e65e783
Support let bindings in Core expressions
keyboardDrummer-bot Apr 17, 2026
b27aba1
Merge remote-tracking branch 'origin/keyboardDrummer-bot/964-let-bind…
keyboardDrummer Apr 18, 2026
248df0c
Try to support local function variables
keyboardDrummer Apr 18, 2026
1fb0a12
Add opaque keyword to Laurel grammar
keyboardDrummer-bot Apr 19, 2026
3e8d949
Disallow transparent statement bodies on non-functional procedures
keyboardDrummer-bot Apr 19, 2026
c7ad4a9
Merge add-opaque-keyword branch and use opaque instead of ensures true
keyboardDrummer-bot Apr 19, 2026
2664cc5
Remove checkTransparentBodies option; make Python pipeline use opaque…
keyboardDrummer-bot Apr 19, 2026
6edf167
Merge branch 'main' into issue-924-functions-and-proofs-ir
keyboardDrummer Apr 19, 2026
63d884d
Merge main into disallow-transparent-statement-bodies
keyboardDrummer-bot Apr 19, 2026
2ac415f
Merge upstream/main into add-opaque-keyword
keyboardDrummer-bot Apr 19, 2026
e666682
Merge branch 'issue-924-functions-and-proofs-ir' into issue-924-contr…
keyboardDrummer Apr 19, 2026
037dd83
Fix CI failures: grammar formatting, parser fallback, and missing opa…
keyboardDrummer-bot Apr 19, 2026
7265e2f
Fix CI: include opaque-with-body procedures in inlinableProcedures
keyboardDrummer-bot Apr 19, 2026
976eb51
Update golden-file expected outputs for kwargs tests
keyboardDrummer-bot Apr 19, 2026
9d4c6ee
Update golden-file expected outputs for class tests
keyboardDrummer-bot Apr 19, 2026
0fbae97
Add phase to inline local variables in expression position
keyboardDrummer-bot Apr 19, 2026
9492438
Fix merge issue
keyboardDrummer Apr 20, 2026
da34998
Undo using Core for let expressions
keyboardDrummer Apr 20, 2026
c70b79e
Merge remote-tracking branch 'fork/inline-local-variables-in-expressi…
keyboardDrummer Apr 20, 2026
01e6109
Fixes
keyboardDrummer Apr 20, 2026
6ff746d
Fixes
keyboardDrummer Apr 20, 2026
f4c1b98
Fixes
keyboardDrummer Apr 20, 2026
966973b
Test improvement
keyboardDrummer Apr 20, 2026
0120b14
Fixes
keyboardDrummer Apr 20, 2026
e32563a
Fix
keyboardDrummer Apr 20, 2026
8ff1914
Fix
keyboardDrummer Apr 20, 2026
64da7ad
Fix test
keyboardDrummer Apr 20, 2026
62424d5
Test fix
keyboardDrummer Apr 20, 2026
aa00ff8
Use core && || and ==>
keyboardDrummer Apr 20, 2026
e1e1e9a
Fixes
keyboardDrummer Apr 20, 2026
82a9748
Fix
keyboardDrummer Apr 20, 2026
e4f0745
Fixes
keyboardDrummer Apr 20, 2026
d4f5a65
Fixes
keyboardDrummer Apr 20, 2026
5721413
Don't call safe functions from functions
keyboardDrummer Apr 20, 2026
5bde4e4
Move diagnostic check for instance procedures
keyboardDrummer Apr 20, 2026
9ae82a4
Undo Core changes
keyboardDrummer Apr 20, 2026
405fd0a
Update LaurelGrammar
keyboardDrummer Apr 20, 2026
2387080
Grammar fix so ensures clauses without opaque are not dropped
keyboardDrummer Apr 20, 2026
edf5a72
Fix AbstractToConcreteTreeTranslator to nest ensures/modifies inside …
keyboardDrummer-bot Apr 20, 2026
9b82817
Tweak
keyboardDrummer Apr 20, 2026
6a5484d
Bring back .ite in shortcircuit op translation
keyboardDrummer Apr 20, 2026
bba17ea
Fix test
keyboardDrummer Apr 20, 2026
70b4e57
Fix test
keyboardDrummer Apr 20, 2026
d754312
Fix test
keyboardDrummer Apr 20, 2026
a903376
Fix test
keyboardDrummer Apr 20, 2026
a56175f
Fix for inferHoleTest
keyboardDrummer Apr 20, 2026
7e99c08
Update test
keyboardDrummer Apr 21, 2026
6f18b0d
Add axioms field to Procedure, populate in ContractPass, use in trans…
keyboardDrummer-bot Apr 21, 2026
7dbd727
Merge remote-tracking branch 'fork/add-opaque-keyword' into disallow-…
keyboardDrummer Apr 21, 2026
862d882
Undo expect file changes
keyboardDrummer Apr 21, 2026
107afed
Update comment
keyboardDrummer Apr 21, 2026
b531ee5
Add wildcard modifies clause support (`modifies *`)
keyboardDrummer-bot Apr 21, 2026
e3fe027
Update test
keyboardDrummer Apr 21, 2026
1e29835
Merge branch 'disallow-transparent-statement-bodies' into issue-924-c…
keyboardDrummer Apr 21, 2026
ec064f6
Test fixes
keyboardDrummer Apr 21, 2026
95489a0
Fix contract pass creating assertions with default source range
keyboardDrummer-bot Apr 21, 2026
872517f
Fix duplicate diagnostic
keyboardDrummer Apr 21, 2026
83bd0fd
Fix missing sources in ContractPass
keyboardDrummer Apr 21, 2026
385f18e
Fix unused variable warning in translateLaurelToCore
keyboardDrummer-bot Apr 21, 2026
41f25f1
Merge branch 'issue-924-contract-and-proof-pass' of github.com:strata…
keyboardDrummer Apr 21, 2026
f56e6cd
Skip Java codegen test gracefully when dependencies are missing
keyboardDrummer-bot Apr 21, 2026
8a2ca4c
Revert "Skip Java codegen test gracefully when dependencies are missing"
keyboardDrummer Apr 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Examples/StringTest.laurel.st
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
procedure testString()
returns (result: string)
requires true
opaque
{
var message: string := "Hello, World!";
return message
Expand All @@ -9,6 +10,7 @@ requires true
procedure testStringConcat()
returns (result: string)
requires true
opaque
{
var hello: string := "Hello";
var world: string := "World";
Expand Down
27 changes: 15 additions & 12 deletions Strata/Languages/Laurel/ConstrainedTypeElim.lean
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ def resolveExprNode (ptMap : ConstrainedTypeMap) (expr : StmtExprMd) : StmtExprM
let source := expr.source
let md := expr.md
match expr.val with
| .LocalVariable n ty init =>
⟨.LocalVariable n (resolveType ptMap ty) init, source, md⟩
| .LocalVariable params init =>
⟨.LocalVariable (params.map fun p => { p with type := resolveType ptMap p.type }) init, source, md⟩
| .Forall param trigger body =>
let param' := { param with type := resolveType ptMap param.type }
-- With bottom-up traversal, `body` is already recursed into. The newly
Expand Down Expand Up @@ -127,15 +127,18 @@ def elimStmt (ptMap : ConstrainedTypeMap)
let source := stmt.source
let md := stmt.md
match _h : stmt.val with
| .LocalVariable name ty init =>
let callOpt := constraintCallFor ptMap ty.val name md (src := source)
if callOpt.isSome then modify fun pv => pv.insert name.text ty.val
| .LocalVariable params init =>
for p in params do
let callOpt := constraintCallFor ptMap p.type.val p.name md (src := source)
if callOpt.isSome then modify fun pv => pv.insert p.name.text p.type.val
let (init', check) : Option StmtExprMd × List StmtExprMd := match init with
| none => match callOpt with
| some c => (none, [⟨.Assume c, source, md⟩])
| none => (none, [])
| some _ => (init, callOpt.toList.map fun c => ⟨.Assert c, source, md⟩)
pure ([⟨.LocalVariable name ty init', source, md⟩] ++ check)
| none =>
let calls := params.filterMap fun p => constraintCallFor ptMap p.type.val p.name md (src := source)
(none, calls.map fun c => ⟨.Assume c, source, md⟩)
| some _ =>
let calls := params.filterMap fun p => constraintCallFor ptMap p.type.val p.name md (src := source)
(init, calls.map fun c => ⟨.Assert c, source, md⟩)
pure ([⟨.LocalVariable params init', source, md⟩] ++ check)

| .Assign [target] _ => match target.val with
| .Identifier name => do
Expand Down Expand Up @@ -209,13 +212,13 @@ private def mkWitnessProc (ptMap : ConstrainedTypeMap) (ct : ConstrainedType) :
let md := ct.witness.md
let witnessId : Identifier := mkId "$witness"
let witnessInit : StmtExprMd :=
⟨.LocalVariable witnessId (resolveType ptMap ct.base) (some ct.witness), src, md⟩
⟨.LocalVariable [{ name := witnessId, type := resolveType ptMap ct.base }] (some ct.witness), src, md⟩
let assert : StmtExprMd :=
⟨.Assert (constraintCallFor ptMap (.UserDefined ct.name) witnessId md (src := src)).get!, src, md⟩
{ name := mkId s!"$witness_{ct.name.text}"
inputs := []
outputs := []
body := .Transparent ⟨.Block [witnessInit, assert] none, src, md⟩
body := .Opaque [] (some ⟨.Block [witnessInit, assert] none, src, md⟩) []
preconditions := []
isFunctional := false
decreases := none }
Expand Down
328 changes: 328 additions & 0 deletions Strata/Languages/Laurel/ContractPass.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
/-
Copyright Strata Contributors

SPDX-License-Identifier: Apache-2.0 OR MIT
-/
module

public import Strata.Languages.Laurel.MapStmtExpr

/-!
## Contract Pass (Laurel → Laurel)

Removes pre- and postconditions from all procedures and replaces them with
explicit precondition/postcondition helper procedures, assumptions, and
assertions.

For each procedure with contracts:
- Generate a precondition procedure (`foo$pre`) returning the conjunction of preconditions.
- Generate a postcondition procedure (`foo$post`) that takes only the *input*
parameters, internally calls the original procedure to obtain the outputs,
and returns the conjunction of postconditions.
- Insert `assume foo$pre(inputs)` at the start of the body.
- Insert `assert foo$post(inputs)` at the end of the body.

For each call to a contracted procedure:
- Insert `assert foo$pre(args)` before the call (precondition check).
- Insert `assume foo$post(args)` after the call (postcondition assumption).

The postcondition procedure calls the original procedure internally so that
the `assume` at call sites only references pre-call arguments. This avoids
a soundness issue where mutable variables (e.g. `$heap`) are overwritten by
the call's result destructuring before the `assume` is evaluated.
-/

namespace Strata.Laurel

public section

private def emptyMd : MetaData := .empty

private def mkMd (e : StmtExpr) : StmtExprMd := { val := e, source := none }

/-- Create a `StmtExprMd` with a property summary in its metadata. -/
private def mkMdWithSummary (e : StmtExpr) (summary : String) : StmtExprMd :=
⟨e, none, emptyMd.withPropertySummary summary⟩

/-- Build a conjunction of expressions. Returns `LiteralBool true` for an empty list. -/
private def conjoin (exprs : List StmtExprMd) : StmtExprMd :=
match exprs with
| [] => mkMd (.LiteralBool true)
| [e] => e
| e :: rest => rest.foldl (fun acc x =>
mkMd (.PrimitiveOp .And [acc, x])) e

/-- Name for the precondition helper procedure. -/
def preCondProcName (procName : String) : String := s!"{procName}$pre"

/-- Name for the postcondition helper procedure. -/
def postCondProcName (procName : String) : String := s!"{procName}$post"

/-- Get postconditions from a procedure body. -/
private def getPostconditions (body : Body) : List StmtExprMd :=
match body with
| .Opaque postconds _ _ => postconds
| .Abstract postconds => postconds
| _ => []

/-- Build a call expression. -/
private def mkCall (callee : String) (args : List StmtExprMd) : StmtExprMd :=
mkMd (.StaticCall (mkId callee) args)

/-- Convert parameters to identifier expressions. -/
private def paramsToArgs (params : List Parameter) : List StmtExprMd :=
params.map fun p => mkMd (.Identifier p.name)

/-- Build a helper function that returns the conjunction of the given conditions. -/
private def mkConditionProc (name : String) (params : List Parameter)
(conditions : List StmtExprMd) : Procedure :=
-- Use "$result" as the output name to avoid clashing with user-defined
-- parameter names (user names cannot contain '$').
{ name := mkId name
inputs := params
outputs := [⟨mkId "$result", { val := .TBool, source := none }⟩] -- TODO, enable anonymous output parameters
preconditions := []
decreases := none
isFunctional := true
body := .Transparent (conjoin conditions) }

/-- Build a postcondition procedure that takes only the *input* parameters
and internally calls the original procedure to obtain the outputs.

For a procedure `foo(a, b) returns (x, y)` with postcondition `P(a, b, x, y)`,
generates:
```
procedure foo$post(a, b) returns ($result : bool) {
var x, y : Tx := foo(a, b);
P(a, b, x, y)
}
```

This ensures the `assume` at call sites only references pre-call arguments,
avoiding a soundness issue where mutable variables (e.g. `$heap`) are
overwritten by the call's result destructuring before the `assume` is
evaluated. -/
private def mkPostConditionProc (name : String) (originalProcName : String)
(inputParams : List Parameter) (outputParams : List Parameter)
(conditions : List StmtExprMd) : Procedure :=
let inputArgs := paramsToArgs inputParams
let callExpr := mkMd (.StaticCall (mkId originalProcName) inputArgs)
let localVarStmt := mkMd (.LocalVariable outputParams (some callExpr))
-- Body: single initialized local variable, then postcondition conjunction
let bodyStmts := [localVarStmt, conjoin conditions]
let body := mkMd (.Block bodyStmts none)
{ name := mkId name
inputs := inputParams
outputs := [⟨mkId "$result", { val := .TBool, source := none }⟩]
preconditions := []
decreases := none
isFunctional := false
body := .Transparent body }

/-- Extract a combined summary from a list of contract clauses. -/
private def combinedSummary (clauses : List StmtExprMd) : Option String :=
let summaries := clauses.filterMap fun c => c.md.getPropertySummary
match summaries with
| [] => none
| [s] => some s
| ss => some (String.intercalate ", " ss)

/-- Information about a procedure's contracts. -/
private structure ContractInfo where
hasPreCondition : Bool
hasPostCondition : Bool
preName : String
postName : String
preSummary : Option String
postSummary : Option String
/-- The original procedure's input parameters (needed for postcondition generation). -/
inputParams : List Parameter
/-- The original procedure's output parameters (needed for postcondition generation). -/
outputParams : List Parameter

/-- Collect contract info for all procedures with contracts. -/
private def collectContractInfo (procs : List Procedure) : Std.HashMap String ContractInfo :=
procs.foldl (fun m proc =>
let postconds := getPostconditions proc.body
let hasPre := !proc.preconditions.isEmpty
let hasPost := !postconds.isEmpty
if hasPre || hasPost then
m.insert proc.name.text {
hasPreCondition := hasPre
hasPostCondition := hasPost
preName := preCondProcName proc.name.text
postName := postCondProcName proc.name.text
preSummary := combinedSummary proc.preconditions
postSummary := combinedSummary postconds
inputParams := proc.inputs
outputParams := proc.outputs
}
else m) {}

/-- Transform a procedure body to add assume/assert for its own contracts. -/
private def transformProcBody (proc : Procedure) (info : ContractInfo) : Body :=
let inputArgs := paramsToArgs proc.inputs
let postconds := getPostconditions proc.body
-- Use the source location from the first precondition for the assume node
let preAssume : List StmtExprMd :=
if info.hasPreCondition then
let (preSrc, preMd) := match proc.preconditions.head? with
| some pc => (pc.source, pc.md)
| none => (none, emptyMd)
[⟨.Assume (mkCall info.preName inputArgs), preSrc, preMd⟩]
else []
let postAssert : List StmtExprMd :=
if info.hasPostCondition then
-- Use the source location and metadata from the first postcondition so
-- the diagnostic carries the source location of the `ensures` clause.
let (baseSrc, baseMd) := match postconds.head? with
| some pc => (pc.source, pc.md)
| none => (none, emptyMd)
let summary := info.postSummary.getD "postcondition"
-- Directly assert the postcondition conjunction rather than calling $post.
-- The $post procedure re-invokes the original (opaque) procedure to obtain
-- outputs, which is correct at *call sites* but wrong inside the body:
-- here the output variables (e.g. $heap) are already in scope with their
-- actual values, so we assert the postcondition directly.
[⟨.Assert (conjoin postconds),
baseSrc, baseMd.withPropertySummary summary⟩]
else []
match proc.body with
| .Transparent body =>
.Transparent ⟨.Block (preAssume ++ [body] ++ postAssert) none, body.source, emptyMd ⟩
| .Opaque _ (some impl) _ =>
.Opaque [] (some ⟨.Block (preAssume ++ [impl] ++ postAssert) none, impl.source, emptyMd⟩) []
| .Opaque _ none _ | .Abstract _ =>
.Opaque [] (some ⟨ .Block [] none, none, emptyMd⟩) []
| b => b

/-- Rewrite a single statement that may be a call to a contracted procedure.
Returns a list of statements (the original plus any inserted assert/assume).
Uses the call site's metadata for generated assert/assume nodes.
The postcondition assume passes only the call arguments (not the results),
since the $post procedure internally calls the original to obtain outputs. -/
private def rewriteStmt (contractInfoMap : Std.HashMap String ContractInfo)
(e : StmtExprMd) : List StmtExprMd :=
let md := e.md
let src := e.source
let mkWithMd (se : StmtExpr) : StmtExprMd := ⟨se, src, md⟩
let mkWithMdSummary (se : StmtExpr) (summary : String) : StmtExprMd :=
⟨se, src, md.withPropertySummary summary⟩
match e.val with
| .Assign _targets (.mk (.StaticCall callee args) ..) =>
match contractInfoMap.get? callee.text with
| some info =>
let preAssert := if info.hasPreCondition
then [mkWithMdSummary (.Assert (mkCall info.preName args)) (info.preSummary.getD "precondition")] else []
-- Assume $post *before* the assignment so that args still reference
-- pre-call values (e.g. $heap before it is overwritten by the call result).
-- The $post procedure internally calls the original to obtain outputs.
let postAssume := if info.hasPostCondition
then [mkWithMd (.Assume (mkCall info.postName args))] else []
preAssert ++ postAssume ++ [e]
| none => [e]
| .LocalVariable _params (some (.mk (.StaticCall callee args) ..)) =>
match contractInfoMap.get? callee.text with
| some info =>
let preAssert := if info.hasPreCondition
then [mkWithMdSummary (.Assert (mkCall info.preName args)) (info.preSummary.getD "precondition")] else []
-- Assume $post *before* the local variable binding so that args still
-- reference pre-call values. The $post procedure internally calls the
-- original to obtain outputs.
let postAssume := if info.hasPostCondition
then [mkWithMd (.Assume (mkCall info.postName args))] else []
preAssert ++ postAssume ++ [e]
| none => [e]
| .StaticCall callee args =>
match contractInfoMap.get? callee.text with
| some info =>
let preAssert := if info.hasPreCondition
then [mkWithMdSummary (.Assert (mkCall info.preName args)) (info.preSummary.getD "precondition")] else []
preAssert ++ [e]
| none => [e]
| _ => [e]

/-- Rewrite call sites in a statement/expression tree. Processes Block children
at the statement level to avoid interfering with expression-level calls.
For each statement-level call to a contracted procedure, inserts
`assert pre(args)` before and `assume post(args)` after. -/
private def rewriteCallSites (contractInfoMap : Std.HashMap String ContractInfo)
(expr : StmtExprMd) : StmtExprMd :=
let result := mapStmtExpr (fun e =>
match e.val with
| .Block stmts label =>
let stmts' := stmts.flatMap (rewriteStmt contractInfoMap)
if stmts'.length == stmts.length then e
else ⟨.Block stmts' label, e.source, e.md⟩
| _ => e) expr
-- Handle top-level non-Block statements (e.g., bare Assign or StaticCall)
let expanded := rewriteStmt contractInfoMap result
match expanded with
| [single] => single
| many => mkMd (.Block many none)

/-- Rewrite call sites in all bodies of a procedure. -/
private def rewriteCallSitesInProc (contractInfoMap : Std.HashMap String ContractInfo)
(proc : Procedure) : Procedure :=
let rw := rewriteCallSites contractInfoMap
match proc.body with
| .Transparent body =>
{ proc with body := .Transparent (rw body) }
| .Opaque posts impl mods =>
let body := Body.Opaque (posts.map rw) (impl.map rw) (mods.map rw)
{ proc with body := body }
| _ => proc

/-- Build an axiom expression from `invokeOn` trigger and ensures clauses.
Produces `∀ p1, ∀ p2, ..., ∀ pn :: { trigger } (ensures1 && ensures2 && ...)`. -/
private def mkInvokeOnAxiom (params : List Parameter) (trigger : StmtExprMd)
(postconds : List StmtExprMd) : StmtExprMd :=
let body := conjoin postconds
-- Wrap in nested Forall from last param (innermost) to first (outermost).
-- The trigger is placed on the innermost quantifier.
params.foldr (init := (body, true)) (fun p (acc, isInnermost) =>
let trig := if isInnermost then some trigger else none
(mkMd (.Forall p trig acc), false)) |>.1

/-- Run the contract pass on a Laurel program.
All procedures with contracts are transformed. -/
def contractPass (program : Program) : Program :=
let contractInfoMap := collectContractInfo program.staticProcedures

-- Generate helper procedures for all procedures with contracts
let helperProcs := program.staticProcedures.flatMap fun proc =>
let postconds := getPostconditions proc.body
let preProc :=
if proc.preconditions.isEmpty then []
else [mkConditionProc (preCondProcName proc.name.text) proc.inputs proc.preconditions]
let postProc :=
if postconds.isEmpty then []
else [mkPostConditionProc (postCondProcName proc.name.text) proc.name.text
proc.inputs proc.outputs postconds]
preProc ++ postProc

-- Transform procedures: strip contracts, add assume/assert, rewrite call sites
let transformedProcs := program.staticProcedures.map fun proc =>
-- Build axioms from invokeOn + ensures BEFORE transforming the body
-- (transformProcBody strips postconditions from the body)
let proc := match proc.invokeOn with
| some trigger =>
let postconds := getPostconditions proc.body
if postconds.isEmpty then { proc with invokeOn := none }
else { proc with
axioms := [mkInvokeOnAxiom proc.inputs trigger postconds]
invokeOn := none }
| none => proc
let proc := match contractInfoMap.get? proc.name.text with
| some info =>
{ proc with
preconditions := []
body := transformProcBody proc info }
| none => proc
-- Rewrite call sites in the procedure body
rewriteCallSitesInProc contractInfoMap proc

{ program with staticProcedures := helperProcs ++ transformedProcs }

end -- public section
end Strata.Laurel
Loading
Loading