From 7e34b046fa82bcb6f28dcdd1aa15c8200c9ba0c6 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 26 May 2026 09:55:06 +0000 Subject: [PATCH 001/115] Improve Laurel printer --- Strata/DDM/Format.lean | 2 +- .../Laurel/Grammar/LaurelGrammar.lean | 2 +- .../Languages/Laurel/Grammar/LaurelGrammar.st | 4 +- .../Laurel/LaurelCompilationPipeline.lean | 6 +- .../Laurel/LaurelToCoreTranslator.lean | 30 +-- .../AbstractToConcreteTreeTranslatorTest.lean | 79 ++++++-- .../Laurel/ConstrainedTypeElimTest.lean | 54 +++++- .../Laurel/LiftExpressionAssignmentsTest.lean | 13 +- .../Languages/Laurel/LiftHolesTest.lean | 176 +++++++++++++----- .../LiftImperativeCallsInAssertTest.lean | 40 +++- 10 files changed, 303 insertions(+), 103 deletions(-) diff --git a/Strata/DDM/Format.lean b/Strata/DDM/Format.lean index 613a726135..4d97f30839 100644 --- a/Strata/DDM/Format.lean +++ b/Strata/DDM/Format.lean @@ -453,7 +453,7 @@ private partial def ArgF.mformatM {α} : ArgF α → FormatM PrecFormat if z : entries.size = 0 then pure (.atom .nil) else do - let f i q s := return s ++ "; " ++ (← entries[i].mformatM).format + let f i q s := return s ++ ";\n" ++ (← entries[i].mformatM).format let a := (← entries[0].mformatM).format .atom <$> entries.size.foldlM f (start := 1) a diff --git a/Strata/Languages/Laurel/Grammar/LaurelGrammar.lean b/Strata/Languages/Laurel/Grammar/LaurelGrammar.lean index 02e2159643..c185d90edc 100644 --- a/Strata/Languages/Laurel/Grammar/LaurelGrammar.lean +++ b/Strata/Languages/Laurel/Grammar/LaurelGrammar.lean @@ -9,7 +9,7 @@ module -- Laurel dialect definition, loaded from LaurelGrammar.st -- NOTE: Changes to LaurelGrammar.st are not automatically tracked by the build system. -- Update this file (e.g. this comment) to trigger a recompile after modifying LaurelGrammar.st. --- Last grammar change: multiAssign supports field access targets, added opaque keyword. +-- Last grammar change: block format uses indent(2) with leading spaces for vertical layout. public import Strata.DDM.Integration.Lean public meta import Strata.DDM.Integration.Lean diff --git a/Strata/Languages/Laurel/Grammar/LaurelGrammar.st b/Strata/Languages/Laurel/Grammar/LaurelGrammar.st index b81b0d8b11..5ddc8609f5 100644 --- a/Strata/Languages/Laurel/Grammar/LaurelGrammar.st +++ b/Strata/Languages/Laurel/Grammar/LaurelGrammar.st @@ -106,8 +106,8 @@ op ifThenElse (cond: StmtExpr, thenBranch: StmtExpr, elseBranch: Option ElseBran op assert (cond : StmtExpr, errorMessage: Option ErrorSummary) : StmtExpr => @[prec(0)] "assert " cond:0 errorMessage:0; op assume (cond : StmtExpr) : StmtExpr => @[prec(0)] "assume " cond:0; op return (value : StmtExpr) : StmtExpr => @[prec(0)] "return " value:0; -op block (stmts : SemicolonSepBy StmtExpr) : StmtExpr => @[prec(1000)] "{ " stmts " }"; -op labelledBlock (stmts : SemicolonSepBy StmtExpr, label : Ident) : StmtExpr => @[prec(1000)] "{ " stmts " }" label; +op block (stmts : SemicolonSepBy StmtExpr) : StmtExpr => @[prec(1000)] "{\n " indent(2, stmts) "\n}"; +op labelledBlock (stmts : SemicolonSepBy StmtExpr, label : Ident) : StmtExpr => @[prec(1000)] "{\n " indent(2, stmts) "\n}" label; op exit (label : Ident) : StmtExpr => @[prec(0)] "exit " label; // While loops diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index 54b97fdfd9..a53b30a390 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -217,8 +217,12 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) if let some coreProgram := coreProgramOption then emit "CoreProgram" "core.st" coreProgram let mut allDiagnostics := passDiags ++ translateState.diagnostics + + if translateState.coreDiagnostics.length > 0 && allDiagnostics.isEmpty then + allDiagnostics := allDiagnostics ++ translateState.coreDiagnostics + let coreProgramOption := - if translateState.coreProgramHasSuperfluousErrors then none else coreProgramOption + if !translateState.coreDiagnostics.isEmpty then none else coreProgramOption return (coreProgramOption, allDiagnostics, program, stats) /-- diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index 9e02d9a825..cc33f2dae8 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -63,8 +63,11 @@ structure TranslateState where model : SemanticModel /-- Overflow check configuration -/ overflowChecks : Core.OverflowChecks := {} - /-- Do not process the produces Core program, since it has superfluous errors -/ - coreProgramHasSuperfluousErrors: Bool := false + /-- Diagnostics that indicate the Core program should not be processed further. + When non-empty, the produced Core program is suppressed. Each entry records + why the program was deemed invalid so that if no other diagnostics explain + the suppression, these can be surfaced to the user. -/ + coreDiagnostics : List DiagnosticModel := [] /-- The translation monad: state over Except, allowing both accumulated diagnostics and hard failures -/ @[expose] abbrev TranslateM := OptionT (StateM TranslateState) @@ -73,8 +76,9 @@ structure TranslateState where def emitDiagnostic (d : DiagnosticModel) : TranslateM Unit := modify fun s => { s with diagnostics := s.diagnostics ++ [d] } -private def invalidCoreType : TranslateM LMonoTy := do - modify fun s => { s with coreProgramHasSuperfluousErrors := true } +private def invalidCoreType (source : Option FileRange) (reason : String) : TranslateM LMonoTy := do + modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ + [diagnosticFromSource source reason DiagnosticType.StrataBug] } return .tcons s!"LaurelResolutionErrorPlaceholder" [] /- @@ -98,15 +102,15 @@ def translateType (ty : HighTypeMd) : TranslateM LMonoTy := do | some (.datatypeDefinition dt) => return .tcons dt.name.text [] | some (.datatypeConstructor typeName _) => return .tcons typeName.text [] | _ => do -- resolution should have already emitted a diagnostic - modify fun s => { s with coreProgramHasSuperfluousErrors := true } + modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ + [diagnosticFromSource ty.source s!"UserDefined type could not be resolved to a composite or datatype" DiagnosticType.StrataBug] } return .tcons "Composite" [] | .TCore s => return .tcons s [] | .TReal => return LMonoTy.real - | .MultiValuedExpr _ => invalidCoreType - | .Unknown => invalidCoreType + | .MultiValuedExpr _ => invalidCoreType ty.source "MultiValuedExpr type encountered during Core translation" + | .Unknown => invalidCoreType ty.source "Unknown type encountered during Core translation" | _ => do - emitDiagnostic (diagnosticFromSource ty.source "cannot translate type to Core: not supported yet" DiagnosticType.StrataBug) - invalidCoreType + invalidCoreType ty.source s!"cannot translate type to Core: not supported yet" termination_by ty.val decreasing_by all_goals (first | (cases elementType; term_by_mem) | (cases keyType; term_by_mem) | (cases valueType; term_by_mem)) @@ -131,7 +135,7 @@ private def freshId : TranslateM Nat := do /-- Throw a hard diagnostic error, aborting the current translation -/ def throwExprDiagnostic (d : DiagnosticModel): TranslateM Core.Expression.Expr := do emitDiagnostic d - modify fun s => { s with coreProgramHasSuperfluousErrors := true } + modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ [d] } let id ← freshId return LExpr.fvar () (⟨s!"DUMMY_VAR_{id}", ()⟩) none @@ -349,7 +353,7 @@ private def exprAsUnusedInit (expr : StmtExprMd) (md : Imperative.MetaData Core. def throwStmtDiagnostic (d : DiagnosticModel): TranslateM (List Core.Statement) := do emitDiagnostic d - modify fun s => { s with coreProgramHasSuperfluousErrors := true } + modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ [d] } return [] /-- @@ -497,8 +501,8 @@ def translateStmt (stmt : StmtExprMd) | none => return [.exit "$body" md] | some _ => - emitDiagnostic $ md.toDiagnostic "Return statement with value should have been eliminated by EliminateValueReturns pass" DiagnosticType.StrataBug - modify fun s => { s with coreProgramHasSuperfluousErrors := true } + let d := md.toDiagnostic "Return statement with value should have been eliminated by EliminateValueReturns pass" DiagnosticType.StrataBug + modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ [d] } return [.exit "$body" md] | .While cond invariants decreasesExpr body => let condExpr ← translateExpr cond diff --git a/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean b/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean index 701405b362..7b86139661 100644 --- a/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean +++ b/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean @@ -58,21 +58,30 @@ private def roundtrip (input : String) : IO String := do /-- info: procedure foo() -{ assert true; assert false }; + opaque +{ + assert true; + assert false +}; -/ #guard_msgs in -#eval do IO.println (← roundtrip r"procedure foo() { assert true; assert false };") +#eval do IO.println (← roundtrip r"procedure foo() opaque { assert true; assert false };") /-- info: procedure add(x: int, y: int): int -{ x + y }; + opaque +{ + x + y +}; -/ #guard_msgs in -#eval do IO.println (← roundtrip r"procedure add(x: int, y: int): int { x + y };") +#eval do IO.println (← roundtrip r"procedure add(x: int, y: int): int opaque { x + y };") /-- info: function aFunction(x: int): int -{ x }; +{ + x +}; -/ #guard_msgs in #eval do IO.println (← roundtrip r"function aFunction(x: int): int { x };") @@ -90,17 +99,22 @@ composite Point { /-- info: procedure test(x: int): int -{ if x > 0 then x else 0 - x }; + opaque +{ + if x > 0 then x else 0 - x +}; -/ #guard_msgs in -#eval do IO.println (← roundtrip r"procedure test(x: int): int { if x > 0 then x else 0 - x };") +#eval do IO.println (← roundtrip r"procedure test(x: int): int opaque { if x > 0 then x else 0 - x };") /-- info: procedure divide(x: int, y: int): int requires y != 0 opaque ensures result >= 0 -{ x / y }; +{ + x / y +}; -/ #guard_msgs in #eval do IO.println (← roundtrip r" @@ -113,11 +127,15 @@ procedure divide(x: int, y: int): int /-- info: procedure test() -{ assert forall(x: int) => x == x; assert exists(y: int) => y > 0 }; + opaque +{ + assert forall(x: int) => x == x; + assert exists(y: int) => y > 0 +}; -/ #guard_msgs in #eval do IO.println (← roundtrip r" -procedure test() { +procedure test() opaque { assert forall(x: int) => x == x; assert exists(y: int) => y > 0 }; @@ -127,7 +145,12 @@ procedure test() { info: composite Point { var x: int var y: int } procedure test(): int -{ var p: Point := new Point; p#x := 5; p#x }; + opaque +{ + var p: Point := new Point; + p#x := 5; + p#x +}; -/ #guard_msgs in #eval do IO.println (← roundtrip r" @@ -135,7 +158,7 @@ composite Point { var x: int var y: int } -procedure test(): int { +procedure test(): int opaque { var p: Point := new Point; p#x := 5; p#x @@ -160,25 +183,34 @@ info: composite Animal { } composite Dog extends Animal { } procedure test(a: Animal): bool -{ a is Dog }; + opaque +{ + a is Dog +}; -/ #guard_msgs in #eval do IO.println (← roundtrip r" composite Animal {} composite Dog extends Animal {} -procedure test(a: Animal): bool { a is Dog }; +procedure test(a: Animal): bool opaque { a is Dog }; ") -- Additional coverage: while loops /-- info: procedure test() -{ var x: int := 0; while(x < 10) - invariant x >= 0 { x := x + 1 } }; + opaque +{ + var x: int := 0; + while(x < 10) + invariant x >= 0 { + x := x + 1 + } +}; -/ #guard_msgs in #eval do IO.println (← roundtrip r" -procedure test() { +procedure test() opaque { var x: int := 0; while(x < 10) invariant x >= 0 @@ -203,7 +235,10 @@ procedure modify(c: Container) opaque ensures true modifies c -{ c#value := c#value + 1; true }; +{ + c#value := c#value + 1; + true +}; -/ #guard_msgs in #eval do IO.println (← roundtrip r" @@ -219,9 +254,13 @@ procedure modify(c: Container) /-- info: procedure test(): int -{ }; + opaque +{ + +}; -/ #guard_msgs in -#eval do IO.println (← roundtrip r"procedure test(): int { };") +#eval do IO.println (← roundtrip r"procedure test(): int opaque { };") end Strata.Laurel +end diff --git a/StrataTest/Languages/Laurel/ConstrainedTypeElimTest.lean b/StrataTest/Languages/Laurel/ConstrainedTypeElimTest.lean index 0811d5e955..8a293059d5 100644 --- a/StrataTest/Languages/Laurel/ConstrainedTypeElimTest.lean +++ b/StrataTest/Languages/Laurel/ConstrainedTypeElimTest.lean @@ -44,16 +44,26 @@ def parseLaurelAndElim (input : String) : IO Program := do /-- info: function nat$constraint(x: int): bool -{ x >= 0 }; +{ + x >= 0 +}; procedure test(n: int) returns (r: int) requires nat$constraint(n) opaque ensures nat$constraint(r) -{ assert r >= 0; var y: int := n; assert nat$constraint(y); return y }; +{ + assert r >= 0; + var y: int := n; + assert nat$constraint(y); + return y +}; procedure $witness_nat() opaque -{ var $witness: int := 0; assert nat$constraint($witness) }; +{ + var $witness: int := 0; + assert nat$constraint($witness) +}; -/ #guard_msgs in #eval! do @@ -77,12 +87,26 @@ procedure test(b: bool) { /-- info: function pos$constraint(v: int): bool -{ v > 0 }; +{ + v > 0 +}; procedure test(b: bool) -{ if b then { var x: int := 1; assert pos$constraint(x) }; { var x: int := -5; x := -10 } }; +{ + if b then { + var x: int := 1; + assert pos$constraint(x) + }; + { + var x: int := -5; + x := -10 + } +}; procedure $witness_pos() opaque -{ var $witness: int := 1; assert pos$constraint($witness) }; +{ + var $witness: int := 1; + assert pos$constraint($witness) +}; -/ #guard_msgs in #eval! do @@ -94,7 +118,7 @@ procedure $witness_pos() -- The variable has no known value, only the type constraint is assumed. def uninitProgram : String := r" constrained posint = x: int where x > 0 witness 1 -procedure f() { +procedure f() opaque { var x: posint; assert x == 1 }; @@ -102,12 +126,22 @@ procedure f() { /-- info: function posint$constraint(x: int): bool -{ x > 0 }; +{ + x > 0 +}; procedure f() -{ var x: int; assume posint$constraint(x); assert x == 1 }; + opaque +{ + var x: int; + assume posint$constraint(x); + assert x == 1 +}; procedure $witness_posint() opaque -{ var $witness: int := 1; assert posint$constraint($witness) }; +{ + var $witness: int := 1; + assert posint$constraint($witness) +}; -/ #guard_msgs in #eval! do diff --git a/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean b/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean index f3a3acbd6f..98e1706a3f 100644 --- a/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean +++ b/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean @@ -23,6 +23,7 @@ namespace Strata.Laurel def blockStmtLiftingProgram : String := r" procedure assertInBlockExpr() + opaque { var x: int := 0; var y: int := { assert x == 0; x := 1; x }; @@ -44,7 +45,17 @@ def parseLaurelAndLift (input : String) : IO Program := do /-- info: procedure assertInBlockExpr() -{ var x: int := 0; assert x == 0; var $x_0: int := x; x := 1; var y: int := { x }; assert y == 1 }; + opaque +{ + var x: int := 0; + assert x == 0; + var $x_0: int := x; + x := 1; + var y: int := { + x + }; + assert y == 1 +}; -/ #guard_msgs in #eval! do diff --git a/StrataTest/Languages/Laurel/LiftHolesTest.lean b/StrataTest/Languages/Laurel/LiftHolesTest.lean index 0f5a4997d3..921704dcf1 100644 --- a/StrataTest/Languages/Laurel/LiftHolesTest.lean +++ b/StrataTest/Languages/Laurel/LiftHolesTest.lean @@ -46,11 +46,14 @@ info: function $hole_0() returns ($result: int) opaque; procedure test() -{ var x: int := 1 + $hole_0() }; + opaque +{ + var x: int := 1 + $hole_0() +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() { var x: int := 1 + }; +procedure test() opaque { var x: int := 1 + }; " -- Bare Hole as Assign Declare initializer → replaced with call (no longer preserved as havoc). @@ -59,11 +62,14 @@ info: function $hole_0() returns ($result: int) opaque; procedure test() -{ var x: int := $hole_0() }; + opaque +{ + var x: int := $hole_0() +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() { var x: int := }; +procedure test() opaque { var x: int := }; " -- Hole in comparison arg inside assert → int (inferred from sibling literal). @@ -72,11 +78,14 @@ info: function $hole_0() returns ($result: int) opaque; procedure test() -{ assert $hole_0() > 0 }; + opaque +{ + assert $hole_0() > 0 +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() { assert > 0 }; +procedure test() opaque { assert > 0 }; " -- Hole directly as assert condition → bool. @@ -85,11 +94,14 @@ info: function $hole_0() returns ($result: bool) opaque; procedure test() -{ assert $hole_0() }; + opaque +{ + assert $hole_0() +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() { assert }; +procedure test() opaque { assert }; " -- Hole directly as assume condition → bool. @@ -98,11 +110,14 @@ info: function $hole_0() returns ($result: bool) opaque; procedure test() -{ assume $hole_0() }; + opaque +{ + assume $hole_0() +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() { assume }; +procedure test() opaque { assume }; " -- Hole as if-then-else condition → bool. @@ -111,11 +126,16 @@ info: function $hole_0() returns ($result: bool) opaque; procedure test() -{ if $hole_0() then { assert true } }; + opaque +{ + if $hole_0() then { + assert true + } +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() { if then { assert true } }; +procedure test() opaque { if then { assert true } }; " -- Hole in then-branch of if-then-else inside typed local variable → int. @@ -124,11 +144,14 @@ info: function $hole_0() returns ($result: int) opaque; procedure test() -{ var x: int := if true then $hole_0() else 0 }; + opaque +{ + var x: int := if true then $hole_0() else 0 +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() { var x: int := if true then else 0 }; +procedure test() opaque { var x: int := if true then else 0 }; " -- Hole as while-loop condition → bool. @@ -137,11 +160,16 @@ info: function $hole_0() returns ($result: bool) opaque; procedure test() -{ while($hole_0()) { } }; + opaque +{ + while($hole_0()) { + ⏎ + } +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() { while() {} }; +procedure test() opaque { while() {} }; " -- Hole as while-loop invariant → bool. @@ -150,12 +178,17 @@ info: function $hole_0() returns ($result: bool) opaque; procedure test() -{ while(true) - invariant $hole_0() { } }; + opaque +{ + while(true) + invariant $hole_0() { + ⏎ + } +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() { while(true) invariant {} }; +procedure test() opaque { while(true) invariant {} }; " /-! ## Operators -/ @@ -166,11 +199,14 @@ info: function $hole_0() returns ($result: bool) opaque; procedure test() -{ assert true && $hole_0() }; + opaque +{ + assert true && $hole_0() +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() { assert true && }; +procedure test() opaque { assert true && }; " -- Hole in Neg inside typed local variable → int. @@ -179,11 +215,14 @@ info: function $hole_0() returns ($result: int) opaque; procedure test() -{ var x: int := -$hole_0() }; + opaque +{ + var x: int := -$hole_0() +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() { var x: int := - }; +procedure test() opaque { var x: int := - }; " -- Hole in StrConcat inside typed local variable → string. @@ -192,11 +231,14 @@ info: function $hole_0() returns ($result: string) opaque; procedure test() -{ var s: string := "hello" ++ $hole_0() }; + opaque +{ + var s: string := "hello" ++ $hole_0() +}; -/ #guard_msgs in #eval! parseElimAndPrint - "procedure test() { var s: string := \"hello\" ++ };" + "procedure test() opaque { var s: string := \"hello\" ++ };" /-! ## Multiple holes -/ @@ -209,11 +251,14 @@ function $hole_1() returns ($result: int) opaque; procedure test() -{ var x: int := $hole_0() + $hole_1() }; + opaque +{ + var x: int := $hole_0() + $hole_1() +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() { var x: int := + }; +procedure test() opaque { var x: int := + }; " -- Holes across statements: Mul arg (int) then assert condition (bool). @@ -225,11 +270,15 @@ function $hole_1() returns ($result: bool) opaque; procedure test() -{ var x: int := 2 * $hole_0(); assert $hole_1() }; + opaque +{ + var x: int := 2 * $hole_0(); + assert $hole_1() +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() { var x: int := 2 * ; assert }; +procedure test() opaque { var x: int := 2 * ; assert }; " /-! ## Combinations: holes in nested contexts -/ @@ -240,11 +289,16 @@ info: function $hole_0() returns ($result: int) opaque; procedure test() -{ if 1 + $hole_0() > 0 then { assert true } }; + opaque +{ + if 1 + $hole_0() > 0 then { + assert true + } +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() { if 1 + > 0 then { assert true } }; +procedure test() opaque { if 1 + > 0 then { assert true } }; " -- Hole in Implies inside while invariant → bool. @@ -253,12 +307,18 @@ info: function $hole_0() returns ($result: bool) opaque; procedure test() -{ var p: bool; while(true) - invariant p ==> $hole_0() { } }; + opaque +{ + var p: bool; + while(true) + invariant p ==> $hole_0() { + ⏎ + } +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() { var p: bool; while(true) invariant p ==> {} }; +procedure test() opaque { var p: bool; while(true) invariant p ==> {} }; " -- Hole in Mul inside typed local variable with real type → real. @@ -267,11 +327,14 @@ info: function $hole_0() returns ($result: real) opaque; procedure test() -{ var r: real := 3.14 * $hole_0() }; + opaque +{ + var r: real := 3.14 * $hole_0() +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() { var r: real := 3.14 * }; +procedure test() opaque { var r: real := 3.14 * }; " /-! ## Call argument and return type inference -/ @@ -282,11 +345,14 @@ info: function $hole_0(n: int) returns ($result: int) opaque; procedure test(n: int) -{ assert n > $hole_0(n) }; + opaque +{ + assert n > $hole_0(n) +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test(n: int) { assert n > }; +procedure test(n: int) opaque { assert n > }; " /-! ## Holes in functions -/ @@ -297,11 +363,14 @@ info: function $hole_0(x: int) returns ($result: int) opaque; function test(x: int): int -{ $hole_0(x) }; + opaque +{ + $hole_0(x) +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -function test(x: int): int { }; +function test(x: int): int opaque { }; " /-! ## Nondeterministic holes () -/ @@ -309,11 +378,14 @@ function test(x: int): int { }; -- Nondet hole in procedure → preserved after eliminateHoles (lifted by liftExpressionAssignments). /-- info: procedure test() -{ assert }; + opaque +{ + assert +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() { assert }; +procedure test() opaque { assert }; " -- Mixed: det hole eliminated, nondet hole preserved. @@ -322,11 +394,15 @@ info: function $hole_0() returns ($result: int) opaque; procedure test() -{ var x: int := $hole_0(); assert }; + opaque +{ + var x: int := $hole_0(); + assert +}; -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() { var x: int := ; assert }; +procedure test() opaque { var x: int := ; assert }; " -- Nondet hole in function → should be rejected (not tested here since @@ -344,7 +420,9 @@ info: function $hole_0() returns ($result: IntList) opaque; procedure test() -{ var x: int := IntList..head($hole_0()) }; +{ + var x: int := IntList..head($hole_0()) +}; -/ #guard_msgs in #eval! parseElimAndPrint r" @@ -358,7 +436,9 @@ info: function $hole_0() returns ($result: IntList) opaque; procedure test() -{ var x: int := IntList..head!($hole_0()) }; +{ + var x: int := IntList..head!($hole_0()) +}; -/ #guard_msgs in #eval! parseElimAndPrint r" @@ -372,7 +452,9 @@ info: function $hole_0() returns ($result: IntList) opaque; procedure test() -{ assert IntList..isCons($hole_0()) }; +{ + assert IntList..isCons($hole_0()) +}; -/ #guard_msgs in #eval! parseElimAndPrint r" diff --git a/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean b/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean index e88deb4143..f44067848c 100644 --- a/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean +++ b/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean @@ -42,9 +42,17 @@ private def printLifted (input : String) : IO Unit := do /-- info: procedure impure(): int -{ var x: int := 0; x := x + 1; x }; +{ + var x: int := 0; + x := x + 1; + x +}; procedure test() -{ var $c_0: int; $c_0 := impure(); assert $c_0 == 1 }; +{ + var $c_0: int; + $c_0 := impure(); + assert $c_0 == 1 +}; -/ #guard_msgs in #eval! printLifted r" @@ -62,7 +70,10 @@ procedure test() { /-- info: procedure test() -{ var x: int := 0; assert (x := 2) == 2 }; +{ + var x: int := 0; + assert (x := 2) == 2 +}; -/ #guard_msgs in #eval! printLifted r" @@ -76,9 +87,17 @@ procedure test() { /-- info: procedure impure(): int -{ var x: int := 0; x := x + 1; x }; +{ + var x: int := 0; + x := x + 1; + x +}; procedure test() -{ var $c_0: int; $c_0 := impure(); assume $c_0 == 1 }; +{ + var $c_0: int; + $c_0 := impure(); + assume $c_0 == 1 +}; -/ #guard_msgs in #eval! printLifted r" @@ -99,9 +118,16 @@ procedure test() { /-- info: procedure multi_out(x: int) returns (r: int, extra: int) -{ r := x + 1; extra := x + 2 }; +{ + r := x + 1; + extra := x + 2 +}; procedure test() -{ var $c_0: BUG_MultiValuedExpr; $c_0 := multi_out(5); assert $c_0 == 6 }; +{ + var $c_0: BUG_MultiValuedExpr; + $c_0 := multi_out(5); + assert $c_0 == 6 +}; -/ #guard_msgs in #eval! printLifted r" From f246686a69e2a5e7900b150985b951d09ee85b02 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 26 May 2026 09:57:37 +0000 Subject: [PATCH 002/115] Introduce transparency pass --- Examples/expected/HeapReasoning.core.expected | 5 +- Strata/Languages/Core/ProcedureEval.lean | 4 +- .../Laurel/CoreGroupingAndOrdering.lean | 135 ++++++++------ .../Languages/Laurel/DesugarShortCircuit.lean | 5 +- .../Laurel/EliminateValueReturns.lean | 11 ++ .../Laurel/HeapParameterization.lean | 6 +- Strata/Languages/Laurel/InferHoleTypes.lean | 8 +- Strata/Languages/Laurel/Laurel.lean | 5 + .../Laurel/LaurelCompilationPipeline.lean | 77 +++++++- .../Laurel/LaurelToCoreTranslator.lean | 141 +++++++++------ .../Languages/Laurel/PackMultipleOutputs.lean | 166 ++++++++++++++++++ Strata/Languages/Laurel/Resolution.lean | 29 +-- Strata/Languages/Laurel/TransparencyPass.lean | 159 +++++++++++++++++ .../CBMC/contracts/test_contract.lr.st | 4 +- .../CBMC/contracts/test_contracts_e2e.sh | 40 ++++- StrataTest/Languages/Boole/deterministic.lean | 10 +- .../Core/Examples/FreeRequireEnsure.lean | 13 +- .../Core/Tests/PolymorphicProcedureTest.lean | 9 - .../AbstractToConcreteTreeTranslatorTest.lean | 36 ++-- .../Laurel/ConstrainedTypeElimTest.lean | 13 +- .../Laurel/DivisionByZeroCheckTest.lean | 2 +- .../Languages/Laurel/DuplicateNameTests.lean | 18 +- ...odyError.lean => T20_TransparentBody.lean} | 17 +- .../Fundamentals/T2_ImpureExpressions.lean | 8 +- .../T2_ImpureExpressionsError.lean | 18 +- .../Examples/Fundamentals/T3_ControlFlow.lean | 3 +- .../Fundamentals/T3_ControlFlowError.lean | 5 + .../Examples/Fundamentals/T4_LoopJumps.lean | 4 +- .../Fundamentals/T5_ProcedureCalls.lean | 5 +- .../Fundamentals/T6_Preconditions.lean | 2 +- .../Examples/Fundamentals/T7_Decreases.lean | 1 + .../Fundamentals/T8_Postconditions.lean | 6 +- .../Fundamentals/T8c_BodilessInlining.lean | 33 +--- .../Fundamentals/T9_Nondeterministic.lean | 3 +- .../Examples/Objects/T1_MutableFields.lean | 17 +- .../Examples/Objects/T2_ModifiesClauses.lean | 4 +- .../Examples/Objects/T3_ReadsClauses.lr.st | 4 +- .../Examples/Objects/T4_ImmutableFields.lr.st | 4 +- .../Examples/Objects/T5_inheritance.lean | 2 +- .../Objects/T8_NonCompositeModifies.lean | 4 +- .../Examples/Objects/WIP/5. Allocation.lr.st | 12 +- .../Objects/WIP/5. Constructors.lr.st | 8 +- .../Examples/Objects/WIP/6. TypeTests.lr.st | 3 +- .../Examples/Objects/WIP/9. Closures.lr.st | 6 +- .../Examples/PrimitiveTypes/T2_String.lean | 2 +- .../Languages/Laurel/LiftHolesTest.lean | 83 ++++++--- .../Languages/Laurel/StatisticsTest.lean | 1 + .../Languages/Python/PySpecArgTypeTest.lean | 7 +- StrataTest/Util/TestDiagnostics.lean | 4 + 49 files changed, 857 insertions(+), 305 deletions(-) create mode 100644 Strata/Languages/Laurel/PackMultipleOutputs.lean create mode 100644 Strata/Languages/Laurel/TransparencyPass.lean rename StrataTest/Languages/Laurel/Examples/Fundamentals/{T20_TransparentBodyError.lean => T20_TransparentBody.lean} (60%) diff --git a/Examples/expected/HeapReasoning.core.expected b/Examples/expected/HeapReasoning.core.expected index f19f38c7a6..a936adc936 100644 --- a/Examples/expected/HeapReasoning.core.expected +++ b/Examples/expected/HeapReasoning.core.expected @@ -2,7 +2,6 @@ Successfully parsed. HeapReasoning.core.st(98, 2) [modifiesFrameRef1]: ✅ pass HeapReasoning.core.st(103, 2) [modifiesFrameRef1]: ✅ pass HeapReasoning.core.st(108, 2) [modifiesFrameRef1]: ✅ pass - [Container_ctor_ensures_4]: ✅ pass HeapReasoning.core.st(86, 2) [Container_ctor_ensures_7]: ✅ pass HeapReasoning.core.st(87, 2) [Container_ctor_ensures_8]: ✅ pass HeapReasoning.core.st(88, 2) [Container_ctor_ensures_9]: ✅ pass @@ -12,7 +11,6 @@ HeapReasoning.core.st(169, 2) [modifiesFrameRef2]: ✅ pass HeapReasoning.core.st(172, 2) [modifiesFrameRef1Next]: ✅ pass HeapReasoning.core.st(177, 2) [modifiesFrameRef2Next]: ✅ pass HeapReasoning.core.st(132, 2) [UpdateContainers_ensures_5]: ✅ pass - [UpdateContainers_ensures_6]: ✅ pass HeapReasoning.core.st(150, 2) [UpdateContainers_ensures_14]: ✅ pass HeapReasoning.core.st(151, 2) [UpdateContainers_ensures_15]: ✅ pass HeapReasoning.core.st(152, 2) [UpdateContainers_ensures_16]: ✅ pass @@ -51,5 +49,4 @@ HeapReasoning.core.st(238, 2) [c2Pineapple0]: ✅ pass HeapReasoning.core.st(240, 2) [c1NextEqC2]: ✅ pass HeapReasoning.core.st(241, 2) [c2NextEqC1]: ✅ pass HeapReasoning.core.st(195, 2) [Main_ensures_1]: ✅ pass - [Main_ensures_2]: ✅ pass -All 53 goals passed. +All 50 goals passed. diff --git a/Strata/Languages/Core/ProcedureEval.lean b/Strata/Languages/Core/ProcedureEval.lean index 9ea328f41b..fcf6721d20 100644 --- a/Strata/Languages/Core/ProcedureEval.lean +++ b/Strata/Languages/Core/ProcedureEval.lean @@ -93,13 +93,13 @@ def eval (E : Env) (p : Procedure) : Env × Statistics := match check.attr with | .Free => -- NOTE: A free postcondition is not checked. - -- We simply change a free-postcondition to "true", but + -- We simply change a free-postcondition to "assume true", but -- keep a record in the metadata field. -- TODO: Perhaps introduce an "opaque" expression construct -- that hides the expression from the evaluator, allowing us -- to retain the postcondition body instead of replacing it -- with "true". - (.assert label (.true ()) + (.assume label (.true ()) ((Imperative.MetaData.pushElem #[] (.label label) diff --git a/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean b/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean index f75057d88f..77b51d869f 100644 --- a/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean +++ b/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean @@ -5,9 +5,10 @@ -/ module -public import Strata.Languages.Laurel.Laurel +public import Strata.Languages.Laurel.TransparencyPass import Strata.DL.Lambda.LExpr import Strata.DDM.Util.Graph.Tarjan +import Strata.Languages.Laurel.Grammar.AbstractToConcreteTreeTranslator /-! ## Grouping and Ordering for Core Translation @@ -15,8 +16,6 @@ import Strata.DDM.Util.Graph.Tarjan Utilities for computing the grouping and topological ordering of Laurel declarations before they are emitted as Strata Core declarations. -- `groupDatatypesByScc` — groups mutually recursive datatypes into SCC groups - using Tarjan's SCC algorithm. - `computeSccDecls` — builds the procedure call graph, runs Tarjan's SCC algorithm, and returns each SCC as a list of procedures paired with a flag indicating whether the SCC is recursive. The result is in reverse topological @@ -90,7 +89,7 @@ def collectStaticCallNames (expr : StmtExprMd) : List String := | .InstanceCall t _ args => collectStaticCallNames t ++ args.flatMap (fun a => collectStaticCallNames a) | .Old v | .Fresh v | .Assume v => collectStaticCallNames v - | .Assert ⟨cond, _summary⟩ => collectStaticCallNames cond + | .Assert ⟨cond, _summary, _⟩ => collectStaticCallNames cond | .ProveBy v p => collectStaticCallNames v ++ collectStaticCallNames p | .ReferenceEquals l r => collectStaticCallNames l ++ collectStaticCallNames r | .AsType t _ | .IsType t _ => collectStaticCallNames t @@ -113,27 +112,24 @@ Build the procedure call graph, run Tarjan's SCC algorithm, and return each SCC as a list of procedures paired with a flag indicating whether the SCC is recursive. Results are in reverse topological order: dependencies before dependents. -Procedures with an `invokeOn` trigger are placed as early as possible — before -unrelated procedures without one — by stably partitioning them first before building +Procedures with `invokeOn` are placed as early as possible — before +unrelated procedures without them — by stably partitioning them first before building the graph. Tarjan then naturally assigns them lower indices, causing them to appear earlier in the output. - -External procedures are excluded. -/ -public def computeSccDecls (program : Program) : List (List Procedure × Bool) := - -- External procedures are completely ignored (not translated to Core). +public def computeSccDecls (program : UnorderedCoreWithLaurelTypes) : List (List Procedure × Bool) := -- Stable partition: procedures with invokeOn come first, preserving relative -- order within each group. Tarjan then places them earlier in the topological output. + let allProcs := program.functions ++ program.coreProcedures let (withInvokeOn, withoutInvokeOn) := - (program.staticProcedures.filter (fun p => !p.body.isExternal)) - |>.partition (fun p => p.invokeOn.isSome) - let nonExternal : List Procedure := withInvokeOn ++ withoutInvokeOn + allProcs.partition (fun p => p.invokeOn.isSome) + let orderedProcs : List Procedure := withInvokeOn ++ withoutInvokeOn - -- Build a call-graph over all non-external procedures. + -- Build a call-graph over all procedures. -- An edge proc → callee means proc's body/contracts contain a StaticCall to callee. - let nonExternalArr : Array Procedure := nonExternal.toArray + let procsArr : Array Procedure := orderedProcs.toArray let nameToIdx : Std.HashMap String Nat := - nonExternalArr.foldl (fun (acc : Std.HashMap String Nat × Nat) proc => + procsArr.foldl (fun (acc : Std.HashMap String Nat × Nat) proc => (acc.1.insert proc.name.text acc.2, acc.2 + 1)) ({}, 0) |>.1 -- Collect all callee names from a procedure's body and contracts. @@ -149,9 +145,9 @@ public def computeSccDecls (program : Program) : List (List Procedure × Bool) : (bodyExprs ++ contractExprs).flatMap collectStaticCallNames -- Build the OutGraph for Tarjan. - let n := nonExternalArr.size + let n := procsArr.size let graph : Strata.OutGraph n := - nonExternalArr.foldl (fun (acc : Strata.OutGraph n × Nat) proc => + procsArr.foldl (fun (acc : Strata.OutGraph n × Nat) proc => let callerIdx := acc.2 let g := acc.1 let callees := procCallees proc @@ -167,7 +163,7 @@ public def computeSccDecls (program : Program) : List (List Procedure × Bool) : sccs.toList.filterMap fun scc => let procs := scc.toList.filterMap fun idx => - nonExternalArr[idx.val]? + procsArr[idx.val]? if procs.isEmpty then none else let isRecursive := procs.length > 1 || (match scc.toList.head? with @@ -176,60 +172,85 @@ public def computeSccDecls (program : Program) : List (List Procedure × Bool) : some (procs, isRecursive) /-- -A single declaration in an ordered Laurel program. Declarations are in +A single declaration in a CoreWithLaurelTypes program. Declarations are in dependency order (dependencies before dependents). -/ public inductive OrderedDecl where - /-- A group of functions (single non-recursive, or mutually recursive). -/ - | procs (procs : List Procedure) (isRecursive : Bool) + /-- A group of functions (single non-recursive, or mutually recursive). + Invariant: `funcs.length > 1 → isRecursive = true`. -/ + | funcs (funcs : List Procedure) (isRecursive : Bool) + /-- A single (non-functional) procedure. -/ + | procedure (procedure : Procedure) /-- A group of (possibly mutually recursive) datatypes. -/ | datatypes (dts : List DatatypeDefinition) /-- A named constant. -/ | constant (c : Constant) /-- -A Laurel program whose declarations have been grouped and topologically ordered. -Produced by `orderProgram` from a `Program`. +A program whose declarations have been grouped and topologically ordered, +using Laurel types. Produced by `orderFunctionsAndProcedures` from a +`UnorderedCoreWithLaurelTypes`. -/ -public structure OrderedLaurel where +public structure CoreWithLaurelTypes where decls : List OrderedDecl -/-- -Group mutually recursive datatypes into SCC groups using Tarjan's SCC algorithm. -Returns groups in topological order (dependencies before dependents). --/ -public def groupDatatypesByScc (program : Program) : List (List DatatypeDefinition) := - let laurelDatatypes := program.types.filterMap fun td => match td with - | .Datatype dt => some dt - | _ => none - let n := laurelDatatypes.length - if n == 0 then [] else - let nameToIdx : Std.HashMap String Nat := - laurelDatatypes.foldlIdx (fun m i dt => m.insert dt.name.text i) {} - let edges : List (Nat × Nat) := - laurelDatatypes.foldlIdx (fun acc i dt => - (datatypeRefs dt).filterMap nameToIdx.get? |>.foldl (fun acc j => (j, i) :: acc) acc) [] - let g := OutGraph.ofEdges! n edges - let dtsArr := laurelDatatypes.toArray - OutGraph.tarjan g |>.toList.filterMap fun comp => - let members := comp.toList.filterMap fun idx => dtsArr[idx]? - if members.isEmpty then none else some members +open Std (Format ToFormat) -/-- -Group procedures into SCC groups and wrap them as `OrderedDecl.procs`. --/ -public def groupProcsByScc (program : Program) : List OrderedDecl := - (computeSccDecls program).map fun (procs, isRecursive) => - OrderedDecl.procs procs isRecursive +public section + +def formatOrderedDecl : OrderedDecl → Format + | .funcs funcs _ => Format.joinSep (funcs.map ToFormat.format) "\n\n" + | .procedure proc => ToFormat.format proc + | .datatypes dts => Format.joinSep (dts.map ToFormat.format) "\n\n" + | .constant c => ToFormat.format c + +instance : ToFormat OrderedDecl where + format := formatOrderedDecl + +def formatCoreWithLaurelTypes (p : CoreWithLaurelTypes) : Format := + Format.joinSep (p.decls.map formatOrderedDecl) "\n\n" + +instance : ToFormat CoreWithLaurelTypes where + format := formatCoreWithLaurelTypes + +end -- public section /-- -Produce an `OrderedLaurel` from a `Program` by grouping and ordering -procedures via SCC, collecting datatypes, and constants. +Produce a `CoreWithLaurelTypes` from a `UnorderedCoreWithLaurelTypes` by +computing a combined ordering of functions and proofs using the call graph, +then collecting datatypes and constants. + +Functions are grouped into SCCs (for mutual recursion). Proofs are emitted +as individual `procedure` decls. Both participate in the topological ordering +so that axioms are available to functions that need them. -/ -public def orderProgram (program : Program) : OrderedLaurel := - let datatypeDecls := (groupDatatypesByScc program).map OrderedDecl.datatypes +public def orderFunctionsAndProcedures (program : UnorderedCoreWithLaurelTypes) : CoreWithLaurelTypes := + let datatypeDecls := (groupDatatypesByScc' program).map OrderedDecl.datatypes let constantDecls := program.constants.map OrderedDecl.constant - let procDecls := groupProcsByScc program - { decls := datatypeDecls ++ constantDecls ++ procDecls } + let funcNames : Std.HashSet String := + program.functions.foldl (fun s p => s.insert p.name.text) {} + let orderedDecls := (computeSccDecls program).flatMap fun (procs, isRecursive) => + -- Split the SCC into functions and proofs + let (funcs, proofs) := procs.partition (fun p => funcNames.contains p.name.text) + let funcDecl := if funcs.isEmpty then [] else [OrderedDecl.funcs funcs isRecursive] + let proofDecls := proofs.map OrderedDecl.procedure + funcDecl ++ proofDecls + { decls := datatypeDecls ++ constantDecls ++ orderedDecls } +where + /-- Group datatypes from a UnorderedCoreWithLaurelTypes by SCC. -/ + groupDatatypesByScc' (program : UnorderedCoreWithLaurelTypes) : List (List DatatypeDefinition) := + let laurelDatatypes := program.datatypes + let n := laurelDatatypes.length + if n == 0 then [] else + let nameToIdx : Std.HashMap String Nat := + laurelDatatypes.foldlIdx (fun m i dt => m.insert dt.name.text i) {} + let edges : List (Nat × Nat) := + laurelDatatypes.foldlIdx (fun acc i dt => + (datatypeRefs dt).filterMap nameToIdx.get? |>.foldl (fun acc j => (j, i) :: acc) acc) [] + let g := OutGraph.ofEdges! n edges + let dtsArr := laurelDatatypes.toArray + OutGraph.tarjan g |>.toList.filterMap fun comp => + let members := comp.toList.filterMap fun idx => dtsArr[idx]? + if members.isEmpty then none else some members end Strata.Laurel diff --git a/Strata/Languages/Laurel/DesugarShortCircuit.lean b/Strata/Languages/Laurel/DesugarShortCircuit.lean index 6f4e9c5218..ef5982430e 100644 --- a/Strata/Languages/Laurel/DesugarShortCircuit.lean +++ b/Strata/Languages/Laurel/DesugarShortCircuit.lean @@ -5,8 +5,9 @@ -/ module -public import Strata.Languages.Laurel.MapStmtExpr -public import Strata.Languages.Laurel.LiftImperativeExpressions +public import Strata.Languages.Laurel.Resolution +import Strata.Languages.Laurel.LiftImperativeExpressions +import Strata.Languages.Laurel.MapStmtExpr /-! # Desugar Short-Circuit Operators diff --git a/Strata/Languages/Laurel/EliminateValueReturns.lean b/Strata/Languages/Laurel/EliminateValueReturns.lean index f465c6055c..45d5af0ab4 100644 --- a/Strata/Languages/Laurel/EliminateValueReturns.lean +++ b/Strata/Languages/Laurel/EliminateValueReturns.lean @@ -6,6 +6,7 @@ module public import Strata.Languages.Laurel.MapStmtExpr +public import Strata.Languages.Laurel.TransparencyPass /-! # Eliminate Value Returns @@ -87,6 +88,16 @@ def eliminateValueReturnsTransform (program : Program) : Program × Array Diagno ) ([], #[]) ({ program with staticProcedures := procs.reverse }, diags) +/-- Transform an `UnorderedCoreWithLaurelTypes` by eliminating value returns + in all core (non-functional) procedures. -/ +def eliminateValueReturnsTransformUnordered (uc : UnorderedCoreWithLaurelTypes) + : UnorderedCoreWithLaurelTypes × Array DiagnosticModel := + let (procs, diags) := uc.coreProcedures.foldl (fun (ps, ds) proc => + let (proc', procDiags) := eliminateValueReturnsInProc proc + (proc' :: ps, ds ++ procDiags) + ) ([], #[]) + ({ uc with coreProcedures := procs.reverse }, diags) + end -- public section end Laurel diff --git a/Strata/Languages/Laurel/HeapParameterization.lean b/Strata/Languages/Laurel/HeapParameterization.lean index dae58c3caa..0243113aed 100644 --- a/Strata/Languages/Laurel/HeapParameterization.lean +++ b/Strata/Languages/Laurel/HeapParameterization.lean @@ -85,7 +85,7 @@ def collectExpr (expr : StmtExpr) : StateM AnalysisResult Unit := do | .Assigned n => collectExprMd n | .Old v => collectExprMd v | .Fresh v => collectExprMd v - | .Assert ⟨c, _⟩ => collectExprMd c + | .Assert ⟨c, _, _⟩ => collectExprMd c | .Assume c => collectExprMd c | .ProveBy v p => collectExprMd v; collectExprMd p | .ContractOf _ f => collectExprMd f @@ -434,8 +434,8 @@ where | .Assigned n => return [⟨ .Assigned (← recurseOne n), source ⟩] | .Old v => return [⟨ .Old (← recurseOne v), source ⟩] | .Fresh v => return [⟨ .Fresh (← recurseOne v), source ⟩] - | .Assert ⟨condExpr, summary⟩ => - return [⟨ .Assert { condition := ← recurseOne condExpr, summary }, source ⟩] + | .Assert ⟨condExpr, summary, free⟩ => + return [⟨ .Assert { condition := ← recurseOne condExpr, summary, free }, source ⟩] | .Assume c => return [⟨ .Assume (← recurseOne c), source ⟩] | .ProveBy v p => return [⟨ .ProveBy (← recurseOne v) (← recurseOne p), source ⟩] | .ContractOf ty f => return [⟨ .ContractOf ty (← recurseOne f), source ⟩] diff --git a/Strata/Languages/Laurel/InferHoleTypes.lean b/Strata/Languages/Laurel/InferHoleTypes.lean index ff80f37c5e..8f0ae491b3 100644 --- a/Strata/Languages/Laurel/InferHoleTypes.lean +++ b/Strata/Languages/Laurel/InferHoleTypes.lean @@ -149,9 +149,10 @@ private def inferExpr (expr : StmtExprMd) (expectedType : HighTypeMd) : InferHol | some d => pure (some (← inferExpr d (⟨ .TInt, source ⟩))) | none => pure none return ⟨.While (← inferExpr cond ⟨ .TBool, source ⟩) (← invs.mapM (inferExpr · ⟨ .TBool, source ⟩)) dec' (← inferExpr body ⟨ .TVoid, source⟩), source⟩ - | .Assert ⟨condExpr, summary⟩ => - return ⟨.Assert { condition := ← inferExpr condExpr ⟨ .TBool, source ⟩, summary }, source⟩ - | .Assume cond => return ⟨.Assume (← inferExpr cond ⟨ .TBool, source ⟩), source⟩ + | .Assert ⟨condExpr, summary, free⟩ => + return ⟨.Assert { condition := ← inferExpr condExpr ⟨ .TBool, source ⟩, summary, free }, source⟩ + | .Assume cond => + return ⟨.Assume (← inferExpr cond ⟨ .TBool, source ⟩), source⟩ | .Return (some retExpr) => return ⟨.Return (some (← inferExpr retExpr (← get).currentOutputType)), source⟩ | .Old v => return ⟨.Old (← inferExpr v expectedType), source⟩ @@ -180,6 +181,7 @@ private def inferProcedure (proc : Procedure) : InferHoleM Procedure := do /-- Annotate every `.Hole` in the program with a type inferred from context. +Returns the updated program and any diagnostics (e.g. holes whose type could not be inferred). -/ def inferHoleTypes (model : SemanticModel) (program : Program) : Program × List DiagnosticModel × Statistics := let initState : InferHoleState := { model := model, currentOutputType := { val := .Unknown, source := none }} diff --git a/Strata/Languages/Laurel/Laurel.lean b/Strata/Languages/Laurel/Laurel.lean index 86ae83d022..bd888d566e 100644 --- a/Strata/Languages/Laurel/Laurel.lean +++ b/Strata/Languages/Laurel/Laurel.lean @@ -219,6 +219,11 @@ structure Condition where condition : AstNode StmtExpr /-- Optional human-readable summary describing the property being checked. -/ summary : Option String := none + /-- When `true`, this condition is *free*: assumed but not checked. + A free precondition is assumed by the implementation but not asserted at + call sites. A free postcondition is assumed upon return from calls but + not checked on exit from implementations. -/ + free : Bool := false /-- The body of a procedure. A body can be transparent (with a visible diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index a53b30a390..a5535ce11a 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -8,8 +8,11 @@ module public import Strata.Languages.Laurel.LaurelToCoreTranslator import Strata.Languages.Laurel.DesugarShortCircuit import Strata.Languages.Laurel.EliminateReturnsInExpression + import Strata.Languages.Laurel.EliminateValueReturns import Strata.Languages.Laurel.ConstrainedTypeElim + +import Strata.Languages.Laurel.PackMultipleOutputs import Strata.Languages.Laurel.TypeAliasElim import Strata.Languages.Core.Verifier import Strata.Util.Statistics @@ -24,8 +27,9 @@ to Strata Core. The pipeline is: 2. Run a sequence of Laurel-to-Laurel lowering passes (resolution, heap parameterization, type hierarchy, modifies clauses, hole inference, desugaring, lifting, constrained type elimination). -3. Group and order declarations into an `OrderedLaurel`. -4. Translate the `OrderedLaurel` to a `Core.Program`. +3. Run the transparency pass to produce an `UnorderedCoreWithLaurelTypes`. +4. Group and order declarations into a `CoreWithLaurelTypes`. +5. Translate the `CoreWithLaurelTypes` to a `Core.Program`. -/ open Core (VCResult VCResults VerifyOptions) @@ -188,6 +192,46 @@ private def runLaurelPasses (options : LaurelTranslateOptions) return (program, model, allDiags, allStats) +/-- +Convert an `UnorderedCoreWithLaurelTypes` to a flat `Program` suitable for +resolution and program-level passes. Composite types from the original Laurel +program are included so that references to composite types resolve correctly. +-/ +private def toProgram (uc : UnorderedCoreWithLaurelTypes) (laurelProgram : Program) + : Program := + { staticProcedures := uc.functions ++ uc.coreProcedures, + staticFields := [], + types := uc.datatypes.map TypeDefinition.Datatype ++ + -- Hack to compensate for references to composite types not having been updated yet. + laurelProgram.types.filter (fun t => match t with | .Composite _ => true | _ => false), + constants := uc.constants } + +/-- +Reconstruct an `UnorderedCoreWithLaurelTypes` from a resolved `Program`, +preserving the structure of the original `UnorderedCoreWithLaurelTypes`. +-/ +private def fromResolvedProgram (resolvedProgram : Program) + (_original : UnorderedCoreWithLaurelTypes) : UnorderedCoreWithLaurelTypes := + let resolvedProcs := resolvedProgram.staticProcedures + let resolvedDatatypes := resolvedProgram.types.filterMap fun td => + match td with | .Datatype dt => some dt | _ => none + { functions := resolvedProcs.filter (·.isFunctional) + coreProcedures := resolvedProcs.filter (!·.isFunctional) + datatypes := resolvedDatatypes + constants := resolvedProgram.constants } + +/-- +Resolve an `UnorderedCoreWithLaurelTypes` by converting to a flat `Program`, +running the resolution pass, and reconstructing the result. Returns the +resolved `UnorderedCoreWithLaurelTypes` and the `SemanticModel`. +-/ +def resolveUnorderedCore (uc : UnorderedCoreWithLaurelTypes) + (laurelProgram : Program) (existingModel : Option SemanticModel := none) + : UnorderedCoreWithLaurelTypes × SemanticModel := + let fnProgram := toProgram uc laurelProgram + let fnResolveResult := resolve fnProgram existingModel + (fromResolvedProgram fnResolveResult.program uc, fnResolveResult.model) + /-- Translate Laurel Program to Core Program, also returning the lowered Laurel program. @@ -202,7 +246,19 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) | none => Strata.Pipeline.PipelineContext.create (outputMode := .quiet) runPipelineM options.keepAllFilesPrefix do let (program, model, passDiags, stats) ← runLaurelPasses options pctx program - let ordered := orderProgram program + let unorderedCore := transparencyPass program + emit "transparencyPass" "core.st" unorderedCore + let unorderedCore := packMultipleOutputsInFunctions unorderedCore + -- let unorderedCore := inlineLocalVariablesInExpressions unorderedCore + + -- Resolve so that identifiers introduced by earlier passes get uniqueIds. + let (unorderedCore, model) := resolveUnorderedCore unorderedCore program (some model) + + -- Re-resolve after lifting so that freshly introduced variables (e.g. $cndtn_N) + -- created by liftExpressionAssignments also get uniqueIds in the model. + let (unorderedCore, fnModel) := resolveUnorderedCore unorderedCore program (some model) + + let coreWithLaurelTypes := orderFunctionsAndProcedures unorderedCore -- This early return is a simple way to protect against duplicative errors. Without this return, -- resolution errors reported by Laurel would also be reported by Core. @@ -211,18 +267,21 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) if passDiags.any (·.type != .Warning) then return (none, passDiags, program, stats) - let initState : TranslateState := { model := model, overflowChecks := options.overflowChecks } + emit "CoreWithLaurelTypes" "core.st" coreWithLaurelTypes + let initState : TranslateState := { model := fnModel, overflowChecks := options.overflowChecks } let (coreProgramOption, translateState) := - runTranslateM initState (translateLaurelToCore options program ordered) - if let some coreProgram := coreProgramOption then - emit "CoreProgram" "core.st" coreProgram - let mut allDiagnostics := passDiags ++ translateState.diagnostics + runTranslateM initState (translateLaurelToCore options program coreWithLaurelTypes) + -- Because of the duplication between functions and procedures, this translation is liable to create duplicate diagnostics + -- User errors should be checked in an earlier phase, and all dumb translation errors are Strata bugs + let mut allDiagnostics: List DiagnosticModel := passDiags ++ translateState.diagnostics.eraseDups; if translateState.coreDiagnostics.length > 0 && allDiagnostics.isEmpty then allDiagnostics := allDiagnostics ++ translateState.coreDiagnostics + if coreProgramOption.isSome then + emit "Core" "core.st" coreProgramOption.get! let coreProgramOption := - if !translateState.coreDiagnostics.isEmpty then none else coreProgramOption + if translateState.coreDiagnostics.isEmpty then coreProgramOption else none return (coreProgramOption, allDiagnostics, program, stats) /-- diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index cc33f2dae8..d2585a3873 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -33,7 +33,7 @@ import Strata.Languages.Laurel.ConstrainedTypeElim import Strata.Util.Tactics open Core (VCResult VCResults VerifyOptions) -open Core (intAddOp intSubOp intMulOp intSafeDivOp intSafeModOp intSafeDivTOp intSafeModTOp intNegOp intLtOp intLeOp intGtOp intGeOp boolAndOp boolOrOp boolNotOp boolImpliesOp strConcatOp) +open Core (intAddOp intSubOp intMulOp intDivOp intSafeDivOp intModOp intSafeModOp intDivTOp intSafeDivTOp intModTOp intSafeModTOp intNegOp intLtOp intLeOp intGtOp intGeOp boolAndOp boolOrOp boolNotOp boolImpliesOp strConcatOp) open Core (realAddOp realSubOp realMulOp realDivOp realNegOp realLtOp realLeOp realGtOp realGeOp) namespace Strata.Laurel @@ -68,6 +68,11 @@ structure TranslateState where why the program was deemed invalid so that if no other diagnostics explain the suppression, these can be surfaced to the user. -/ coreDiagnostics : List DiagnosticModel := [] + /-- When `true`, use safe division (`intSafeDivOp`) and safe datatype selectors + (with preconditions). When `false`, use unsafe division (`intDivOp`) and + unsafe datatype selectors (without preconditions). + Set to `true` for proof procedures and `false` for functions. -/ + proof : Bool := false /-- The translation monad: state over Except, allowing both accumulated diagnostics and hard failures -/ @[expose] abbrev TranslateM := OptionT (StateM TranslateState) @@ -76,6 +81,25 @@ structure TranslateState where def emitDiagnostic (d : DiagnosticModel) : TranslateM Unit := modify fun s => { s with diagnostics := s.diagnostics ++ [d] } +/-- Adjust a datatype selector (destructor) name based on the `proof` flag. + Destructor names contain `..` (e.g. `IntList..head`, `IntList..head!`). + Tester names also contain `..` but start with `is` after the separator. + - `proof = true` → use safe selectors (strip `!` suffix) + - `proof = false` → use unsafe selectors (add `!` suffix) -/ +private def adjustSelectorName (name : String) (proof : Bool) : String := + -- Only adjust destructor names (contain ".." but are not testers) + match name.splitOn ".." with + | [_, suffix] => + if suffix.startsWith "is" then name -- tester, leave unchanged + else if proof then + name + -- Safe: strip trailing "!" + -- if name.endsWith "!" then (name.dropEnd 1).toString else name + else + -- Unsafe: add trailing "!" if not already present + if name.endsWith "!" then name else name ++ "!" + | _ => name -- not a destructor name, leave unchanged + private def invalidCoreType (source : Option FileRange) (reason : String) : TranslateM LMonoTy := do modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ [diagnosticFromSource source reason DiagnosticType.StrataBug] } @@ -103,7 +127,8 @@ def translateType (ty : HighTypeMd) : TranslateM LMonoTy := do | some (.datatypeConstructor typeName _) => return .tcons typeName.text [] | _ => do -- resolution should have already emitted a diagnostic modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ - [diagnosticFromSource ty.source s!"UserDefined type could not be resolved to a composite or datatype" DiagnosticType.StrataBug] } + [diagnosticFromSource ty.source s!"UserDefined type could not be resolved to a composite or datatype" DiagnosticType.StrataBug] + } return .tcons "Composite" [] | .TCore s => return .tcons s [] | .TReal => return LMonoTy.real @@ -122,9 +147,6 @@ def lookupType (name : Identifier) : TranslateM LMonoTy := do def runTranslateM (s : TranslateState) (m : TranslateM α) : (Option α × TranslateState) := m s -def returnNone: TranslateM α := - StateT.pure none - /-- Allocate a fresh unique ID. -/ private def freshId : TranslateM Nat := do let s ← get @@ -136,8 +158,7 @@ private def freshId : TranslateM Nat := do def throwExprDiagnostic (d : DiagnosticModel): TranslateM Core.Expression.Expr := do emitDiagnostic d modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ [d] } - let id ← freshId - return LExpr.fvar () (⟨s!"DUMMY_VAR_{id}", ()⟩) none + return default /-- Translate Laurel StmtExpr to Core Expression using the `TranslateM` monad. @@ -158,6 +179,7 @@ def translateExpr (expr : StmtExprMd) let s ← get let model := s.model let md := astNodeToCoreMd expr + let proof := (← get).proof let disallowed (source : Option FileRange) (msg : String) : TranslateM Core.Expression.Expr := do if isPureContext then throwExprDiagnostic $ diagnosticFromSource source msg @@ -214,10 +236,10 @@ def translateExpr (expr : StmtExprMd) | .Add => return binOp (if isReal then realAddOp else intAddOp) | .Sub => return binOp (if isReal then realSubOp else intSubOp) | .Mul => return binOp (if isReal then realMulOp else intMulOp) - | .Div => return binOp (if isReal then realDivOp else intSafeDivOp) - | .Mod => return binOp intSafeModOp - | .DivT => return binOp intSafeDivTOp - | .ModT => return binOp intSafeModTOp + | .Div => return binOp (if isReal then realDivOp else if proof then intSafeDivOp else intDivOp) + | .Mod => return binOp (if (← get).proof then intSafeModOp else intModOp) + | .DivT => return binOp (if (← get).proof then intSafeDivTOp else intDivTOp) + | .ModT => return binOp (if (← get).proof then intSafeModTOp else intModTOp) | .Lt => return binOp (if isReal then realLtOp else intLtOp) | .Leq => return binOp (if isReal then realLeOp else intLeOp) | .Gt => return binOp (if isReal then realGtOp else intGtOp) @@ -242,9 +264,10 @@ def translateExpr (expr : StmtExprMd) | .StaticCall callee args => -- In a pure context, only Core functions (not procedures) are allowed if isPureContext && !model.isFunction callee then - disallowed expr.source "calls to procedures are not supported in functions or contracts" + disallowed expr.source s!"calls to procedures are not supported in functions or contracts" else - let fnOp : Core.Expression.Expr := .op () ⟨callee.text, ()⟩ none + let calleeName := adjustSelectorName callee.text (← get).proof + let fnOp : Core.Expression.Expr := .op () ⟨calleeName, ()⟩ none args.attach.foldlM (fun acc ⟨arg, _⟩ => do let re ← translateExpr arg boundVars isPureContext return .app () acc re) fnOp @@ -300,7 +323,7 @@ def translateExpr (expr : StmtExprMd) -- If we see one here, it's an error in the pipeline throwExprDiagnostic $ diagnosticFromSource expr.source s!"FieldSelect should have been eliminated by heap parameterization: {Std.ToFormat.format target}#{fieldId.text}" DiagnosticType.StrataBug | .Block _ _ => - throwExprDiagnostic $ diagnosticFromSource expr.source "block expression should have been lowered in a separate pass" DiagnosticType.StrataBug + throwExprDiagnostic $ diagnosticFromSource expr.source s!"block expression should have been lowered in a separate pass, expr: {repr expr}" DiagnosticType.StrataBug | .Return _ => disallowed expr.source "return expression should be lowered in a separate pass" | .AsType target _ => throwExprDiagnostic $ diagnosticFromSource expr.source "AsType expression translation" DiagnosticType.NotYetImplemented @@ -346,9 +369,10 @@ private def exprAsUnusedInit (expr : StmtExprMd) (md : Imperative.MetaData Core. : TranslateM (List Core.Statement) := do let coreExpr ← translateExpr expr let id ← freshId + let model := (← get).model let ident : Core.CoreIdent := ⟨s!"$unused_{id}", ()⟩ - let tyVarName := s!"$__ty_unused_{id}" - let coreType := LTy.forAll [tyVarName] (.ftvar tyVarName) + let ty ← translateType (computeExprType model expr) + let coreType := LTy.forAll [] ty return [Core.Statement.init ident coreType (.det coreExpr) md] def throwStmtDiagnostic (d : DiagnosticModel): TranslateM (List Core.Statement) := do @@ -529,7 +553,7 @@ def translateStmt (stmt : StmtExprMd) Translate a list of checks (preconditions or postconditions) to Core checks. Each check gets a label like `"requires"` or `"requires_0"`, `"requires_1"`, etc. -/ -private def translateChecks (checks : List Condition) (labelBase : String) +private def translateChecks (checks : List Condition) (labelBase : String) (overrideFree: Bool) : TranslateM (ListMap Core.CoreLabel Core.Procedure.Check) := checks.mapIdxM (fun i check => do let label := if checks.length == 1 then labelBase else s!"{labelBase}_{i}" @@ -538,7 +562,8 @@ private def translateChecks (checks : List Condition) (labelBase : String) let md := match check.summary with | some msg => baseMd.pushElem Imperative.MetaData.propertySummary (.msg msg) | none => baseMd - let c : Core.Procedure.Check := { expr := checkExpr, md } + let attr := if check.free || overrideFree then Core.Procedure.CheckAttr.Free else .Default + let c : Core.Procedure.Check := { expr := checkExpr, attr, md } return (label, c)) /-- @@ -565,26 +590,33 @@ def translateProcedure (proc : Procedure) : TranslateM Core.Procedure := do outputs := outputs } -- Translate preconditions - let preconditions ← translateChecks proc.preconditions "requires" + let preconditions ← translateChecks proc.preconditions "requires" false - -- Translate postconditions for Opaque and Abstract bodies - let postconditions : ListMap Core.CoreLabel Core.Procedure.Check ← - match proc.body with - | .Opaque postconds _ _ | .Abstract postconds => - translateChecks postconds "postcondition" - | _ => pure [] - let bodyStmts : List Core.Statement ← + let bodyStmts : Option (List Core.Statement) ← match proc.body with - | .Transparent bodyExpr => translateStmt bodyExpr - | .Opaque _postconds (some impl) _ => translateStmt impl + | .Transparent bodyExpr => + let r ← translateStmt bodyExpr + pure $ some r + | .Opaque _postconds (some impl) _ => + let r ← translateStmt impl + pure $ some r | _ => + pure none -- Bodiless procedure: assume postconditions so that verification of the -- procedure itself passes trivially, and inlining only introduces the -- postconditions as assumptions (not the unsound `assume false`). - pure (postconditions.map fun (label, check) => - Core.Statement.assume label check.expr mdWithUnknownLoc) + -- pure (postconditions.map fun (label, check) => + -- Core.Statement.assume label check.expr mdWithUnknownLoc) -- Wrap body in a labeled block so early returns (exit) work correctly. - let body : List Core.Statement := [.block "$body" bodyStmts mdWithUnknownLoc] + + -- Translate postconditions for Opaque and Abstract bodies + let postconditions : ListMap Core.CoreLabel Core.Procedure.Check ← + match proc.body with + | .Opaque postconds _ _ | .Abstract postconds => + translateChecks postconds s!"postcondition{(bodyStmts.getD []).length}" bodyStmts.isNone + | _ => pure [] + + let body : List Core.Statement := [.block "$body" (bodyStmts.getD []) mdWithUnknownLoc] let spec : Core.Procedure.Spec := { preconditions, postconditions } return { header, spec, body } @@ -719,35 +751,33 @@ def translateDatatypeDefinition (dt : DatatypeDefinition) abbrev TranslateResult := (Option Core.Program) × (List DiagnosticModel) /-- -Translate an `OrderedLaurel` program to a `Core.Program`. +Translate a `CoreWithLaurelTypes` program to a `Core.Program`. The `program` parameter is the lowered Laurel program, used for type definitions. -/ -def translateLaurelToCore (options: LaurelTranslateOptions) (program : Program) (ordered : OrderedLaurel): TranslateM Core.Program := do +def translateLaurelToCore (options: LaurelTranslateOptions) (program : Program) (ordered : CoreWithLaurelTypes): TranslateM Core.Program := do let coreDecls ← ordered.decls.flatMapM fun - | .procs procs isRecursive => do - -- For each SCC, determine if it is purely functional or contains procedures. - let isFuncSCC := procs.all (·.isFunctional) - if isFuncSCC then - let funcs ← procs.mapM (translateProcedureToFunction options isRecursive) - if isRecursive then - let coreFuncs := funcs.filterMap (fun d => match d with - | .func f _ => some f - | _ => none) - return [Core.Decl.recFuncBlock coreFuncs mdWithUnknownLoc] - else - return funcs + | .funcs funcs isRecursive => do + modify fun s => { s with proof := false } + let nonExternal := funcs.filter (fun p => !p.body.isExternal) + let coreFuncs ← nonExternal.mapM (translateProcedureToFunction options isRecursive) + if isRecursive then + let coreFuncValues := coreFuncs.filterMap (fun d => match d with + | .func f _ => some f + | _ => none) + return [Core.Decl.recFuncBlock coreFuncValues mdWithUnknownLoc] else - let procDecls ← procs.flatMapM fun proc => do - let procDecl ← translateProcedure proc - -- Turn free postconditions into axioms placed right behind the related procedure - let axiomDecls : List Core.Decl ← match proc.invokeOn with - | none => pure [] - | some trigger => do - let axDecl? ← translateInvokeOnAxiom proc trigger - pure axDecl?.toList - return [Core.Decl.proc procDecl (identifierToCoreMd proc.name)] ++ axiomDecls - return procDecls + return coreFuncs + | .procedure proc => do + modify fun s => { s with proof := true } + let procDecl ← translateProcedure proc + -- Translate axioms from invokeOn + let invokeOnDecls ← match proc.invokeOn with + | some trigger => do + let axDecl? ← translateInvokeOnAxiom proc trigger + pure axDecl?.toList + | none => pure [] + return [Core.Decl.proc procDecl (identifierToCoreMd proc.name)] ++ invokeOnDecls | .datatypes dts => do let ldatatypes ← dts.mapM translateDatatypeDefinition return [Core.Decl.type (.data ldatatypes) mdWithUnknownLoc] @@ -770,6 +800,7 @@ def translateLaurelToCore (options: LaurelTranslateOptions) (program : Program) emitDiagnostic $ diagnosticFromSource proc.name.source s!"Instance procedure '{proc.name.text}' on composite type '{ct.name.text}' is not yet supported" DiagnosticType.NotYetImplemented + pure { decls := coreDecls } end -- public section diff --git a/Strata/Languages/Laurel/PackMultipleOutputs.lean b/Strata/Languages/Laurel/PackMultipleOutputs.lean new file mode 100644 index 0000000000..270f0919d7 --- /dev/null +++ b/Strata/Languages/Laurel/PackMultipleOutputs.lean @@ -0,0 +1,166 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ +module + +public import Strata.Languages.Laurel.TransparencyPass + +/-! +# Eliminate Multiple Outputs + +Transforms bodiless functions with multiple outputs into functions that return +a single synthesized result datatype. Call sites are rewritten to destructure +the result using the generated accessors. + +This pass operates on `UnorderedCoreWithLaurelTypes → UnorderedCoreWithLaurelTypes`. +-/ + +namespace Strata.Laurel + +public section + + +private def mkMd (e : StmtExpr) : StmtExprMd := { val := e, source := none } +private def mkVarMd (v : Variable) : VariableMd := { val := v, source := none } +private def mkTy (t : HighType) : HighTypeMd := { val := t, source := none } + +/-- Info about a function whose multiple outputs have been collapsed into a result datatype. -/ +private structure MultiOutInfo where + funcName : String + resultTypeName : String + constructorName : String + /-- Original output parameters (name, type). -/ + outputs : List Parameter + /-- Number of input parameters (used to detect implicit heap args at call sites). -/ + inputCount : Nat + +/-- Identify bodiless functions with multiple outputs and build info records. -/ +private def collectMultiOutFunctions (funcs : List Procedure) : List MultiOutInfo := + funcs.filterMap fun f => + if f.outputs.length > 1 && !f.body.isTransparent then + some { + funcName := f.name.text + resultTypeName := s!"{f.name.text}$result" + constructorName := s!"{f.name.text}$result$mk" + outputs := f.outputs + inputCount := f.inputs.length + } + else none + +/-- Generate a result datatype for a multi-output function. -/ +private def mkResultDatatype (info : MultiOutInfo) : DatatypeDefinition := + let args := info.outputs.zipIdx.map fun (p, i) => + { name := mkId s!"out{i}", type := p.type : Parameter } + { name := mkId info.resultTypeName + typeArgs := [] + constructors := [{ name := mkId info.constructorName, args := args }] } + +/-- Transform a multi-output function to return the result datatype. -/ +private def transformFunction (info : MultiOutInfo) (proc : Procedure) : Procedure := + let resultOutput : Parameter := + { name := mkId "$result", type := mkTy (.UserDefined (mkId info.resultTypeName)) } + { proc with outputs := [resultOutput] } + +/-- Destructor name for field `outN` of the result datatype. -/ +private def destructorName (info : MultiOutInfo) (idx : Nat) : String := + s!"{info.resultTypeName}..out{idx}" + +/-- Check whether a statement is an Assume node. -/ +private def isAssume (stmt : StmtExprMd) : Bool := + match stmt.val with + | .Assume _ => true + | _ => false + +/-- Rewrite a single multi-output Assign into a temp declaration + destructuring + assignments. Any `Assume` statements from `following` that appear immediately + after the call are collected and placed after the destructuring assignments, + so they observe the post-call variable values. + Returns the rewritten statements and the number of consumed following statements. -/ +private def rewriteAssign (infoMap : Std.HashMap String MultiOutInfo) + (targets : List VariableMd) (callee : Identifier) (args : List StmtExprMd) + (callSrc : Option FileRange) + (following : List StmtExprMd) (counter : Nat) : Option (List StmtExprMd × Nat) := + match infoMap.get? callee.text with + | some info => + if targets.length ≤ info.outputs.length then + let tempName := s!"${callee.text}$temp{counter}" + let fullArgs := args + let tempDecl := mkMd (.Assign [mkVarMd (.Declare ⟨mkId tempName, mkTy (.UserDefined (mkId info.resultTypeName))⟩)] + ⟨.StaticCall callee fullArgs, callSrc⟩) + let assigns := targets.zipIdx.map fun (tgt, i) => + mkMd (.Assign [tgt] + (mkMd (.StaticCall (mkId (destructorName info i)) + [mkMd (.Var (.Local (mkId tempName)))]))) + -- Collect any Assume statements that immediately follow the call. + -- These are placed after the destructuring assignments so they + -- observe the post-call values of output variables. + let assumes := following.takeWhile isAssume + let consumed := assumes.length + some (tempDecl :: assigns ++ assumes, consumed) + else none + | none => none + +/-- Rewrite a statement list, replacing multi-output call patterns. + When a multi-output Assign is followed by Assume statements (inserted by + the contract pass), the Assumes are placed after the destructuring + assignments so they reference post-call variable values. -/ +private def rewriteStmts (infoMap : Std.HashMap String MultiOutInfo) + (stmts : List StmtExprMd) : List StmtExprMd := + let rec go (remaining : List StmtExprMd) (acc : List StmtExprMd) (counter : Nat) : List StmtExprMd := + match remaining with + | [] => acc.reverse + | stmt :: rest => + match stmt.val with + | .Assign targets ⟨.StaticCall callee args, callSrc⟩ => + match rewriteAssign infoMap targets callee args callSrc rest counter with + | some (expanded, consumed) => go (rest.drop consumed) (expanded.reverse ++ acc) (counter + 1) + | none => go rest (stmt :: acc) counter + | _ => go rest (stmt :: acc) counter + termination_by remaining.length + go stmts [] 0 + +/-- Rewrite blocks in a StmtExprMd tree to handle multi-output calls. -/ +private def rewriteExpr (infoMap : Std.HashMap String MultiOutInfo) + (expr : StmtExprMd) : StmtExprMd := + mapStmtExpr (fun e => + match e.val with + | .Block stmts label => ⟨.Block (rewriteStmts infoMap stmts) label, e.source⟩ + | _ => e) expr + +/-- Rewrite all procedure bodies. -/ +private def rewriteProcedure (infoMap : Std.HashMap String MultiOutInfo) + (proc : Procedure) : Procedure := + match proc.body with + | .Transparent b => + -- Wrap in a block so rewriteStmts can process top-level statements + let wrapped := mkMd (.Block [b] none) + let rewritten := rewriteExpr infoMap wrapped + { proc with body := .Transparent rewritten } + | .Opaque posts (some impl) mods => + let wrapped := mkMd (.Block [impl] none) + let rewritten := rewriteExpr infoMap wrapped + { proc with body := .Opaque posts (some rewritten) mods } + | _ => proc + +/-- Eliminate multiple outputs from a UnorderedCoreWithLaurelTypes. -/ +def packMultipleOutputsInFunctions (program : UnorderedCoreWithLaurelTypes) + : UnorderedCoreWithLaurelTypes := + let infos := collectMultiOutFunctions program.functions + if infos.isEmpty then program else + let infoMap : Std.HashMap String MultiOutInfo := + infos.foldl (fun m info => m.insert info.funcName info) {} + let newDatatypes := infos.map mkResultDatatype + let functions := program.functions.map fun f => + match infoMap.get? f.name.text with + | some info => rewriteProcedure infoMap (transformFunction info f) + | none => rewriteProcedure infoMap f + let coreProcedures := program.coreProcedures.map fun p => rewriteProcedure infoMap p + { program with + functions := functions + coreProcedures := coreProcedures + datatypes := program.datatypes ++ newDatatypes } + +end -- public section +end Strata.Laurel diff --git a/Strata/Languages/Laurel/Resolution.lean b/Strata/Languages/Laurel/Resolution.lean index 75e6ac1292..eb09d57bb5 100644 --- a/Strata/Languages/Laurel/Resolution.lean +++ b/Strata/Languages/Laurel/Resolution.lean @@ -240,7 +240,6 @@ private def freshId : ResolveM Nat := do set { s with nextId := id + 1 } return id - /-- Like `defineName`, but reports a diagnostic if the name already exists in the current scope. Inserts an `.unresolved` node so subsequent references still resolve without cascading errors. -/ def defineNameCheckDup (iden : Identifier) (node : ResolvedNode) (overrideResolutionName: Option String := none) : ResolveM Identifier := do @@ -426,9 +425,7 @@ def resolveStmtExpr (exprMd : StmtExprMd) : ResolveM StmtExprMd := do let name' ← defineNameCheckDup param.name (.var param.name ty') pure (⟨.Declare ⟨name', ty'⟩, vs⟩ : VariableMd) let value' ← resolveStmtExpr value - -- Check that LHS target count matches the number of outputs from the RHS. - -- This fires for procedure calls (which can have multiple outputs). - -- Functions always have exactly 1 output in the model, so single-target function calls pass trivially. + -- Check that LHS target count matches the number of outputs from the RHS let expectedOutputCount ← match value'.val with | .StaticCall callee _ => do let s ← get @@ -524,12 +521,12 @@ def resolveStmtExpr (exprMd : StmtExprMd) : ResolveM StmtExprMd := do | .Fresh val => let val' ← resolveStmtExpr val pure (.Fresh val') - | .Assert ⟨condExpr, summary⟩ => + | .Assert ⟨condExpr, summary, free⟩ => let saved := (← get).inValueContext modify fun s => { s with inValueContext := true } let cond' ← resolveStmtExpr condExpr modify fun s => { s with inValueContext := saved } - pure (.Assert { condition := cond', summary }) + pure (.Assert { condition := cond', summary, free }) | .Assume cond => let saved := (← get).inValueContext modify fun s => { s with inValueContext := true } @@ -585,10 +582,6 @@ def resolveProcedure (proc : Procedure) : ResolveM Procedure := do let pres' ← proc.preconditions.mapM (·.mapM resolveStmtExpr) let dec' ← proc.decreases.mapM resolveStmtExpr let body' ← resolveBody proc.body - if !proc.isFunctional && body'.isTransparent then - let diag := diagnosticFromSource proc.name.source - s!"transparent procedures are not yet supported. Add 'opaque' to make the procedure opaque" - modify fun s => { s with errors := s.errors.push diag } let invokeOn' ← proc.invokeOn.mapM resolveStmtExpr return { name := procName', inputs := inputs', outputs := outputs', isFunctional := proc.isFunctional, @@ -618,9 +611,9 @@ def resolveInstanceProcedure (typeName : Identifier) (proc : Procedure) : Resolv let pres' ← proc.preconditions.mapM (·.mapM resolveStmtExpr) let dec' ← proc.decreases.mapM resolveStmtExpr let body' ← resolveBody proc.body - if !proc.isFunctional && body'.isTransparent then + if !proc.isFunctional && body'.isTransparent && !proc.name.text.any (· == '$') then let diag := diagnosticFromSource proc.name.source - s!"transparent procedures are not yet supported. Add 'opaque' to make the procedure opaque" + s!"transparent statement bodies are not supported. Add 'opaque' to make the procedure opaque" modify fun s => { s with errors := s.errors.push diag } let invokeOn' ← proc.invokeOn.mapM resolveStmtExpr modify fun s => { s with instanceTypeName := savedInstType } @@ -778,7 +771,7 @@ private def collectStmtExpr (map : Std.HashMap Nat ResolvedNode) (expr : StmtExp | .Assigned name => collectStmtExpr map name | .Old val => collectStmtExpr map val | .Fresh val => collectStmtExpr map val - | .Assert ⟨cond, _⟩ => collectStmtExpr map cond + | .Assert ⟨cond, _, _⟩ => collectStmtExpr map cond | .Assume cond => collectStmtExpr map cond | .ProveBy val proof => let map := collectStmtExpr map val @@ -916,8 +909,18 @@ private def preRegisterTopLevel (program : Program) : ResolveM Unit := do /-- Run the full resolution pass on a Laurel program. -/ def resolve (program : Program) (existingModel: Option SemanticModel := none) : ResolutionResult := + -- Phase 1: pre-register all top-level names, then assign IDs and resolve references let phase1 : ResolveM Program := do + + for td in program.types do + if let .Composite ct := td then + for proc in ct.instanceProcedures do + let diag := diagnosticFromSource proc.name.source + s!"Instance procedure '{proc.name.text}' on composite type '{ct.name.text}' is not yet supported" + DiagnosticType.NotYetImplemented + modify fun s => { s with errors := s.errors.push diag } + preRegisterTopLevel program let types' ← program.types.mapM resolveTypeDefinition let constants' ← program.constants.mapM resolveConstant diff --git a/Strata/Languages/Laurel/TransparencyPass.lean b/Strata/Languages/Laurel/TransparencyPass.lean new file mode 100644 index 0000000000..43a7eed2da --- /dev/null +++ b/Strata/Languages/Laurel/TransparencyPass.lean @@ -0,0 +1,159 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ +module + +public import Strata.Languages.Laurel.MapStmtExpr +public import Strata.Languages.Laurel.Laurel +import Strata.Languages.Laurel.Grammar.AbstractToConcreteTreeTranslator + +/-! +## Transparency Pass + +For each Core procedure, generate a function with the same signature and name +suffixed with `$asFunction`. If a Core procedure is marked as transparent, +attempt to add a body to its function version. In the functional body, +assertions are erased and all calls are to functional versions. If the function +has a body, add a free postcondition to the related procedure that equates the +two. + +This IR sits between Laurel and CoreWithLaurelTypes in the pipeline: + Laurel → UnorderedCoreWithLaurelTypes → CoreWithLaurelTypes → Core +-/ + +namespace Strata.Laurel + +public section + +/-- +An intermediate representation produced by the transparency pass. +Functions are pure computational procedures (suffixed `$asFunction`); +coreProcedures are the original procedures with any free postconditions +embedded in their `Body.Opaque` postcondition lists. +-/ +public structure UnorderedCoreWithLaurelTypes where + functions : List Procedure + coreProcedures : List Procedure + datatypes : List DatatypeDefinition + constants : List Constant + +/-- Deep traversal that strips all Assert and Assume nodes from a StmtExpr tree. + Assert/Assume nodes are replaced with `LiteralBool true`, and Block nodes + are collapsed by filtering out trivial `LiteralBool true` leftovers. -/ +def stripAssertAssume (expr : StmtExprMd) : StmtExprMd := + mapStmtExpr (fun e => + match e.val with + | .Assert _ | .Assume _ => ⟨.LiteralBool true, e.source⟩ + | .Block stmts label => + let stmts' := stmts.filter fun s => + match s.val with | .LiteralBool true => false | _ => true + match stmts' with + | [] => ⟨.LiteralBool true, e.source⟩ + | [s] => if label.isNone then s else ⟨.Block [s] label, e.source⟩ + | _ => ⟨.Block stmts' label, e.source⟩ + | _ => e) expr + +/-- Rewrite StaticCall callees to their `$asFunction` versions, + but only for procedures whose names appear in `nonExternalNames`. -/ +private def rewriteCallsToFunctional (asFunctionNames : List String) (expr : StmtExprMd) : StmtExprMd := + mapStmtExpr (fun e => + match e.val with + | .StaticCall callee args => + if asFunctionNames.contains callee.text then + let funcCallee := { callee with text := callee.text ++ "$asFunction", uniqueId := none } + ⟨.StaticCall funcCallee args, e.source⟩ + else e + | _ => e) expr + +/-- Build a free postcondition equating the procedure's output to its functional version. + For a procedure `foo(a, b) returns (r)`, produces: + `r == foo$asFunction(a, b)` -/ +private def mkFreePostcondition (proc : Procedure) : StmtExprMd := + let source := proc.name.source + let funcName := { proc.name with text := proc.name.text ++ "$asFunction", uniqueId := none } + let inputArgs := proc.inputs.map fun p => (⟨ .Var (.Local p.name), source ⟩ : StmtExprMd) + let funcCall: StmtExprMd := ⟨ .StaticCall funcName inputArgs, source ⟩ + match proc.outputs with + | [out] => ⟨ .PrimitiveOp .Eq [⟨ .Var (.Local out.name), source⟩, funcCall], source ⟩ + | _ => ⟨ .LiteralBool true, source ⟩ + +/-- Create the function copy of a procedure (suffixed `$asFunction`). + If the procedure is transparent, include a functional body. + Otherwise the function is opaque. -/ +private def mkFunctionCopy (asFunctionNames : List String) (proc : Procedure) : Procedure := + let funcName := { proc.name with text := proc.name.text ++ "$asFunction", uniqueId := none } + let body := match proc.body with + | .Transparent b => .Transparent (rewriteCallsToFunctional asFunctionNames (stripAssertAssume b)) + | .Opaque _ _ _ => .Opaque [] none [] + | x => x + { proc with name := funcName, isFunctional := true, body := body, preconditions := [] } + +/-- Check whether a function copy has a body (i.e. the procedure was transparent). -/ +private def functionHasBody (proc : Procedure) : Bool := + match proc.body with + | .Transparent _ => true + | _ => false + +/-- Append a free postcondition to a procedure's body postconditions. + For Opaque and Abstract bodies, the free condition is appended to the + existing postcondition list. For Transparent bodies, the body is promoted + to Opaque so the free postcondition can be carried. -/ +private def addFreePostcondition (proc : Procedure) (freePost : StmtExprMd) : Procedure := + match freePost.val with + | .LiteralBool true => proc -- trivial, skip + | _ => + let freeCond : Condition := { condition := freePost, free := true } + match proc.body with + | .Opaque postconds impl modif => + { proc with body := .Opaque (postconds ++ [freeCond]) impl modif } + | .Abstract postconds => + { proc with body := .Abstract (postconds ++ [freeCond]) } + | .Transparent body => + { proc with body := .Opaque [freeCond] (some body) [] } + | _ => proc + +/-- +Transparency pass: translate a Laurel program to the UnorderedCoreWithLaurelTypes IR. + +For each procedure: +- Generate a function with the same signature, named `foo$asFunction` +- If transparent, the function gets a functional body (assertions erased, calls to functional versions) +- If the function has a body, add a free postcondition equating the procedure output to the function +-/ +def transparencyPass (program : Program) : UnorderedCoreWithLaurelTypes := + let (skipped, notSkipped) := program.staticProcedures.partition (fun p => p.body.isExternal || + -- Skip functions until we introduce a contract pass, + -- which enables lifting procedure calls from contracts + p.isFunctional) + let asFunctionNames := notSkipped.map (fun p => p.name.text) + let asFunctions := notSkipped.map (mkFunctionCopy asFunctionNames) + + -- External procedures get a plain function copy (they have no $asFunction version) + let (skippedFunctions, skippedProcedures) := skipped.partition (fun p => p.isFunctional) + let functions := skippedFunctions ++ asFunctions + let coreProcedures := notSkipped.map fun p => + let freePostcondition := mkFreePostcondition p + let p := addFreePostcondition p freePostcondition + { p with isFunctional := false } + let datatypes := program.types.filterMap fun td => match td with + | .Datatype dt => some dt + | _ => none + let procs: List Procedure := skippedProcedures ++ coreProcedures + { functions, coreProcedures := procs, datatypes, constants := program.constants } + +open Std (Format ToFormat) + +def formatUnorderedCoreWithLaurelTypes (p : UnorderedCoreWithLaurelTypes) : Format := + let datatypeFmts := p.datatypes.map ToFormat.format + let constantFmts := p.constants.map ToFormat.format + let functionFmts := p.functions.map ToFormat.format + let procFmts := p.coreProcedures.map ToFormat.format + Format.joinSep (datatypeFmts ++ constantFmts ++ functionFmts ++ procFmts) "\n\n" + +instance : ToFormat UnorderedCoreWithLaurelTypes where + format := formatUnorderedCoreWithLaurelTypes + +end -- public section +end Strata.Laurel diff --git a/StrataTest/Backends/CBMC/contracts/test_contract.lr.st b/StrataTest/Backends/CBMC/contracts/test_contract.lr.st index 5660809918..20505c2cbc 100644 --- a/StrataTest/Backends/CBMC/contracts/test_contract.lr.st +++ b/StrataTest/Backends/CBMC/contracts/test_contract.lr.st @@ -6,7 +6,9 @@ procedure add(x: int, y: int) returns (r: int) r := x + y; }; -procedure main() { +procedure main() + opaque +{ var a: int := 42; assert a > 0; }; diff --git a/StrataTest/Backends/CBMC/contracts/test_contracts_e2e.sh b/StrataTest/Backends/CBMC/contracts/test_contracts_e2e.sh index eef32397e1..0ab00ccab1 100755 --- a/StrataTest/Backends/CBMC/contracts/test_contracts_e2e.sh +++ b/StrataTest/Backends/CBMC/contracts/test_contracts_e2e.sh @@ -66,12 +66,15 @@ echo "--- Test 1: Procedure with requires/ensures (full DFCC + CBMC) ---" build_goto "contract" 'procedure add(x: int, y: int, out r: int) requires x >= 0 + opaque ensures r >= x { r := x + y; } -procedure main() { +procedure main() + opaque +{ var a: int := 42; assert a > 0; }' @@ -102,7 +105,9 @@ echo "" # ---- Test 2: Simple assert (full CBMC verification) ---- echo "--- Test 2: Simple assert (full DFCC + CBMC) ---" -build_goto "assert" 'procedure main() { +build_goto "assert" 'procedure main() + opaque +{ var x: int := 10; var y: int := x + 5; assert y > x; @@ -122,12 +127,15 @@ echo "--- Test 3: Procedure with ensures (full DFCC + CBMC) ---" build_goto "ensures" 'procedure inc(x: int, out r: int) requires x >= 0 + opaque ensures r > x { r := x + 1; } -procedure main() { +procedure main() + opaque +{ var v: int := 10; assert v > 0; }' @@ -146,6 +154,7 @@ echo "--- Test 4: Loop with invariant (full DFCC + CBMC) ---" build_goto "loop" 'procedure sum_to_n(n: int, out s: int) requires n >= 0 + opaque ensures s >= 0 { var i: int := 0; @@ -159,7 +168,9 @@ build_goto "loop" 'procedure sum_to_n(n: int, out s: int) } } -procedure main() { +procedure main() + opaque +{ var x: int := 5; assert x > 0; }' @@ -179,12 +190,15 @@ echo "--- Test 5: Procedure call (full DFCC + CBMC) ---" build_goto "call" 'procedure double(x: int, out r: int) requires x >= 0 + opaque ensures r == x + x { r := x + x; } -procedure main() { +procedure main() + opaque +{ var a: int := 3; assert a > 0; }' @@ -217,6 +231,7 @@ echo "--- Test 6: Multiple procedures with contracts ---" build_goto "multi" 'procedure inc(x: int, out r: int) requires x >= 0 + opaque ensures r == x + 1 { r := x + 1; @@ -224,12 +239,15 @@ build_goto "multi" 'procedure inc(x: int, out r: int) procedure dec(x: int, out r: int) requires x > 0 + opaque ensures r == x - 1 { r := x - 1; } -procedure main() { +procedure main() + opaque +{ var x: int := 5; assert x > 0; }' @@ -263,12 +281,15 @@ echo "--- Test 7: Call inside if-then-else (GOTO output) ---" build_goto "nested_call" 'procedure inc(x: int, out r: int) requires x >= 0 + opaque ensures r == x + 1 { r := x + 1; } -procedure main() { +procedure main() + opaque +{ var a: int := 3; var b: int; if (a > 0) { @@ -299,12 +320,15 @@ echo "--- Test 8: Call inside loop (GOTO output) ---" build_goto "loop_call" 'procedure inc(x: int, out r: int) requires x >= 0 + opaque ensures r == x + 1 { r := x + 1; } -procedure main() { +procedure main() + opaque +{ var i: int := 0; var s: int := 0; while (i < 3) diff --git a/StrataTest/Languages/Boole/deterministic.lean b/StrataTest/Languages/Boole/deterministic.lean index c45c756007..f5ac8b1603 100644 --- a/StrataTest/Languages/Boole/deterministic.lean +++ b/StrataTest/Languages/Boole/deterministic.lean @@ -44,14 +44,12 @@ procedure Check(x1:int, x2:int) returns () #end -/-- info: -Obligation: Foo_ensures_0_251 -Property: assert -Result: ✅ pass - +/-- +info: Obligation: assert_1_557 Property: assert -Result: ✅ pass-/ +Result: ✅ pass +-/ #guard_msgs in #eval Strata.Boole.verify "cvc5" deterministic (options := .quiet) diff --git a/StrataTest/Languages/Core/Examples/FreeRequireEnsure.lean b/StrataTest/Languages/Core/Examples/FreeRequireEnsure.lean index 43b4c86f13..b9eeb0fa0d 100644 --- a/StrataTest/Languages/Core/Examples/FreeRequireEnsure.lean +++ b/StrataTest/Languages/Core/Examples/FreeRequireEnsure.lean @@ -42,13 +42,6 @@ g_eq_15: g@1 == 15 Obligation: g@1 > 10 -Label: g_lt_10 -Property: assert -Assumptions: -g_eq_15: g@1 == 15 -Obligation: -true - Label: g_eq_15_internal Property: assert Assumptions: @@ -62,15 +55,11 @@ Obligation: g_gt_10_internal Property: assert Result: ✅ pass -Obligation: g_lt_10 -Property: assert -Result: ✅ pass - Obligation: g_eq_15_internal Property: assert Result: ❓ unknown Model: -(g@5, 0) (g@1, 0) +(g@5, 0) -/ #guard_msgs in #eval verify freeReqEnsPgm diff --git a/StrataTest/Languages/Core/Tests/PolymorphicProcedureTest.lean b/StrataTest/Languages/Core/Tests/PolymorphicProcedureTest.lean index 4ac4de001b..fe110bef30 100644 --- a/StrataTest/Languages/Core/Tests/PolymorphicProcedureTest.lean +++ b/StrataTest/Languages/Core/Tests/PolymorphicProcedureTest.lean @@ -92,11 +92,6 @@ info: [Strata.Core] Type checking succeeded. VCs: -Label: MkCons_ensures_0 -Property: assert -Obligation: -true - Label: assert_0 Property: assert Assumptions: @@ -113,10 +108,6 @@ true --- info: -Obligation: MkCons_ensures_0 -Property: assert -Result: ✅ pass - Obligation: assert_0 Property: assert Result: ✅ pass diff --git a/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean b/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean index 7b86139661..f6352c4487 100644 --- a/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean +++ b/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean @@ -65,7 +65,9 @@ info: procedure foo() }; -/ #guard_msgs in -#eval do IO.println (← roundtrip r"procedure foo() opaque { assert true; assert false };") +#eval do IO.println (← roundtrip r"procedure foo() + opaque +{ assert true; assert false };") /-- info: procedure add(x: int, y: int): int @@ -75,7 +77,9 @@ info: procedure add(x: int, y: int): int }; -/ #guard_msgs in -#eval do IO.println (← roundtrip r"procedure add(x: int, y: int): int opaque { x + y };") +#eval do IO.println (← roundtrip r"procedure add(x: int, y: int): int + opaque +{ x + y };") /-- info: function aFunction(x: int): int @@ -84,7 +88,8 @@ info: function aFunction(x: int): int }; -/ #guard_msgs in -#eval do IO.println (← roundtrip r"function aFunction(x: int): int { x };") +#eval do IO.println (← roundtrip r"function aFunction(x: int): int +{ x };") /-- info: composite Point { var x: int var y: int } @@ -105,7 +110,9 @@ info: procedure test(x: int): int }; -/ #guard_msgs in -#eval do IO.println (← roundtrip r"procedure test(x: int): int opaque { if x > 0 then x else 0 - x };") +#eval do IO.println (← roundtrip r"procedure test(x: int): int + opaque +{ if x > 0 then x else 0 - x };") /-- info: procedure divide(x: int, y: int): int @@ -135,7 +142,9 @@ info: procedure test() -/ #guard_msgs in #eval do IO.println (← roundtrip r" -procedure test() opaque { +procedure test() + opaque +{ assert forall(x: int) => x == x; assert exists(y: int) => y > 0 }; @@ -158,7 +167,9 @@ composite Point { var x: int var y: int } -procedure test(): int opaque { +procedure test(): int + opaque +{ var p: Point := new Point; p#x := 5; p#x @@ -192,7 +203,9 @@ procedure test(a: Animal): bool #eval do IO.println (← roundtrip r" composite Animal {} composite Dog extends Animal {} -procedure test(a: Animal): bool opaque { a is Dog }; +procedure test(a: Animal): bool + opaque +{ a is Dog }; ") -- Additional coverage: while loops @@ -210,7 +223,9 @@ info: procedure test() -/ #guard_msgs in #eval do IO.println (← roundtrip r" -procedure test() opaque { +procedure test() + opaque +{ var x: int := 0; while(x < 10) invariant x >= 0 @@ -260,7 +275,8 @@ info: procedure test(): int }; -/ #guard_msgs in -#eval do IO.println (← roundtrip r"procedure test(): int opaque { };") +#eval do IO.println (← roundtrip r"procedure test(): int + opaque +{ };") end Strata.Laurel -end diff --git a/StrataTest/Languages/Laurel/ConstrainedTypeElimTest.lean b/StrataTest/Languages/Laurel/ConstrainedTypeElimTest.lean index 8a293059d5..2e5fb6c3f1 100644 --- a/StrataTest/Languages/Laurel/ConstrainedTypeElimTest.lean +++ b/StrataTest/Languages/Laurel/ConstrainedTypeElimTest.lean @@ -23,7 +23,9 @@ namespace Strata.Laurel def testProgram : String := r" constrained nat = x: int where x >= 0 witness 0 -procedure test(n: nat) returns (r: nat) { +procedure test(n: nat) returns (r: nat) + opaque +{ assert r >= 0; var y: nat := n; return y @@ -74,7 +76,9 @@ procedure $witness_nat() -- Scope management: constrained variable in if-branch must not leak into sibling block def scopeProgram : String := r" constrained pos = v: int where v > 0 witness 1 -procedure test(b: bool) { +procedure test(b: bool) + opaque +{ if b then { var x: pos := 1 }; @@ -91,6 +95,7 @@ info: function pos$constraint(v: int): bool v > 0 }; procedure test(b: bool) + opaque { if b then { var x: int := 1; @@ -118,7 +123,9 @@ procedure $witness_pos() -- The variable has no known value, only the type constraint is assumed. def uninitProgram : String := r" constrained posint = x: int where x > 0 witness 1 -procedure f() opaque { +procedure f() + opaque +{ var x: posint; assert x == 1 }; diff --git a/StrataTest/Languages/Laurel/DivisionByZeroCheckTest.lean b/StrataTest/Languages/Laurel/DivisionByZeroCheckTest.lean index 40e8a9112d..d5d5671ade 100644 --- a/StrataTest/Languages/Laurel/DivisionByZeroCheckTest.lean +++ b/StrataTest/Languages/Laurel/DivisionByZeroCheckTest.lean @@ -60,6 +60,6 @@ procedure callPureDivUnsafe(x: int) " #guard_msgs(drop info, error) in -#eval testInputWithOffset "DivByZeroE2E" e2eProgram 22 processLaurelFile +#eval testInputWithOffset "DivByZeroE2E" e2eProgram 20 processLaurelFile end Laurel diff --git a/StrataTest/Languages/Laurel/DuplicateNameTests.lean b/StrataTest/Languages/Laurel/DuplicateNameTests.lean index beb1a06737..08c5085dea 100644 --- a/StrataTest/Languages/Laurel/DuplicateNameTests.lean +++ b/StrataTest/Languages/Laurel/DuplicateNameTests.lean @@ -72,25 +72,33 @@ composite Foo { /-! ## Duplicate parameter names in a procedure -/ def dupParams := r" -procedure foo(x: int, x: bool) opaque { }; +procedure foo(x: int, x: bool) // ^ error: Duplicate definition 'x' is already defined in this scope + opaque +{ }; " #guard_msgs (error, drop all) in -#eval testInputWithOffset "DupParams" dupParams 61 processResolution +#eval testInputWithOffset "DupParams" dupParams 77 processResolution /-! ## Duplicate instance procedure names in a composite type -/ def dupInstanceProcs := r" composite Foo { - procedure bar() opaque { }; - procedure bar() opaque { }; + procedure bar() +// ^^^ error: Instance procedure 'bar' on composite type 'Foo' is not yet supported + opaque + { }; + procedure bar() +// ^^^ error: Instance procedure 'bar' on composite type 'Foo' is not yet supported // ^^^ error: Duplicate definition 'bar' is already defined in this scope + opaque + { }; } " #guard_msgs (error, drop all) in -#eval testInputWithOffset "DupInstanceProcs" dupInstanceProcs 71 processResolution +#eval testInputWithOffset "DupInstanceProcs" dupInstanceProcs 89 processResolution /-! ## Duplicate local variable names in the same block -/ diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean similarity index 60% rename from StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean rename to StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean index c86b0e3c9c..2f44c90122 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean @@ -13,11 +13,22 @@ namespace Strata namespace Laurel def transparentBodyProgram := r" -procedure transparentBody() -// ^^^^^^^^^^^^^^^ error: transparent procedures are not yet supported. Add 'opaque' to make the procedure opaque +procedure transparentBody(): int { - assert true + assert true; + 3 }; + +procedure transparentProcedureCaller() opaque { + var x: int := transparentBody(); + assert x == 3 +}; + +// No support for transparent void procedures yet +// procedure transparentBody() +// { +// assert true +// }; " #guard_msgs(drop info, error) in diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean index e65d283f57..e170ce5dd1 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean @@ -80,7 +80,6 @@ procedure nestedImpureStatementsAndOpaque() // An imperative procedure call in expression position is lifted before the // surrounding expression is evaluated. procedure imperativeProc(x: int) returns (r: int) - // ensures clause required because Core's symbolic verification does not support transparent proceduces yet opaque ensures r == x + 1 { @@ -139,13 +138,10 @@ procedure addProcCaller(): int { var x: int := 0; var y: int := addProc({x := 1; x}, {x := x + 10; x}); - assert y == 11 + assert y == 11 // TOOD should be 12. Fix in other PR - // The next statement is not translated correctly. - // I think it's a bug in the handling of StaticCall - // Where a reference is substituted when it should not be // var z: int := addProc({x := 1; x}, {x := x + 10; x}) + (x := 3); - // assert z == 14 + // assert z == 15 }; " diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean index 2b3767391c..88bd9f1648 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean @@ -13,7 +13,7 @@ open Strata namespace Strata.Laurel def program: String := r" -procedure impure(): int +procedure hasMutatingAssignment(): int opaque { var x: int := 0; @@ -21,29 +21,29 @@ procedure impure(): int x }; -function impureFunction1(x: int): int +function functionWithMutatingAssignment(x: int): int { x := x + 1 //^^^^^^^^^^ error: destructive assignments are not supported in functions or contracts }; -function impureFunction2(x: int): int +function functionWithWhile(x: int): int { while(false) {} //^^^^^^^^^^^^^^^ error: loops are not supported in functions or contracts }; -function impureFunction3(x: int): int +function functionCallingHasMutationAssignment(x: int): int { - impure() -//^^^^^^^^ error: calls to procedures are not supported in functions or contracts + hasMutatingAssignment() +//^^^^^^^^^^^^^^^^^^^^^^^ error: calls to procedures are not supported in functions or contracts }; procedure impureContractIsNotLegal1(x: int) - requires x == impure() -// ^^^^^^^^ error: calls to procedures are not supported in functions or contracts + requires x == hasMutatingAssignment() +// ^^^^^^^^^^^^^^^^^^^^^^^ error: calls to procedures are not supported in functions or contracts opaque { - assert impure() == 1 + assert hasMutatingAssignment() == 1 }; procedure impureContractIsNotLegal2(x: int) diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean index 6e79453261..b58d8daa4e 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean @@ -13,7 +13,8 @@ open Strata namespace Strata.Laurel def program := r" -function returnAtEnd(x: int) returns (r: int) { +function returnAtEnd(x: int) returns (r: int) +{ if x > 0 then { if x == 1 then { return 1 diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean index 3ebe4eb4cf..6fe07c0d4b 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean @@ -29,6 +29,11 @@ function letsInFunction() returns (r: int) { z }; +procedure callLetsInFunction() opaque { + var x: int := letsInFunction(); + assert x == 2 +}; + function localVariableWithoutInitializer(): int { var x: int; //^^^^^^^^^^ error: local variables in functions must have initializers diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T4_LoopJumps.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T4_LoopJumps.lean index 040bf3a186..6fa2eaab2e 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T4_LoopJumps.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T4_LoopJumps.lean @@ -13,7 +13,9 @@ open Strata namespace Laurel def program := r" -procedure whileWithBreakAndContinue(steps: int, continueSteps: int, exitSteps: int): int { +procedure whileWithBreakAndContinue(steps: int, continueSteps: int, exitSteps: int): int + opaque +{ var counter = 0 { while(steps > 0) diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T5_ProcedureCalls.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T5_ProcedureCalls.lean index e1e5c0cfd8..b014d1c7cd 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T5_ProcedureCalls.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T5_ProcedureCalls.lean @@ -14,7 +14,7 @@ namespace Strata.Laurel def program := r" procedure fooReassign(): int - opaque + opaque // required because we don't yet support destructive assignment in transparent bodies { var x: int := 0; x := x + 1; @@ -24,7 +24,6 @@ procedure fooReassign(): int }; procedure fooSingleAssign(): int - opaque { var x: int := 0; var x2: int := x + 1; @@ -38,7 +37,7 @@ procedure fooProof() var x: int := fooReassign(); var y: int := fooSingleAssign() // The following assertions fails while it should succeed, -// because Core does not yet support transparent procedures +// because we don't yet support making fooReassign transparent // assert x == y; }; diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T6_Preconditions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T6_Preconditions.lean index c7f1742a88..9776411a4d 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T6_Preconditions.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T6_Preconditions.lean @@ -61,7 +61,7 @@ procedure multipleRequiresCaller() { var a: int := multipleRequires(1, 2); var b: int := multipleRequires(-1, 2) -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: precondition does not hold +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: precondition could not be proved }; function funcMultipleRequires(x: int, y: int): int diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T7_Decreases.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T7_Decreases.lean index ee5cfc149d..1fd5cdfbca 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T7_Decreases.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T7_Decreases.lean @@ -19,6 +19,7 @@ A procedure with a decreases clause may be called in an erased context. def program := r" procedure noDecreases(x: int): boolean; + procedure caller(x: int) requires noDecreases(x) // ^ error: noDecreases can not be called from a pure context, because it is not proven to terminate diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean index 526a03dd92..4c0321a009 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean @@ -31,9 +31,9 @@ procedure callerOfOpaqueProcedure() }; procedure invalidPostcondition(x: int) - opaque - ensures false -// ^^^^^ error: assertion does not hold + opaque + ensures false +// ^^^^^ error: assertion does not hold { }; " diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8c_BodilessInlining.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8c_BodilessInlining.lean index 34ef67a97e..0e6c623a03 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8c_BodilessInlining.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8c_BodilessInlining.lean @@ -5,6 +5,8 @@ -/ import Strata.SimpleAPI +import StrataTest.Util.TestDiagnostics +import StrataTest.Languages.Laurel.TestExamples /-! # Bodiless Procedure Inlining Test @@ -16,6 +18,9 @@ the inlined call is correctly rejected. -/ namespace Strata.Laurel.BodilessInliningTest +open StrataTest.Util +open Strata + private def laurelSource := " procedure bodilessProcedure() returns (r: int) opaque @@ -28,32 +33,12 @@ procedure caller() var x: int := bodilessProcedure(); assert x > 0; assert false +//^^^^^^^^^^^^ error: assertion could not be proved }; " -/-- info: "assert(161): ❌ fail" -/ -#guard_msgs in -#eval show IO String from do - let laurelProg ← Strata.parseLaurelText "test.laurel" laurelSource - let coreProg ← match ← Strata.laurelToCore laurelProg with - | .ok p => pure p - | .error e => throw (IO.userError s!"Translation failed: {e}") - let inlined ← match Strata.Core.inlineProcedures coreProg {} with - | .ok p => pure p - | .error e => throw (IO.userError s!"Inlining failed: {e}") - let vcResults ← - EIO.toIO (fun e => IO.Error.userError e) - (Strata.Core.verifyProgram inlined - { Core.VerifyOptions.default with verbose := .quiet } - (proceduresToVerify := some ["caller"])) - -- Collect only failing results - let failures := vcResults.filter fun vcr => - match vcr.outcome with - | .ok o => o.validityProperty != .unsat - | .error _ => true - let mut output := "" - for vcr in failures do - output := output ++ s!"{vcr.obligation.label}: {vcr.formatOutcome}" - return output +#guard_msgs (drop info, error) in +#eval testInputWithOffset "Postconditions" laurelSource 23 + (fun p => processLaurelFileWithOptions { translateOptions := { inlineFunctionsWhenPossible := true} } p) end Strata.Laurel.BodilessInliningTest diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T9_Nondeterministic.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T9_Nondeterministic.lean index d8dbacf369..1be2b5405c 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T9_Nondeterministic.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T9_Nondeterministic.lean @@ -20,7 +20,8 @@ nondet procedure nonDeterministic(x: int): (r: int) assumed }; -procedure caller() { +procedure caller() +{ var x = nonDeterministic(1) assert x > 0; var y = nonDeterministic(1) diff --git a/StrataTest/Languages/Laurel/Examples/Objects/T1_MutableFields.lean b/StrataTest/Languages/Laurel/Examples/Objects/T1_MutableFields.lean index e46f03ef99..b75a63c5de 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/T1_MutableFields.lean +++ b/StrataTest/Languages/Laurel/Examples/Objects/T1_MutableFields.lean @@ -67,20 +67,21 @@ procedure updatesAndAliasing() assert dAlias#intValue == d#intValue }; -procedure subsequentHeapMutations(c: Container) +procedure subsequentHeapMutations() opaque - modifies c { + var c: Container := new Container; + // The additional parenthesis on the next line are needed to let the parser succeed. Joe, any idea why this is needed? var sum: int := ((c#intValue := 1) + c#intValue) + (c#intValue := 2); assert sum == 4 }; -procedure implicitEquality(c: Container, d: Container) +procedure implicitEquality() opaque - modifies c - modifies d { + var c: Container := new Container; + var d: Container := new Container; c#intValue := 1; d#intValue := 2; if c#intValue == d#intValue then { @@ -101,11 +102,11 @@ composite SameFieldName { var intValue: bool } -procedure sameFieldNameDifferentType(a: Container, b: SameFieldName) +procedure sameFieldNameDifferentType() opaque - modifies a - modifies b { + var a: Container := new Container; + var b: SameFieldName := new SameFieldName; a#intValue := 1; b#intValue := true; diff --git a/StrataTest/Languages/Laurel/Examples/Objects/T2_ModifiesClauses.lean b/StrataTest/Languages/Laurel/Examples/Objects/T2_ModifiesClauses.lean index 52a16146c5..4ea1ec4ffc 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/T2_ModifiesClauses.lean +++ b/StrataTest/Languages/Laurel/Examples/Objects/T2_ModifiesClauses.lean @@ -127,7 +127,7 @@ procedure modifiesWildcardBodilessCaller() var x: int := d#value; modifiesWildcardBodiless(c, d); assert x == d#value // this should fail because modifies * means anything can change -//^^^^^^^^^^^^^^^^^^^ error: assertion does not hold +//^^^^^^^^^^^^^^^^^^^ error: assertion could not be proved }; procedure modifiesWildcardWithBody(c: Container, d: Container) @@ -152,7 +152,7 @@ procedure modifiesWildcardAndSpecificCaller() var x: int := d#value; modifiesWildcardAndSpecific(c, d); assert x == d#value // fails because modifies * subsumes modifies c -//^^^^^^^^^^^^^^^^^^^ error: assertion does not hold +//^^^^^^^^^^^^^^^^^^^ error: assertion could not be proved }; " diff --git a/StrataTest/Languages/Laurel/Examples/Objects/T3_ReadsClauses.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/T3_ReadsClauses.lr.st index d95606784d..5732c7d40c 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/T3_ReadsClauses.lr.st +++ b/StrataTest/Languages/Laurel/Examples/Objects/T3_ReadsClauses.lr.st @@ -16,10 +16,11 @@ composite Container { procedure opaqueProcedure(c: Container): int reads c - ensures true + opaque ; procedure foo(c: Container, d: Container) + opaque { var x = opaqueProcedure(c); d.value = 1; @@ -33,6 +34,7 @@ procedure foo(c: Container, d: Container) procedure permissionLessReader(c: Container): int reads {} + opaque { c.value } // ^^^^^^^ error: enclosing procedure 'permissionLessReader' does not have permission to read 'c.value' ; diff --git a/StrataTest/Languages/Laurel/Examples/Objects/T4_ImmutableFields.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/T4_ImmutableFields.lr.st index d92a44c6bd..f287c7f84d 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/T4_ImmutableFields.lr.st +++ b/StrataTest/Languages/Laurel/Examples/Objects/T4_ImmutableFields.lr.st @@ -9,6 +9,7 @@ composite ImmutableContainer { } procedure valueReader(c: ImmutableContainer): int + opaque { c.value } // no reads clause needed because value is immutable ; @@ -18,7 +19,8 @@ Translation towards SMT: type Composite; function ImmutableContainer_value(c: Composite): int -function valueReader(c: Composite): int { +function valueReader(c: Composite): int +{ ImmutableContainer_value(c) } diff --git a/StrataTest/Languages/Laurel/Examples/Objects/T5_inheritance.lean b/StrataTest/Languages/Laurel/Examples/Objects/T5_inheritance.lean index ba406b0ddc..50a880c4f6 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/T5_inheritance.lean +++ b/StrataTest/Languages/Laurel/Examples/Objects/T5_inheritance.lean @@ -89,7 +89,7 @@ procedure diamondInheritance() }; // Currently does not pass. Implementation needs b type invariant mechanism that we have yet to add. -//procedure typedParameter(b: Bottom) { +//procedure typedParameter(b: Bottom) opaque { // var b: Bottom := b; // assert b is Left; // assert b is Right; diff --git a/StrataTest/Languages/Laurel/Examples/Objects/T8_NonCompositeModifies.lean b/StrataTest/Languages/Laurel/Examples/Objects/T8_NonCompositeModifies.lean index 76bb786239..cb0a8c69a1 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/T8_NonCompositeModifies.lean +++ b/StrataTest/Languages/Laurel/Examples/Objects/T8_NonCompositeModifies.lean @@ -27,7 +27,7 @@ composite Container { procedure incWithPrimitiveModifies(x: int) returns (r: int) opaque modifies x -// ^ error: non-composite type +// ^ error: modifies clause entry has non-composite type 'int' and will be ignored { r := x + 1 }; @@ -36,7 +36,7 @@ procedure modifyContainerAndPrimitive(c: Container, x: int) opaque modifies c modifies x -// ^ error: non-composite type +// ^ error: modifies clause entry has non-composite type 'int' and will be ignored { c#value := 1 }; diff --git a/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Allocation.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Allocation.lr.st index cc0377ee27..dd02787340 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Allocation.lr.st +++ b/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Allocation.lr.st @@ -23,7 +23,8 @@ composite Immutable { }; } -procedure foo() { +procedure foo() +{ val immutable = Immutable.construct(); // constructor instance method can be called as a static. }; @@ -33,7 +34,7 @@ composite ImmutableChainOfTwo { invariant other.other == this // reading other.other is allowed because the field is immutable - procedure construct() + procedure construct() constructor requires contructing == {this} ensures constructing == {} @@ -55,7 +56,8 @@ composite ImmutableChainOfTwo { }; } -procedure foo2() { +procedure foo2() +{ val immutable = ImmutableChainOfTwo.construct(); val same = immutable.other.other; assert immutable =&= same; @@ -66,7 +68,7 @@ composite UsesHelperConstructor { val x: int val y: int - procedure setXhelper() + procedure setXhelper() constructor requires constructing == {this} ensures constructing == {this} && assigned(this.x) @@ -74,7 +76,7 @@ composite UsesHelperConstructor { this.x = 3; }; - procedure construct() + procedure construct() constructor requires contructing == {this} ensures constructing == {} diff --git a/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Constructors.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Constructors.lr.st index 5d2c02cfd5..2d9a5afaa4 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Constructors.lr.st +++ b/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Constructors.lr.st @@ -26,14 +26,15 @@ composite Immutable { // implicit: assert modifiesOf(construct()).forall(x -> x.invariant()); }; - procedure assignToY() + procedure assignToY() constructs Immutable { this.y = 3; }; } -procedure foo() { +procedure foo() +{ var c = new Immutable.construct(); var temp = c.x; c.z = 1; @@ -41,7 +42,8 @@ procedure foo() { assert temp == c.x; // pass }; -procedure pureCompositeAllocator(): boolean { +procedure pureCompositeAllocator(): boolean +{ // can be called in a determinstic context var i: Immutable = Immutable.construct(); var j: Immutable = Immutable.construct(); diff --git a/StrataTest/Languages/Laurel/Examples/Objects/WIP/6. TypeTests.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/WIP/6. TypeTests.lr.st index 6d94f9a08d..813865f4e7 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/WIP/6. TypeTests.lr.st +++ b/StrataTest/Languages/Laurel/Examples/Objects/WIP/6. TypeTests.lr.st @@ -19,7 +19,8 @@ composite Extended2 extends Base { var z: int } -procedure typeTests(e: Extended1) { +procedure typeTests(e: Extended1) +{ var b: Base = e as Base; // even upcasts are not implicit, but they pass statically var e2 = e as Extended2; // ^^ error: could not prove 'e' is of type 'Extended2' diff --git a/StrataTest/Languages/Laurel/Examples/Objects/WIP/9. Closures.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/WIP/9. Closures.lr.st index 73a72a4738..212df76ab8 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/WIP/9. Closures.lr.st +++ b/StrataTest/Languages/Laurel/Examples/Objects/WIP/9. Closures.lr.st @@ -100,17 +100,19 @@ procedure hasClosure() returns (r: int) // Option B: type closures composite ATrait { - procedure foo() returns (r: int) ensures r > 0 { + procedure foo() returns (r: int) ensures r > 0 + { abstract }; } procedure hasClosure() returns (r: int) + opaque ensures r == 7 { var x = 3; var aClosure := closure extends ATrait { - procedure foo() returns (r: int) + procedure foo() returns (r: int) { r = x + 4; }; diff --git a/StrataTest/Languages/Laurel/Examples/PrimitiveTypes/T2_String.lean b/StrataTest/Languages/Laurel/Examples/PrimitiveTypes/T2_String.lean index 02b5729dc8..c5f150cb95 100644 --- a/StrataTest/Languages/Laurel/Examples/PrimitiveTypes/T2_String.lean +++ b/StrataTest/Languages/Laurel/Examples/PrimitiveTypes/T2_String.lean @@ -15,7 +15,7 @@ namespace Laurel def program := r#" procedure testStringKO() -returns (result: string) + returns (result: string) opaque { var message: string := "Hello"; diff --git a/StrataTest/Languages/Laurel/LiftHolesTest.lean b/StrataTest/Languages/Laurel/LiftHolesTest.lean index 921704dcf1..cecc2d6cd5 100644 --- a/StrataTest/Languages/Laurel/LiftHolesTest.lean +++ b/StrataTest/Languages/Laurel/LiftHolesTest.lean @@ -53,7 +53,9 @@ procedure test() -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() opaque { var x: int := 1 + }; +procedure test() + opaque +{ var x: int := 1 + }; " -- Bare Hole as Assign Declare initializer → replaced with call (no longer preserved as havoc). @@ -69,7 +71,9 @@ procedure test() -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() opaque { var x: int := }; +procedure test() + opaque +{ var x: int := }; " -- Hole in comparison arg inside assert → int (inferred from sibling literal). @@ -85,7 +89,9 @@ procedure test() -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() opaque { assert > 0 }; +procedure test() + opaque +{ assert > 0 }; " -- Hole directly as assert condition → bool. @@ -101,7 +107,9 @@ procedure test() -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() opaque { assert }; +procedure test() + opaque +{ assert }; " -- Hole directly as assume condition → bool. @@ -117,7 +125,9 @@ procedure test() -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() opaque { assume }; +procedure test() + opaque +{ assume }; " -- Hole as if-then-else condition → bool. @@ -135,7 +145,9 @@ procedure test() -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() opaque { if then { assert true } }; +procedure test() + opaque +{ if then { assert true } }; " -- Hole in then-branch of if-then-else inside typed local variable → int. @@ -151,7 +163,9 @@ procedure test() -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() opaque { var x: int := if true then else 0 }; +procedure test() + opaque +{ var x: int := if true then else 0 }; " -- Hole as while-loop condition → bool. @@ -169,7 +183,9 @@ procedure test() -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() opaque { while() {} }; +procedure test() + opaque +{ while() {} }; " -- Hole as while-loop invariant → bool. @@ -188,7 +204,9 @@ procedure test() -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() opaque { while(true) invariant {} }; +procedure test() + opaque +{ while(true) invariant {} }; " /-! ## Operators -/ @@ -206,7 +224,9 @@ procedure test() -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() opaque { assert true && }; +procedure test() + opaque +{ assert true && }; " -- Hole in Neg inside typed local variable → int. @@ -222,7 +242,9 @@ procedure test() -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() opaque { var x: int := - }; +procedure test() + opaque +{ var x: int := - }; " -- Hole in StrConcat inside typed local variable → string. @@ -231,14 +253,13 @@ info: function $hole_0() returns ($result: string) opaque; procedure test() - opaque { var s: string := "hello" ++ $hole_0() }; -/ #guard_msgs in #eval! parseElimAndPrint - "procedure test() opaque { var s: string := \"hello\" ++ };" + "procedure test() { var s: string := \"hello\" ++ };" /-! ## Multiple holes -/ @@ -258,7 +279,9 @@ procedure test() -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() opaque { var x: int := + }; +procedure test() + opaque +{ var x: int := + }; " -- Holes across statements: Mul arg (int) then assert condition (bool). @@ -278,7 +301,9 @@ procedure test() -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() opaque { var x: int := 2 * ; assert }; +procedure test() + opaque +{ var x: int := 2 * ; assert }; " /-! ## Combinations: holes in nested contexts -/ @@ -298,7 +323,9 @@ procedure test() -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() opaque { if 1 + > 0 then { assert true } }; +procedure test() + opaque +{ if 1 + > 0 then { assert true } }; " -- Hole in Implies inside while invariant → bool. @@ -318,7 +345,9 @@ procedure test() -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() opaque { var p: bool; while(true) invariant p ==> {} }; +procedure test() + opaque +{ var p: bool; while(true) invariant p ==> {} }; " -- Hole in Mul inside typed local variable with real type → real. @@ -334,7 +363,9 @@ procedure test() -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() opaque { var r: real := 3.14 * }; +procedure test() + opaque +{ var r: real := 3.14 * }; " /-! ## Call argument and return type inference -/ @@ -352,7 +383,9 @@ procedure test(n: int) -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test(n: int) opaque { assert n > }; +procedure test(n: int) + opaque +{ assert n > }; " /-! ## Holes in functions -/ @@ -370,7 +403,9 @@ function test(x: int): int -/ #guard_msgs in #eval! parseElimAndPrint r" -function test(x: int): int opaque { }; +function test(x: int): int + opaque +{ }; " /-! ## Nondeterministic holes () -/ @@ -385,7 +420,9 @@ info: procedure test() -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() opaque { assert }; +procedure test() + opaque +{ assert }; " -- Mixed: det hole eliminated, nondet hole preserved. @@ -402,7 +439,9 @@ procedure test() -/ #guard_msgs in #eval! parseElimAndPrint r" -procedure test() opaque { var x: int := ; assert }; +procedure test() + opaque +{ var x: int := ; assert }; " -- Nondet hole in function → should be rejected (not tested here since diff --git a/StrataTest/Languages/Laurel/StatisticsTest.lean b/StrataTest/Languages/Laurel/StatisticsTest.lean index 00bc6c7b24..94deaaf6d9 100644 --- a/StrataTest/Languages/Laurel/StatisticsTest.lean +++ b/StrataTest/Languages/Laurel/StatisticsTest.lean @@ -66,6 +66,7 @@ procedure p1(a: bool, b: bool) returns (r: bool) }; procedure p2(x: int) returns (y: int) + opaque { y := x + }; diff --git a/StrataTest/Languages/Python/PySpecArgTypeTest.lean b/StrataTest/Languages/Python/PySpecArgTypeTest.lean index 21f8e48e21..4f9254b3c6 100644 --- a/StrataTest/Languages/Python/PySpecArgTypeTest.lean +++ b/StrataTest/Languages/Python/PySpecArgTypeTest.lean @@ -99,7 +99,12 @@ preconditions redundant. -/ info: procedure test_typed_func(x: Any, y: Any): Any opaque modifies * -{ result := ; assert Any..isfrom_int(x); assert Any..isfrom_str(y); assume Any..isfrom_float(result) }; +{ + result := ; + assert Any..isfrom_int(x); + assert Any..isfrom_str(y); + assume Any..isfrom_float(result) +}; -/ #guard_msgs in #eval! do diff --git a/StrataTest/Util/TestDiagnostics.lean b/StrataTest/Util/TestDiagnostics.lean index ebfb9f8ecb..3a23a20c4b 100644 --- a/StrataTest/Util/TestDiagnostics.lean +++ b/StrataTest/Util/TestDiagnostics.lean @@ -137,6 +137,10 @@ def testInputWithOffset (filename: String) (input : String) (lineOffset : Nat) IO.println s!"\nUnexpected diagnostics:" for diag in unmatchedDiagnostics do IO.println s!" - Line {diag.start.line}, Col {diag.start.column}-{diag.ending.column}: {diag.message}" + + if unmatchedExpectations.length == 0 && unmatchedDiagnostics.length == 0 then + IO.println s!"Duplicate diagnostics: {repr diagnostics}" + throw (IO.userError "Test failed") def testInput (filename: String) (input : String) (process : Lean.Parser.InputContext -> IO (Array Diagnostic)) : IO Unit := From b11e04296556ab254c8b0926d2bd7abb355646d9 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 26 May 2026 10:47:25 +0000 Subject: [PATCH 003/115] Fixes --- .../Laurel/AbstractToConcreteTreeTranslatorTest.lean | 1 - StrataTest/Languages/Python/PySpecArgTypeTest.lean | 7 ++++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean b/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean index 7b86139661..5134de4f39 100644 --- a/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean +++ b/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean @@ -263,4 +263,3 @@ info: procedure test(): int #eval do IO.println (← roundtrip r"procedure test(): int opaque { };") end Strata.Laurel -end diff --git a/StrataTest/Languages/Python/PySpecArgTypeTest.lean b/StrataTest/Languages/Python/PySpecArgTypeTest.lean index 21f8e48e21..4f9254b3c6 100644 --- a/StrataTest/Languages/Python/PySpecArgTypeTest.lean +++ b/StrataTest/Languages/Python/PySpecArgTypeTest.lean @@ -99,7 +99,12 @@ preconditions redundant. -/ info: procedure test_typed_func(x: Any, y: Any): Any opaque modifies * -{ result := ; assert Any..isfrom_int(x); assert Any..isfrom_str(y); assume Any..isfrom_float(result) }; +{ + result := ; + assert Any..isfrom_int(x); + assert Any..isfrom_str(y); + assume Any..isfrom_float(result) +}; -/ #guard_msgs in #eval! do From 9e4ee85485a003ddabd955fc03075f50cb93fbd5 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 26 May 2026 11:04:18 +0000 Subject: [PATCH 004/115] Refactoring --- Strata/Languages/Laurel/LaurelToCoreTranslator.lean | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index d2585a3873..7bb62d08d6 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -602,12 +602,6 @@ def translateProcedure (proc : Procedure) : TranslateM Core.Procedure := do pure $ some r | _ => pure none - -- Bodiless procedure: assume postconditions so that verification of the - -- procedure itself passes trivially, and inlining only introduces the - -- postconditions as assumptions (not the unsound `assume false`). - -- pure (postconditions.map fun (label, check) => - -- Core.Statement.assume label check.expr mdWithUnknownLoc) - -- Wrap body in a labeled block so early returns (exit) work correctly. -- Translate postconditions for Opaque and Abstract bodies let postconditions : ListMap Core.CoreLabel Core.Procedure.Check ← From 7408103194f6e09fdccca53c2b693f01c6d1d3e7 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 26 May 2026 11:07:57 +0000 Subject: [PATCH 005/115] Remove pack multiple outputs pass --- .../Laurel/LaurelCompilationPipeline.lean | 3 - .../Languages/Laurel/PackMultipleOutputs.lean | 166 ------------------ 2 files changed, 169 deletions(-) delete mode 100644 Strata/Languages/Laurel/PackMultipleOutputs.lean diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index a5535ce11a..90d740593b 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -12,7 +12,6 @@ import Strata.Languages.Laurel.EliminateReturnsInExpression import Strata.Languages.Laurel.EliminateValueReturns import Strata.Languages.Laurel.ConstrainedTypeElim -import Strata.Languages.Laurel.PackMultipleOutputs import Strata.Languages.Laurel.TypeAliasElim import Strata.Languages.Core.Verifier import Strata.Util.Statistics @@ -248,8 +247,6 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) let (program, model, passDiags, stats) ← runLaurelPasses options pctx program let unorderedCore := transparencyPass program emit "transparencyPass" "core.st" unorderedCore - let unorderedCore := packMultipleOutputsInFunctions unorderedCore - -- let unorderedCore := inlineLocalVariablesInExpressions unorderedCore -- Resolve so that identifiers introduced by earlier passes get uniqueIds. let (unorderedCore, model) := resolveUnorderedCore unorderedCore program (some model) diff --git a/Strata/Languages/Laurel/PackMultipleOutputs.lean b/Strata/Languages/Laurel/PackMultipleOutputs.lean deleted file mode 100644 index 270f0919d7..0000000000 --- a/Strata/Languages/Laurel/PackMultipleOutputs.lean +++ /dev/null @@ -1,166 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ -module - -public import Strata.Languages.Laurel.TransparencyPass - -/-! -# Eliminate Multiple Outputs - -Transforms bodiless functions with multiple outputs into functions that return -a single synthesized result datatype. Call sites are rewritten to destructure -the result using the generated accessors. - -This pass operates on `UnorderedCoreWithLaurelTypes → UnorderedCoreWithLaurelTypes`. --/ - -namespace Strata.Laurel - -public section - - -private def mkMd (e : StmtExpr) : StmtExprMd := { val := e, source := none } -private def mkVarMd (v : Variable) : VariableMd := { val := v, source := none } -private def mkTy (t : HighType) : HighTypeMd := { val := t, source := none } - -/-- Info about a function whose multiple outputs have been collapsed into a result datatype. -/ -private structure MultiOutInfo where - funcName : String - resultTypeName : String - constructorName : String - /-- Original output parameters (name, type). -/ - outputs : List Parameter - /-- Number of input parameters (used to detect implicit heap args at call sites). -/ - inputCount : Nat - -/-- Identify bodiless functions with multiple outputs and build info records. -/ -private def collectMultiOutFunctions (funcs : List Procedure) : List MultiOutInfo := - funcs.filterMap fun f => - if f.outputs.length > 1 && !f.body.isTransparent then - some { - funcName := f.name.text - resultTypeName := s!"{f.name.text}$result" - constructorName := s!"{f.name.text}$result$mk" - outputs := f.outputs - inputCount := f.inputs.length - } - else none - -/-- Generate a result datatype for a multi-output function. -/ -private def mkResultDatatype (info : MultiOutInfo) : DatatypeDefinition := - let args := info.outputs.zipIdx.map fun (p, i) => - { name := mkId s!"out{i}", type := p.type : Parameter } - { name := mkId info.resultTypeName - typeArgs := [] - constructors := [{ name := mkId info.constructorName, args := args }] } - -/-- Transform a multi-output function to return the result datatype. -/ -private def transformFunction (info : MultiOutInfo) (proc : Procedure) : Procedure := - let resultOutput : Parameter := - { name := mkId "$result", type := mkTy (.UserDefined (mkId info.resultTypeName)) } - { proc with outputs := [resultOutput] } - -/-- Destructor name for field `outN` of the result datatype. -/ -private def destructorName (info : MultiOutInfo) (idx : Nat) : String := - s!"{info.resultTypeName}..out{idx}" - -/-- Check whether a statement is an Assume node. -/ -private def isAssume (stmt : StmtExprMd) : Bool := - match stmt.val with - | .Assume _ => true - | _ => false - -/-- Rewrite a single multi-output Assign into a temp declaration + destructuring - assignments. Any `Assume` statements from `following` that appear immediately - after the call are collected and placed after the destructuring assignments, - so they observe the post-call variable values. - Returns the rewritten statements and the number of consumed following statements. -/ -private def rewriteAssign (infoMap : Std.HashMap String MultiOutInfo) - (targets : List VariableMd) (callee : Identifier) (args : List StmtExprMd) - (callSrc : Option FileRange) - (following : List StmtExprMd) (counter : Nat) : Option (List StmtExprMd × Nat) := - match infoMap.get? callee.text with - | some info => - if targets.length ≤ info.outputs.length then - let tempName := s!"${callee.text}$temp{counter}" - let fullArgs := args - let tempDecl := mkMd (.Assign [mkVarMd (.Declare ⟨mkId tempName, mkTy (.UserDefined (mkId info.resultTypeName))⟩)] - ⟨.StaticCall callee fullArgs, callSrc⟩) - let assigns := targets.zipIdx.map fun (tgt, i) => - mkMd (.Assign [tgt] - (mkMd (.StaticCall (mkId (destructorName info i)) - [mkMd (.Var (.Local (mkId tempName)))]))) - -- Collect any Assume statements that immediately follow the call. - -- These are placed after the destructuring assignments so they - -- observe the post-call values of output variables. - let assumes := following.takeWhile isAssume - let consumed := assumes.length - some (tempDecl :: assigns ++ assumes, consumed) - else none - | none => none - -/-- Rewrite a statement list, replacing multi-output call patterns. - When a multi-output Assign is followed by Assume statements (inserted by - the contract pass), the Assumes are placed after the destructuring - assignments so they reference post-call variable values. -/ -private def rewriteStmts (infoMap : Std.HashMap String MultiOutInfo) - (stmts : List StmtExprMd) : List StmtExprMd := - let rec go (remaining : List StmtExprMd) (acc : List StmtExprMd) (counter : Nat) : List StmtExprMd := - match remaining with - | [] => acc.reverse - | stmt :: rest => - match stmt.val with - | .Assign targets ⟨.StaticCall callee args, callSrc⟩ => - match rewriteAssign infoMap targets callee args callSrc rest counter with - | some (expanded, consumed) => go (rest.drop consumed) (expanded.reverse ++ acc) (counter + 1) - | none => go rest (stmt :: acc) counter - | _ => go rest (stmt :: acc) counter - termination_by remaining.length - go stmts [] 0 - -/-- Rewrite blocks in a StmtExprMd tree to handle multi-output calls. -/ -private def rewriteExpr (infoMap : Std.HashMap String MultiOutInfo) - (expr : StmtExprMd) : StmtExprMd := - mapStmtExpr (fun e => - match e.val with - | .Block stmts label => ⟨.Block (rewriteStmts infoMap stmts) label, e.source⟩ - | _ => e) expr - -/-- Rewrite all procedure bodies. -/ -private def rewriteProcedure (infoMap : Std.HashMap String MultiOutInfo) - (proc : Procedure) : Procedure := - match proc.body with - | .Transparent b => - -- Wrap in a block so rewriteStmts can process top-level statements - let wrapped := mkMd (.Block [b] none) - let rewritten := rewriteExpr infoMap wrapped - { proc with body := .Transparent rewritten } - | .Opaque posts (some impl) mods => - let wrapped := mkMd (.Block [impl] none) - let rewritten := rewriteExpr infoMap wrapped - { proc with body := .Opaque posts (some rewritten) mods } - | _ => proc - -/-- Eliminate multiple outputs from a UnorderedCoreWithLaurelTypes. -/ -def packMultipleOutputsInFunctions (program : UnorderedCoreWithLaurelTypes) - : UnorderedCoreWithLaurelTypes := - let infos := collectMultiOutFunctions program.functions - if infos.isEmpty then program else - let infoMap : Std.HashMap String MultiOutInfo := - infos.foldl (fun m info => m.insert info.funcName info) {} - let newDatatypes := infos.map mkResultDatatype - let functions := program.functions.map fun f => - match infoMap.get? f.name.text with - | some info => rewriteProcedure infoMap (transformFunction info f) - | none => rewriteProcedure infoMap f - let coreProcedures := program.coreProcedures.map fun p => rewriteProcedure infoMap p - { program with - functions := functions - coreProcedures := coreProcedures - datatypes := program.datatypes ++ newDatatypes } - -end -- public section -end Strata.Laurel From 4e8fa8b1db58e60fd5762ef7eeda58957ab65e7d Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 26 May 2026 11:11:27 +0000 Subject: [PATCH 006/115] Add error file --- .../T20_TransparentBodyError.lean | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean new file mode 100644 index 0000000000..9c2e4d5875 --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean @@ -0,0 +1,41 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Util.TestDiagnostics +import StrataTest.Languages.Laurel.TestExamples + +open StrataTest.Util + +namespace Strata +namespace Laurel + +def transparentBodyProgram := r" +procedure transparentBodyMultipleOuts() returns (q: int, r: int) +{ + assert true; + q := 3; + r := 2 +}; + +// No support for transparent void procedures yet +procedure transparentBody() +{ + assert true +}; + +procedure transparentProcedureCaller() opaque { + assign var x: int, var y: int := transparentBodyMultipleOuts(); + assert x == 3; + assert y == 2; + + transparentBody() +}; +" + +#guard_msgs(drop info, error) in +#eval testInputWithOffset "TransparentBody" transparentBodyProgram 14 processLaurelFile + +end Laurel From 2d1962949d29606d53f91169dc5a6c7cf0d9b54f Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 26 May 2026 11:13:26 +0000 Subject: [PATCH 007/115] Update tests --- .../Laurel/Examples/Fundamentals/T20_TransparentBody.lean | 6 ------ .../Examples/Fundamentals/T20_TransparentBodyError.lean | 1 - 2 files changed, 7 deletions(-) diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean index 2f44c90122..a1e82883de 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean @@ -23,12 +23,6 @@ procedure transparentProcedureCaller() opaque { var x: int := transparentBody(); assert x == 3 }; - -// No support for transparent void procedures yet -// procedure transparentBody() -// { -// assert true -// }; " #guard_msgs(drop info, error) in diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean index 9c2e4d5875..f97ba10875 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean @@ -20,7 +20,6 @@ procedure transparentBodyMultipleOuts() returns (q: int, r: int) r := 2 }; -// No support for transparent void procedures yet procedure transparentBody() { assert true From c9fd151569caf4b77f07a140f7a29c7c14a18aa1 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 26 May 2026 11:17:02 +0000 Subject: [PATCH 008/115] Add sources to lift phase --- .../Laurel/LiftImperativeExpressions.lean | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index e87b24d480..4a033cfc2d 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -87,12 +87,6 @@ structure LiftState where private def emptyMd : Option String := none -/-- Wrap a StmtExpr value with empty metadata -/ -private def bare (v : StmtExpr) : StmtExprMd := ⟨v, none⟩ - -/-- Wrap a HighType value with empty metadata -/ -private def bareType (v : HighType) : HighTypeMd := ⟨v, none⟩ - private def freshTempFor (varName : Identifier) : LiftM Identifier := do let counters := (← get).varCounters let counter := counters.find? (·.1 == varName) |>.map (·.2) |>.getD 0 @@ -231,7 +225,7 @@ private def liftAssignExpr (targets : List VariableMd) (seqValue : StmtExprMd) match target.val with | .Local varName => let snapshotName ← freshTempFor varName - let varType ← computeType (bare (.Var (.Local varName))) + let varType ← computeType (⟨ .Var (.Local varName), source⟩) -- Snapshot goes before the assignment (cons pushes to front) prepend (⟨.Assign [⟨.Declare ⟨snapshotName, varType⟩, source⟩] (⟨.Var (.Local varName), source⟩), source⟩) setSubst varName snapshotName @@ -254,8 +248,8 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do | .Hole false (some holeType) => -- Nondeterministic typed hole: lift to a fresh variable with no initializer (havoc) let holeVar ← freshCondVar - prepend (bare (.Var (.Declare ⟨holeVar, holeType⟩))) - return bare (.Var (.Local holeVar)) + prepend ⟨ .Var (.Declare ⟨holeVar, holeType⟩), source⟩ + return ⟨ .Var (.Local holeVar), source ⟩ | .Assign targets value => -- The expression result is the current substitution for the first target @@ -313,7 +307,7 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do ⟨.Assign [⟨.Local callResultVar, source⟩] seqCall, source⟩ ] modify fun s => { s with prependedStmts := s.prependedStmts ++ liftedCall} - return bare (.Var (.Local callResultVar)) + return ⟨.Var (.Local callResultVar), source⟩ | .IfThenElse cond thenBranch elseBranch => let model := (← get).model @@ -332,14 +326,14 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do modify fun s => { s with prependedStmts := [], subst := [] } let seqThen ← transformExpr thenBranch let thenPrepends ← takePrepends - let thenBlock := bare (.Block (thenPrepends ++ [⟨.Assign [⟨ .Local condVar, source⟩] seqThen, source⟩]) none) + let thenBlock := ⟨.Block (thenPrepends ++ [⟨.Assign [⟨ .Local condVar, source⟩] seqThen, source⟩]) none, source⟩ -- Process else-branch from scratch modify fun s => { s with prependedStmts := [], subst := [] } let seqElse ← match elseBranch with | some e => do let se ← transformExpr e let elsePrepends ← takePrepends - pure (some (bare (.Block (elsePrepends ++ [⟨.Assign [⟨ .Local condVar, source⟩] se, source⟩]) none))) + pure (some ⟨.Block (elsePrepends ++ [⟨.Assign [⟨ .Local condVar, source⟩] se, source⟩]) none, source⟩) | none => pure none -- Restore outer state modify fun s => { s with subst := savedSubst, prependedStmts := savedPrepends } @@ -350,8 +344,8 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do -- IfThenElse added first (cons puts it deeper), then declaration (cons puts it on top) -- Output order: declaration, then if-then-else prepend (⟨.IfThenElse seqCond thenBlock seqElse, source⟩) - prepend (bare (.Var (.Declare ⟨condVar, condType⟩))) - return bare (.Var (.Local condVar)) + prepend ⟨.Var (.Declare ⟨condVar, condType⟩), source⟩ + return ⟨.Var (.Local condVar), source⟩ else -- No assignments in branches — recurse normally let seqCond ← transformExpr cond @@ -436,7 +430,7 @@ def transformStmt (stmt : StmtExprMd) : LiftM (List StmtExprMd) := do | .Block stmts metadata => let seqStmts ← stmts.mapM transformStmt - return [bare (.Block seqStmts.flatten metadata)] + return [⟨.Block seqStmts.flatten metadata, source⟩] | .Var (.Declare _) => return [stmt] @@ -470,11 +464,11 @@ def transformStmt (stmt : StmtExprMd) : LiftM (List StmtExprMd) := do let condPrepends ← takePrepends let seqThen ← do let stmts ← transformStmt thenBranch - pure (bare (.Block stmts none)) + pure ⟨.Block stmts none, source⟩ let seqElse ← match elseBranch with | some e => do let se ← transformStmt e - pure (some (bare (.Block se none))) + pure $ some ⟨.Block se none, source⟩ | none => pure none return condPrepends ++ [⟨.IfThenElse seqCond seqThen seqElse, source⟩] @@ -490,7 +484,7 @@ def transformStmt (stmt : StmtExprMd) : LiftM (List StmtExprMd) := do let decPrepends ← takePrepends let seqBody ← do let stmts ← transformStmt body - pure (bare (.Block stmts none)) + pure ⟨.Block stmts none, source⟩ return condPrepends ++ invPrepends ++ decPrepends ++ [⟨.While seqCond seqInvs seqDec seqBody, source⟩] @@ -513,20 +507,20 @@ def transformStmt (stmt : StmtExprMd) : LiftM (List StmtExprMd) := do all_goals (apply Prod.Lex.left; try term_by_mem) end -def transformProcedureBody (body : StmtExprMd) : LiftM StmtExprMd := do +def transformProcedureBody (proc : Procedure) (body : StmtExprMd) : LiftM StmtExprMd := do let stmts ← transformStmt body match stmts with | [single] => pure single - | multiple => pure (bare (.Block multiple none)) + | multiple => pure ⟨.Block multiple none, proc.name.source⟩ def transformProcedure (proc : Procedure) : LiftM Procedure := do modify fun s => { s with subst := [], prependedStmts := [], varCounters := [] } match proc.body with | .Transparent bodyExpr => - let seqBody ← transformProcedureBody bodyExpr + let seqBody ← transformProcedureBody proc bodyExpr pure { proc with body := .Transparent seqBody } | .Opaque postconds impl modif => - let impl' ← impl.mapM transformProcedureBody + let impl' ← impl.mapM (transformProcedureBody proc) pure { proc with body := .Opaque postconds impl' modif } | .Abstract _ => pure proc From 19987e8ac8696b38d15a3934bbc01f4eb33733e0 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 26 May 2026 11:24:47 +0000 Subject: [PATCH 009/115] Update tests and improve errors --- Strata/Languages/Laurel/Laurel.lean | 35 +++++++++++++++++++ .../Laurel/LaurelToCoreTranslator.lean | 10 ++++-- .../T20_TransparentBodyError.lean | 5 +-- .../T2_ImpureExpressionsError.lean | 6 ++-- 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/Strata/Languages/Laurel/Laurel.lean b/Strata/Languages/Laurel/Laurel.lean index bd888d566e..cc0259aafa 100644 --- a/Strata/Languages/Laurel/Laurel.lean +++ b/Strata/Languages/Laurel/Laurel.lean @@ -450,6 +450,41 @@ def HighType.isBool : HighType → Bool | TBool => true | _ => false +/-- Return the constructor name of a `StmtExprMd` as a `String`. -/ +def StmtExpr.constructorName (e : StmtExpr) : String := + match e with + | .IfThenElse .. => "IfThenElse" + | .Block .. => "Block" + | .While .. => "While" + | .Exit .. => "Exit" + | .Return .. => "Return" + | .LiteralInt .. => "LiteralInt" + | .LiteralBool .. => "LiteralBool" + | .LiteralString .. => "LiteralString" + | .LiteralDecimal .. => "LiteralDecimal" + | .Var .. => "Var" + | .Assign .. => "Assign" + | .PureFieldUpdate .. => "PureFieldUpdate" + | .StaticCall .. => "StaticCall" + | .PrimitiveOp .. => "PrimitiveOp" + | .New .. => "New" + | .This => "This" + | .ReferenceEquals .. => "ReferenceEquals" + | .AsType .. => "AsType" + | .IsType .. => "IsType" + | .InstanceCall .. => "InstanceCall" + | .Quantifier .. => "Quantifier" + | .Assigned .. => "Assigned" + | .Old .. => "Old" + | .Fresh .. => "Fresh" + | .Assert .. => "Assert" + | .Assume .. => "Assume" + | .ProveBy .. => "ProveBy" + | .ContractOf .. => "ContractOf" + | .Abstract => "Abstract" + | .All => "All" + | .Hole .. => "Hole" + /-- Check whether a single modifies entry is the wildcard (`*`). -/ def StmtExprMd.isWildcard (m : StmtExprMd) : Bool := match m.val with | .All => true | _ => false diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index 7bb62d08d6..42b6bd3766 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -293,7 +293,7 @@ def translateExpr (expr : StmtExprMd) let re2 ← translateExpr e2 boundVars isPureContext return .eq () re1 re2 | .Assign _ _ => - disallowed expr.source "destructive assignments are not supported in functions or contracts" + disallowed expr.source "destructive assignments are not supported in transparent bodies or contracts" | .While _ _ _ _ => disallowed expr.source "loops are not supported in functions or contracts" | .Exit _ => disallowed expr.source "exit is not supported in expression position" @@ -322,8 +322,12 @@ def translateExpr (expr : StmtExprMd) -- Field selects should have been eliminated by heap parameterization -- If we see one here, it's an error in the pipeline throwExprDiagnostic $ diagnosticFromSource expr.source s!"FieldSelect should have been eliminated by heap parameterization: {Std.ToFormat.format target}#{fieldId.text}" DiagnosticType.StrataBug - | .Block _ _ => - throwExprDiagnostic $ diagnosticFromSource expr.source s!"block expression should have been lowered in a separate pass, expr: {repr expr}" DiagnosticType.StrataBug + | .Block (⟨ .Assign _ _, assignSource⟩ :: tail) _ => + disallowed assignSource "destructive assignments are not supported in transparent bodies or contracts" + | .Block (head :: tail) _ => + throwExprDiagnostic $ diagnosticFromSource expr.source s!"block expression starting with {head.val.constructorName} should have been lowered in a separate pass" DiagnosticType.StrataBug + | .Block [] _ => + throwExprDiagnostic $ diagnosticFromSource expr.source "empty block expression should have been lowered in a separate pass" DiagnosticType.StrataBug | .Return _ => disallowed expr.source "return expression should be lowered in a separate pass" | .AsType target _ => throwExprDiagnostic $ diagnosticFromSource expr.source "AsType expression translation" DiagnosticType.NotYetImplemented diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean index f97ba10875..282c0d520d 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean @@ -17,10 +17,11 @@ procedure transparentBodyMultipleOuts() returns (q: int, r: int) { assert true; q := 3; +//^^^^^^ error: destructive assignments are not supported in transparent bodies or contracts r := 2 }; -procedure transparentBody() +procedure transparentBodyNoOuts() { assert true }; @@ -30,7 +31,7 @@ procedure transparentProcedureCaller() opaque { assert x == 3; assert y == 2; - transparentBody() + transparentBodyNoOuts() }; " diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean index 88bd9f1648..390c0e6c62 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean @@ -24,7 +24,7 @@ procedure hasMutatingAssignment(): int function functionWithMutatingAssignment(x: int): int { x := x + 1 -//^^^^^^^^^^ error: destructive assignments are not supported in functions or contracts +//^^^^^^^^^^ error: destructive assignments are not supported in transparent bodies or contracts }; function functionWithWhile(x: int): int @@ -48,11 +48,11 @@ procedure impureContractIsNotLegal1(x: int) procedure impureContractIsNotLegal2(x: int) requires (x := 2) == 2 -// ^^^^^^ error: destructive assignments are not supported in functions or contracts +// ^^^^^^ error: destructive assignments are not supported in transparent bodies or contracts opaque { assert (x := 2) == 2 -// ^^^^^^ error: destructive assignments are not supported in functions or contracts +// ^^^^^^ error: destructive assignments are not supported in transparent bodies or contracts }; " From 24c398310af586efc9b0fa3d035ada2179787fe7 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 26 May 2026 11:36:57 +0000 Subject: [PATCH 010/115] Update comment --- .../Laurel/LaurelCompilationPipeline.lean | 49 ++----------------- Strata/Languages/Laurel/Resolution.lean | 47 ++++++++++++++++++ 2 files changed, 50 insertions(+), 46 deletions(-) diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index 90d740593b..2e84bd7902 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -191,46 +191,6 @@ private def runLaurelPasses (options : LaurelTranslateOptions) return (program, model, allDiags, allStats) -/-- -Convert an `UnorderedCoreWithLaurelTypes` to a flat `Program` suitable for -resolution and program-level passes. Composite types from the original Laurel -program are included so that references to composite types resolve correctly. --/ -private def toProgram (uc : UnorderedCoreWithLaurelTypes) (laurelProgram : Program) - : Program := - { staticProcedures := uc.functions ++ uc.coreProcedures, - staticFields := [], - types := uc.datatypes.map TypeDefinition.Datatype ++ - -- Hack to compensate for references to composite types not having been updated yet. - laurelProgram.types.filter (fun t => match t with | .Composite _ => true | _ => false), - constants := uc.constants } - -/-- -Reconstruct an `UnorderedCoreWithLaurelTypes` from a resolved `Program`, -preserving the structure of the original `UnorderedCoreWithLaurelTypes`. --/ -private def fromResolvedProgram (resolvedProgram : Program) - (_original : UnorderedCoreWithLaurelTypes) : UnorderedCoreWithLaurelTypes := - let resolvedProcs := resolvedProgram.staticProcedures - let resolvedDatatypes := resolvedProgram.types.filterMap fun td => - match td with | .Datatype dt => some dt | _ => none - { functions := resolvedProcs.filter (·.isFunctional) - coreProcedures := resolvedProcs.filter (!·.isFunctional) - datatypes := resolvedDatatypes - constants := resolvedProgram.constants } - -/-- -Resolve an `UnorderedCoreWithLaurelTypes` by converting to a flat `Program`, -running the resolution pass, and reconstructing the result. Returns the -resolved `UnorderedCoreWithLaurelTypes` and the `SemanticModel`. --/ -def resolveUnorderedCore (uc : UnorderedCoreWithLaurelTypes) - (laurelProgram : Program) (existingModel : Option SemanticModel := none) - : UnorderedCoreWithLaurelTypes × SemanticModel := - let fnProgram := toProgram uc laurelProgram - let fnResolveResult := resolve fnProgram existingModel - (fromResolvedProgram fnResolveResult.program uc, fnResolveResult.model) - /-- Translate Laurel Program to Core Program, also returning the lowered Laurel program. @@ -249,11 +209,8 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) emit "transparencyPass" "core.st" unorderedCore -- Resolve so that identifiers introduced by earlier passes get uniqueIds. - let (unorderedCore, model) := resolveUnorderedCore unorderedCore program (some model) - - -- Re-resolve after lifting so that freshly introduced variables (e.g. $cndtn_N) - -- created by liftExpressionAssignments also get uniqueIds in the model. - let (unorderedCore, fnModel) := resolveUnorderedCore unorderedCore program (some model) + let compositeTypes := program.types.filter (fun t => match t with | .Composite _ => true | _ => false) + let (unorderedCore, model) := resolveUnorderedCore unorderedCore (existingModel := some model) (additionalTypes := compositeTypes) let coreWithLaurelTypes := orderFunctionsAndProcedures unorderedCore @@ -265,7 +222,7 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) return (none, passDiags, program, stats) emit "CoreWithLaurelTypes" "core.st" coreWithLaurelTypes - let initState : TranslateState := { model := fnModel, overflowChecks := options.overflowChecks } + let initState : TranslateState := { model := model, overflowChecks := options.overflowChecks } let (coreProgramOption, translateState) := runTranslateM initState (translateLaurelToCore options program coreWithLaurelTypes) -- Because of the duplication between functions and procedures, this translation is liable to create duplicate diagnostics diff --git a/Strata/Languages/Laurel/Resolution.lean b/Strata/Languages/Laurel/Resolution.lean index eb09d57bb5..11c1503c62 100644 --- a/Strata/Languages/Laurel/Resolution.lean +++ b/Strata/Languages/Laurel/Resolution.lean @@ -7,6 +7,7 @@ module public import Strata.Languages.Laurel.Laurel public import Strata.Languages.Laurel.Grammar.AbstractToConcreteTreeTranslator +public import Strata.Languages.Laurel.TransparencyPass import Strata.Util.Tactics import Strata.Languages.Python.PythonLaurelCorePrelude @@ -941,4 +942,50 @@ def resolve (program : Program) (existingModel: Option SemanticModel := none) : errors := finalState.errors } +/-! ## Resolution for UnorderedCoreWithLaurelTypes -/ + +/-- +Convert an `UnorderedCoreWithLaurelTypes` to a flat `Program` suitable for +resolution. Additional type definitions (e.g. composite types from the original +Laurel program) can be supplied so that `UserDefined` type references resolve +correctly. +-/ +private def unorderedCoreToProgram (uc : UnorderedCoreWithLaurelTypes) + (additionalTypes : List TypeDefinition := []) : Program := + { staticProcedures := uc.functions ++ uc.coreProcedures, + staticFields := [], + types := uc.datatypes.map TypeDefinition.Datatype ++ additionalTypes, + constants := uc.constants } + +/-- +Reconstruct an `UnorderedCoreWithLaurelTypes` from a resolved `Program`. +-/ +private def fromResolvedProgram (resolvedProgram : Program) + : UnorderedCoreWithLaurelTypes := + let resolvedProcs := resolvedProgram.staticProcedures + let resolvedDatatypes := resolvedProgram.types.filterMap fun td => + match td with | .Datatype dt => some dt | _ => none + { functions := resolvedProcs.filter (·.isFunctional) + coreProcedures := resolvedProcs.filter (!·.isFunctional) + datatypes := resolvedDatatypes + constants := resolvedProgram.constants } + +/-- +Resolve an `UnorderedCoreWithLaurelTypes` by converting to a flat `Program`, +running the resolution pass, and reconstructing the result. Returns the +resolved `UnorderedCoreWithLaurelTypes` and the `SemanticModel`. + +`additionalTypes` can supply extra type definitions (e.g. composite types) that +are not part of the `UnorderedCoreWithLaurelTypes` but are needed for resolving +`UserDefined` type references. These additional types should not be necessary +but they are because certain type references have incorrectly not been updated. +-/ +def resolveUnorderedCore (uc : UnorderedCoreWithLaurelTypes) + (existingModel : Option SemanticModel := none) + (additionalTypes : List TypeDefinition := []) + : UnorderedCoreWithLaurelTypes × SemanticModel := + let fnProgram := unorderedCoreToProgram uc additionalTypes + let fnResolveResult := resolve fnProgram existingModel + (fromResolvedProgram fnResolveResult.program, fnResolveResult.model) + end From 3c235312f8b2ccadadaddf33ecee53607448e4cb Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 26 May 2026 11:51:58 +0000 Subject: [PATCH 011/115] Trigger CI From 604638105cd2556b39e603d9042d77f5113531d0 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 26 May 2026 13:24:54 +0000 Subject: [PATCH 012/115] Trigger CI From 86962e4e592f6e019a4afcc4de98e1ec940ff90d Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 26 May 2026 15:02:42 +0000 Subject: [PATCH 013/115] Trigger CI From d19234623ba5d345fb7e3a291de039401d12ab20 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Wed, 27 May 2026 10:50:42 +0000 Subject: [PATCH 014/115] Replace panic with safe access --- Strata/DL/Imperative/SMTUtils.lean | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Strata/DL/Imperative/SMTUtils.lean b/Strata/DL/Imperative/SMTUtils.lean index 541ec17d98..2c5fda6917 100644 --- a/Strata/DL/Imperative/SMTUtils.lean +++ b/Strata/DL/Imperative/SMTUtils.lean @@ -104,7 +104,9 @@ def getSMTId {Ident Ty} [ToFormat Ident] | (var, some ty) => do let (var', ty') ← typedVarToSMTFn var ty let key : Strata.SMT.UF := { id := var', args := [], out := ty' } - .ok (E.ufs[key]!) + match E.ufs[key]? with + | some id => .ok id + | none => .error f!"Variable {var} (SMT name: {var'}) not found in encoder state" def runSolver (solver : String) (args : Array String) : IO IO.Process.Output := do let output ← IO.Process.output { From 7a04e669db09e64d94584dcb3777060a578d104b Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 09:23:05 +0000 Subject: [PATCH 015/115] Move proof skipping logic from LaurelToCore translator to transparency pass --- Strata/Languages/Laurel/FilterPrelude.lean | 2 +- .../AbstractToConcreteTreeTranslator.lean | 6 +-- Strata/Languages/Laurel/Laurel.lean | 4 +- .../Laurel/LaurelToCoreTranslator.lean | 41 ++++--------------- Strata/Languages/Laurel/LaurelTypes.lean | 2 +- Strata/Languages/Laurel/MapStmtExpr.lean | 4 +- Strata/Languages/Laurel/Resolution.lean | 6 +-- Strata/Languages/Laurel/TransparencyPass.lean | 20 ++++++++- 8 files changed, 39 insertions(+), 46 deletions(-) diff --git a/Strata/Languages/Laurel/FilterPrelude.lean b/Strata/Languages/Laurel/FilterPrelude.lean index c4c2181c81..06534dbef0 100644 --- a/Strata/Languages/Laurel/FilterPrelude.lean +++ b/Strata/Languages/Laurel/FilterPrelude.lean @@ -107,7 +107,7 @@ private partial def collectExprNames (expr : StmtExprMd) : CollectM Unit := do | .Var (.Declare param) => collectHighTypeNames param.type | .PureFieldUpdate target _ newVal => collectExprNames target; collectExprNames newVal - | .PrimitiveOp _ args => args.forM collectExprNames + | .PrimitiveOp _ args _ => args.forM collectExprNames | .AsType target ty => collectExprNames target; collectHighTypeNames ty | .IsType target ty => collectExprNames target; collectHighTypeNames ty | .Quantifier _ param trigger body => diff --git a/Strata/Languages/Laurel/Grammar/AbstractToConcreteTreeTranslator.lean b/Strata/Languages/Laurel/Grammar/AbstractToConcreteTreeTranslator.lean index 1ed8e93828..ecc23beda1 100644 --- a/Strata/Languages/Laurel/Grammar/AbstractToConcreteTreeTranslator.lean +++ b/Strata/Languages/Laurel/Grammar/AbstractToConcreteTreeTranslator.lean @@ -133,11 +133,11 @@ where let calleeArg := laurelOp "identifier" #[ident callee.text] let argsArr := args.map stmtExprToArg |>.toArray laurelOp "call" #[calleeArg, commaSep argsArr] - | .PrimitiveOp op [a] => + | .PrimitiveOp op [a] _skipProof => laurelOp (operationName op) #[stmtExprToArg a] - | .PrimitiveOp op [a, b] => + | .PrimitiveOp op [a, b] _skipProof => laurelOp (operationName op) #[stmtExprToArg a, stmtExprToArg b] - | .PrimitiveOp op args => + | .PrimitiveOp op args _skipProof => -- Fallback for unusual arities let argsArr := args.map stmtExprToArg |>.toArray laurelOp (operationName op) argsArr diff --git a/Strata/Languages/Laurel/Laurel.lean b/Strata/Languages/Laurel/Laurel.lean index cc0259aafa..1a851785c9 100644 --- a/Strata/Languages/Laurel/Laurel.lean +++ b/Strata/Languages/Laurel/Laurel.lean @@ -293,8 +293,10 @@ inductive StmtExpr : Type where | PureFieldUpdate (target : AstNode StmtExpr) (fieldName : Identifier) (newValue : AstNode StmtExpr) /-- Call a static procedure by name with the given arguments. -/ | StaticCall (callee : Identifier) (arguments : List (AstNode StmtExpr)) - /-- Apply a primitive operation to the given arguments. -/ + /-- Apply a primitive operation to the given arguments. + The skipProof property is used internally. -/ | PrimitiveOp (operator : Operation) (arguments : List (AstNode StmtExpr)) + (skipProof: Bool := false) /-- Create new object (`new`). -/ | New (ref : Identifier) /-- Reference to the current object (`this`/`self`). -/ diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index 42b6bd3766..47135289fc 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -68,11 +68,6 @@ structure TranslateState where why the program was deemed invalid so that if no other diagnostics explain the suppression, these can be surfaced to the user. -/ coreDiagnostics : List DiagnosticModel := [] - /-- When `true`, use safe division (`intSafeDivOp`) and safe datatype selectors - (with preconditions). When `false`, use unsafe division (`intDivOp`) and - unsafe datatype selectors (without preconditions). - Set to `true` for proof procedures and `false` for functions. -/ - proof : Bool := false /-- The translation monad: state over Except, allowing both accumulated diagnostics and hard failures -/ @[expose] abbrev TranslateM := OptionT (StateM TranslateState) @@ -81,25 +76,6 @@ structure TranslateState where def emitDiagnostic (d : DiagnosticModel) : TranslateM Unit := modify fun s => { s with diagnostics := s.diagnostics ++ [d] } -/-- Adjust a datatype selector (destructor) name based on the `proof` flag. - Destructor names contain `..` (e.g. `IntList..head`, `IntList..head!`). - Tester names also contain `..` but start with `is` after the separator. - - `proof = true` → use safe selectors (strip `!` suffix) - - `proof = false` → use unsafe selectors (add `!` suffix) -/ -private def adjustSelectorName (name : String) (proof : Bool) : String := - -- Only adjust destructor names (contain ".." but are not testers) - match name.splitOn ".." with - | [_, suffix] => - if suffix.startsWith "is" then name -- tester, leave unchanged - else if proof then - name - -- Safe: strip trailing "!" - -- if name.endsWith "!" then (name.dropEnd 1).toString else name - else - -- Unsafe: add trailing "!" if not already present - if name.endsWith "!" then name else name ++ "!" - | _ => name -- not a destructor name, leave unchanged - private def invalidCoreType (source : Option FileRange) (reason : String) : TranslateM LMonoTy := do modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ [diagnosticFromSource source reason DiagnosticType.StrataBug] } @@ -179,7 +155,6 @@ def translateExpr (expr : StmtExprMd) let s ← get let model := s.model let md := astNodeToCoreMd expr - let proof := (← get).proof let disallowed (source : Option FileRange) (msg : String) : TranslateM Core.Expression.Expr := do if isPureContext then throwExprDiagnostic $ diagnosticFromSource source msg @@ -217,7 +192,8 @@ def translateExpr (expr : StmtExprMd) return .app () (if isReal then realNegOp else intNegOp) re | _ => throwExprDiagnostic $ diagnosticFromSource expr.source s!"translateExpr: Invalid unary op: {repr op}" DiagnosticType.StrataBug - | .PrimitiveOp op [e1, e2] => + | .PrimitiveOp op [e1, e2] skipProof => + let proof := !skipProof let re1 ← translateExpr e1 boundVars isPureContext let re2 ← translateExpr e2 boundVars isPureContext let binOp (bop : Core.Expression.Expr) : Core.Expression.Expr := @@ -237,9 +213,9 @@ def translateExpr (expr : StmtExprMd) | .Sub => return binOp (if isReal then realSubOp else intSubOp) | .Mul => return binOp (if isReal then realMulOp else intMulOp) | .Div => return binOp (if isReal then realDivOp else if proof then intSafeDivOp else intDivOp) - | .Mod => return binOp (if (← get).proof then intSafeModOp else intModOp) - | .DivT => return binOp (if (← get).proof then intSafeDivTOp else intDivTOp) - | .ModT => return binOp (if (← get).proof then intSafeModTOp else intModTOp) + | .Mod => return binOp (if proof then intSafeModOp else intModOp) + | .DivT => return binOp (if proof then intSafeDivTOp else intDivTOp) + | .ModT => return binOp (if proof then intSafeModTOp else intModTOp) | .Lt => return binOp (if isReal then realLtOp else intLtOp) | .Leq => return binOp (if isReal then realLeOp else intLeOp) | .Gt => return binOp (if isReal then realGtOp else intGtOp) @@ -247,7 +223,7 @@ def translateExpr (expr : StmtExprMd) | .StrConcat => return binOp strConcatOp | _ => throwExprDiagnostic $ diagnosticFromSource expr.source s!"Invalid binary op: {repr op}" DiagnosticType.NotYetImplemented - | .PrimitiveOp op args => + | .PrimitiveOp op args _ => throwExprDiagnostic $ diagnosticFromSource expr.source s!"PrimitiveOp {repr op} with {args.length} args is not supported" DiagnosticType.UserError | .IfThenElse cond thenBranch elseBranch => let bcond ← translateExpr cond boundVars isPureContext @@ -266,8 +242,7 @@ def translateExpr (expr : StmtExprMd) if isPureContext && !model.isFunction callee then disallowed expr.source s!"calls to procedures are not supported in functions or contracts" else - let calleeName := adjustSelectorName callee.text (← get).proof - let fnOp : Core.Expression.Expr := .op () ⟨calleeName, ()⟩ none + let fnOp : Core.Expression.Expr := .op () ⟨callee.text, ()⟩ none args.attach.foldlM (fun acc ⟨arg, _⟩ => do let re ← translateExpr arg boundVars isPureContext return .app () acc re) fnOp @@ -756,7 +731,6 @@ def translateLaurelToCore (options: LaurelTranslateOptions) (program : Program) let coreDecls ← ordered.decls.flatMapM fun | .funcs funcs isRecursive => do - modify fun s => { s with proof := false } let nonExternal := funcs.filter (fun p => !p.body.isExternal) let coreFuncs ← nonExternal.mapM (translateProcedureToFunction options isRecursive) if isRecursive then @@ -767,7 +741,6 @@ def translateLaurelToCore (options: LaurelTranslateOptions) (program : Program) else return coreFuncs | .procedure proc => do - modify fun s => { s with proof := true } let procDecl ← translateProcedure proc -- Translate axioms from invokeOn let invokeOnDecls ← match proc.invokeOn with diff --git a/Strata/Languages/Laurel/LaurelTypes.lean b/Strata/Languages/Laurel/LaurelTypes.lean index 9bbdc86a83..d428718263 100644 --- a/Strata/Languages/Laurel/LaurelTypes.lean +++ b/Strata/Languages/Laurel/LaurelTypes.lean @@ -59,7 +59,7 @@ def computeExprType (model : SemanticModel) (expr : StmtExprMd) : HighTypeMd := | .StaticCall callee _ => getCallType source model callee | .InstanceCall _ callee _ => getCallType source model callee -- Operators - | .PrimitiveOp op args => + | .PrimitiveOp op args _ => match args with | head :: tail => match op with diff --git a/Strata/Languages/Laurel/MapStmtExpr.lean b/Strata/Languages/Laurel/MapStmtExpr.lean index e3892bae93..10ca4756b7 100644 --- a/Strata/Languages/Laurel/MapStmtExpr.lean +++ b/Strata/Languages/Laurel/MapStmtExpr.lean @@ -59,8 +59,8 @@ def mapStmtExprM [Monad m] (f : StmtExprMd → m StmtExprMd) (expr : StmtExprMd) pure ⟨.PureFieldUpdate (← mapStmtExprM f target) fieldName (← mapStmtExprM f newValue), source⟩ | .StaticCall callee args => pure ⟨.StaticCall callee (← args.attach.mapM fun ⟨e, _⟩ => mapStmtExprM f e), source⟩ - | .PrimitiveOp op args => - pure ⟨.PrimitiveOp op (← args.attach.mapM fun ⟨e, _⟩ => mapStmtExprM f e), source⟩ + | .PrimitiveOp op args skipProof => + pure ⟨.PrimitiveOp op (← args.attach.mapM fun ⟨e, _⟩ => mapStmtExprM f e) skipProof, source⟩ | .ReferenceEquals lhs rhs => pure ⟨.ReferenceEquals (← mapStmtExprM f lhs) (← mapStmtExprM f rhs), source⟩ | .AsType target ty => diff --git a/Strata/Languages/Laurel/Resolution.lean b/Strata/Languages/Laurel/Resolution.lean index 11c1503c62..2efa70dc3b 100644 --- a/Strata/Languages/Laurel/Resolution.lean +++ b/Strata/Languages/Laurel/Resolution.lean @@ -476,13 +476,13 @@ def resolveStmtExpr (exprMd : StmtExprMd) : ResolveM StmtExprMd := do s!"Multi-output procedure '{callee'.text}' used in expression position; it returns {outputCount} values but only one can be used here. Use a multi-target assignment instead." modify fun s => { s with errors := s.errors.push diag } pure (.StaticCall callee' args') - | .PrimitiveOp op args => + | .PrimitiveOp op args skipProof => -- Resolve arguments in value context let saved := (← get).inValueContext modify fun s => { s with inValueContext := true } let args' ← args.mapM resolveStmtExpr modify fun s => { s with inValueContext := saved } - pure (.PrimitiveOp op args') + pure (.PrimitiveOp op args' skipProof) | .New ref => let ref' ← resolveRef ref source (expected := #[.compositeType, .datatypeDefinition]) @@ -751,7 +751,7 @@ private def collectStmtExpr (map : Std.HashMap Nat ResolvedNode) (expr : StmtExp let map := collectStmtExpr map target collectStmtExpr map newVal | .StaticCall _ args => args.foldl collectStmtExpr map - | .PrimitiveOp _ args => args.foldl collectStmtExpr map + | .PrimitiveOp _ args _ => args.foldl collectStmtExpr map | .ReferenceEquals lhs rhs => let map := collectStmtExpr map lhs collectStmtExpr map rhs diff --git a/Strata/Languages/Laurel/TransparencyPass.lean b/Strata/Languages/Laurel/TransparencyPass.lean index 43a7eed2da..8b30043007 100644 --- a/Strata/Languages/Laurel/TransparencyPass.lean +++ b/Strata/Languages/Laurel/TransparencyPass.lean @@ -55,6 +55,21 @@ def stripAssertAssume (expr : StmtExprMd) : StmtExprMd := | _ => ⟨.Block stmts' label, e.source⟩ | _ => e) expr +/-- Adjust a datatype selector (destructor) name based on the `proof` flag. + Destructor names contain `..` (e.g. `IntList..head`, `IntList..head!`). + Tester names also contain `..` but start with `is` after the separator. + - `proof = true` → use safe selectors (strip `!` suffix) + - `proof = false` → use unsafe selectors (add `!` suffix) -/ +private def adjustSelectorName (name : String) : String := + -- Only adjust destructor names (contain ".." but are not testers) + match name.splitOn ".." with + | [_, suffix] => + if suffix.startsWith "is" then name -- tester, leave unchanged + else + -- Unsafe: add trailing "!" if not already present + if name.endsWith "!" then name else name ++ "!" + | _ => name -- not a destructor name, leave unchanged + /-- Rewrite StaticCall callees to their `$asFunction` versions, but only for procedures whose names appear in `nonExternalNames`. -/ private def rewriteCallsToFunctional (asFunctionNames : List String) (expr : StmtExprMd) : StmtExprMd := @@ -64,7 +79,10 @@ private def rewriteCallsToFunctional (asFunctionNames : List String) (expr : Stm if asFunctionNames.contains callee.text then let funcCallee := { callee with text := callee.text ++ "$asFunction", uniqueId := none } ⟨.StaticCall funcCallee args, e.source⟩ - else e + else + let newName := adjustSelectorName callee.text + ⟨ .StaticCall newName args, e.source⟩ + | .PrimitiveOp operator arguments _ => ⟨ .PrimitiveOp operator arguments true, e.source⟩ | _ => e) expr /-- Build a free postcondition equating the procedure's output to its functional version. From a45f8f0c3393abb906320870923d8cbd4ae3eae0 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 09:32:14 +0000 Subject: [PATCH 016/115] Small refactoring --- Strata/Languages/Laurel/LaurelToCoreTranslator.lean | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index 47135289fc..4209435b01 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -193,7 +193,6 @@ def translateExpr (expr : StmtExprMd) | _ => throwExprDiagnostic $ diagnosticFromSource expr.source s!"translateExpr: Invalid unary op: {repr op}" DiagnosticType.StrataBug | .PrimitiveOp op [e1, e2] skipProof => - let proof := !skipProof let re1 ← translateExpr e1 boundVars isPureContext let re2 ← translateExpr e2 boundVars isPureContext let binOp (bop : Core.Expression.Expr) : Core.Expression.Expr := @@ -212,10 +211,10 @@ def translateExpr (expr : StmtExprMd) | .Add => return binOp (if isReal then realAddOp else intAddOp) | .Sub => return binOp (if isReal then realSubOp else intSubOp) | .Mul => return binOp (if isReal then realMulOp else intMulOp) - | .Div => return binOp (if isReal then realDivOp else if proof then intSafeDivOp else intDivOp) - | .Mod => return binOp (if proof then intSafeModOp else intModOp) - | .DivT => return binOp (if proof then intSafeDivTOp else intDivTOp) - | .ModT => return binOp (if proof then intSafeModTOp else intModTOp) + | .Div => return binOp (if isReal then realDivOp else if skipProof then intDivOp else intSafeDivOp ) + | .Mod => return binOp (if skipProof then intModOp else intSafeModOp) + | .DivT => return binOp (if skipProof then intDivTOp else intSafeDivTOp) + | .ModT => return binOp (if skipProof then intModTOp else intSafeModTOp) | .Lt => return binOp (if isReal then realLtOp else intLtOp) | .Leq => return binOp (if isReal then realLeOp else intLeOp) | .Gt => return binOp (if isReal then realGtOp else intGtOp) From d0fdd35d61c2b6e068bdd9481ec4bd421eee7af4 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 09:37:28 +0000 Subject: [PATCH 017/115] Cleanup --- Strata/Languages/Laurel/EliminateValueReturns.lean | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Strata/Languages/Laurel/EliminateValueReturns.lean b/Strata/Languages/Laurel/EliminateValueReturns.lean index 45d5af0ab4..f465c6055c 100644 --- a/Strata/Languages/Laurel/EliminateValueReturns.lean +++ b/Strata/Languages/Laurel/EliminateValueReturns.lean @@ -6,7 +6,6 @@ module public import Strata.Languages.Laurel.MapStmtExpr -public import Strata.Languages.Laurel.TransparencyPass /-! # Eliminate Value Returns @@ -88,16 +87,6 @@ def eliminateValueReturnsTransform (program : Program) : Program × Array Diagno ) ([], #[]) ({ program with staticProcedures := procs.reverse }, diags) -/-- Transform an `UnorderedCoreWithLaurelTypes` by eliminating value returns - in all core (non-functional) procedures. -/ -def eliminateValueReturnsTransformUnordered (uc : UnorderedCoreWithLaurelTypes) - : UnorderedCoreWithLaurelTypes × Array DiagnosticModel := - let (procs, diags) := uc.coreProcedures.foldl (fun (ps, ds) proc => - let (proc', procDiags) := eliminateValueReturnsInProc proc - (proc' :: ps, ds ++ procDiags) - ) ([], #[]) - ({ uc with coreProcedures := procs.reverse }, diags) - end -- public section end Laurel From c62cfb47f9b868eec019a90ce7b46b3eb2bd3e5b Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 09:45:16 +0000 Subject: [PATCH 018/115] Add contract pass --- Strata/Languages/Core/Factory.lean | 1 + Strata/Languages/Laurel/ContractPass.lean | 421 ++++++++++++++++++ .../Laurel/CoreDefinitionsForLaurel.lean | 11 +- .../Laurel/CoreGroupingAndOrdering.lean | 13 +- Strata/Languages/Laurel/DatatypeTesters.lean | 54 +++ .../Languages/Laurel/DesugarShortCircuit.lean | 17 +- .../Laurel/EliminateMultipleOutputs.lean | 166 +++++++ .../Laurel/EliminateReturnStatements.lean | 73 +++ .../Laurel/EliminateReturnsInExpression.lean | 215 +++++---- ...rns.lean => EliminateValuesInReturns.lean} | 39 +- Strata/Languages/Laurel/FilterPrelude.lean | 2 - .../AbstractToConcreteTreeTranslator.lean | 9 +- .../ConcreteToAbstractTreeTranslator.lean | 5 + .../Laurel/Grammar/LaurelGrammar.lean | 2 +- .../Languages/Laurel/Grammar/LaurelGrammar.st | 4 +- .../Laurel/HeapParameterization.lean | 11 +- .../InlineLocalVariablesInExpressions.lean | 83 ++++ Strata/Languages/Laurel/Laurel.lean | 3 + .../Laurel/LaurelCompilationPipeline.lean | 152 +++++-- .../Laurel/LaurelToCoreTranslator.lean | 67 ++- .../Laurel/LiftImperativeExpressions.lean | 252 ++++++++--- Strata/Languages/Laurel/MapStmtExpr.lean | 174 +++++++- Strata/Languages/Laurel/Resolution.lean | 31 +- Strata/Languages/Laurel/TransparencyPass.lean | 56 ++- Strata/Languages/Python/PythonToLaurel.lean | 2 +- .../Languages/Core/Examples/TypeDecl.lean | 2 +- .../AbstractToConcreteTreeTranslatorTest.lean | 4 +- .../Laurel/ConstrainedTypeElimTest.lean | 3 +- .../Laurel/DivisionByZeroCheckTest.lean | 2 +- .../Fundamentals/T10_ConstrainedTypes.lean | 22 +- .../Fundamentals/T14_Quantifiers.lean | 2 +- .../Fundamentals/T15_ShortCircuit.lean | 1 - .../Fundamentals/T16_PropertySummary.lean | 4 +- .../Fundamentals/T18_RecursiveFunction.lean | 9 +- .../Examples/Fundamentals/T19_InvokeOn.lean | 2 +- .../T20_TransparentBodyError.lean | 15 +- .../Fundamentals/T22_ArityMismatch.lean | 5 +- .../Fundamentals/T2_ImpureExpressions.lean | 9 +- .../T2_ImpureExpressionsError.lean | 1 - .../Examples/Fundamentals/T3_ControlFlow.lean | 33 +- .../Fundamentals/T3_ControlFlowError.lean | 5 +- .../Fundamentals/T6_Preconditions.lean | 8 +- .../Examples/Fundamentals/T7_Decreases.lean | 8 +- .../Fundamentals/T8_Postconditions.lean | 23 +- .../Fundamentals/T8_PostconditionsErrors.lean | 39 -- .../T8b_EarlyReturnPostconditions.lean | 2 +- .../T8d_HeapMutatingValueReturn.lean | 2 +- .../Fundamentals/T9_Nondeterministic.lean | 6 + .../Examples/Objects/T1_MutableFields.lean | 8 +- .../Examples/Objects/T3_ReadsClauses.lr.st | 3 +- .../Laurel/Examples/Objects/T6_Datatypes.lean | 28 +- .../Examples/Objects/WIP/5. Allocation.lr.st | 2 + .../Objects/WIP/5. Constructors.lr.st | 1 + .../Objects/WIP/7. InstanceCallables.lr.st | 3 + .../Examples/Objects/WIP/9. Closures.lr.st | 10 + .../Laurel/LiftExpressionAssignmentsTest.lean | 6 +- .../Languages/Laurel/LiftHolesTest.lean | 14 +- .../LiftImperativeCallsInAssertTest.lean | 24 +- StrataTest/Languages/Python/ToLaurelTest.lean | 24 +- .../expected_laurel/test_any_dict.expected | 2 +- .../expected_laurel/test_any_list.expected | 2 +- .../expected_laurel/test_arithmetic.expected | 12 +- .../test_assert_false.expected | 2 +- .../expected_laurel/test_augadd_list.expected | 4 +- .../expected_laurel/test_augfloordiv.expected | 5 +- .../test_augmented_assign.expected | 7 +- .../expected_laurel/test_augmod.expected | 5 +- .../test_break_continue.expected | 4 +- .../test_bubble_sort_step.expected | 47 +- .../expected_laurel/test_class_empty.expected | 2 +- .../test_class_field_init.expected | 3 +- .../test_class_field_use.expected | 3 +- .../test_class_method_call_from_main.expected | 3 +- .../test_class_methods.expected | 13 +- .../test_class_mixed_init.expected | 4 +- .../test_class_no_init.expected | 2 +- .../test_class_no_init_multi_field.expected | 2 +- .../test_class_no_init_with_method.expected | 7 +- .../test_class_with_methods.expected | 2 +- .../test_coerce_int_in_any_list.expected | 4 +- .../expected_laurel/test_datetime.expected | 7 +- .../test_datetime_now_tz.expected | 7 +- .../test_default_params.expected | 16 +- .../test_dict_add_key.expected | 2 +- .../expected_laurel/test_dict_assign.expected | 2 +- .../expected_laurel/test_dict_create.expected | 4 +- .../expected_laurel/test_dict_in.expected | 4 +- .../test_dict_overwrite.expected | 2 +- .../test_empty_dict_access.expected | 4 +- .../test_flag_pattern.expected | 7 +- .../test_for_else_break.expected | 2 +- .../expected_laurel/test_for_loop.expected | 6 +- .../expected_laurel/test_for_range.expected | 6 +- .../test_function_def_calls.expected | 6 +- .../expected_laurel/test_if_elif.expected | 3 +- .../test_int_bool_conversion.expected | 2 +- .../test_int_floordiv.expected | 4 +- .../expected_laurel/test_int_mod.expected | 4 +- .../test_int_negative_floordiv.expected | 5 +- .../test_int_negative_mod.expected | 5 +- .../expected_laurel/test_list_assign.expected | 2 +- .../expected_laurel/test_list_concat.expected | 4 +- .../expected_laurel/test_list_create.expected | 4 +- .../expected_laurel/test_list_empty.expected | 2 +- .../expected_laurel/test_list_in.expected | 4 +- .../expected_laurel/test_loops.expected | 16 +- .../test_method_call_with_kwargs.expected | 11 +- .../test_method_kwargs_no_hierarchy.expected | 2 +- .../test_mixed_types_list.expected | 4 +- .../test_module_level.expected | 10 +- .../test_multi_function.expected | 20 +- .../test_nested_calls.expected | 4 +- .../test_nested_optional.expected | 2 +- .../test_none_in_list.expected | 2 +- .../test_param_reassign.expected | 16 +- .../test_param_reassign_kwargs.expected | 6 +- .../test_precondition_verification.expected | 18 +- .../test_procedure_in_assert.expected | 8 +- .../test_regex_negative.expected | 50 +-- .../test_regex_positive.expected | 306 ++++++------- .../test_return_types.expected | 10 +- .../test_timedelta_expr.expected | 7 +- .../test_try_except_modeled.expected | 9 +- .../test_try_except_nested.expected | 4 +- .../test_tuple_create.expected | 4 +- .../expected_laurel/test_tuple_swap.expected | 5 +- .../expected_laurel/test_tuple_type.expected | 2 +- .../test_tuple_unpack.expected | 5 +- .../test_type_dict_annotation.expected | 2 +- .../test_type_list_annotation.expected | 2 +- .../test_var_shadow_func.expected | 3 +- .../test_variable_reassign.expected | 4 +- .../expected_laurel/test_while_loop.expected | 9 +- .../Languages/Python/run_py_analyze_sarif.py | 6 +- .../Languages/Python/tests/cbmc_expected.txt | 1 + .../Languages/Python/AnalyzeLaurelTest.lean | 7 +- 136 files changed, 2140 insertions(+), 857 deletions(-) create mode 100644 Strata/Languages/Laurel/ContractPass.lean create mode 100644 Strata/Languages/Laurel/DatatypeTesters.lean create mode 100644 Strata/Languages/Laurel/EliminateMultipleOutputs.lean create mode 100644 Strata/Languages/Laurel/EliminateReturnStatements.lean rename Strata/Languages/Laurel/{EliminateValueReturns.lean => EliminateValuesInReturns.lean} (70%) create mode 100644 Strata/Languages/Laurel/InlineLocalVariablesInExpressions.lean delete mode 100644 StrataTest/Languages/Laurel/Examples/Fundamentals/T8_PostconditionsErrors.lean diff --git a/Strata/Languages/Core/Factory.lean b/Strata/Languages/Core/Factory.lean index a5ea88df7a..6cb4bd296d 100644 --- a/Strata/Languages/Core/Factory.lean +++ b/Strata/Languages/Core/Factory.lean @@ -42,6 +42,7 @@ def KnownLTys : LTys := t[real], t[Triggers], t[TriggerGroup], + t[errorVoid], -- Note: t[bv] elaborates to (.forAll [] .tcons "bitvec" ). -- We can simply add the following here. t[∀n. bitvec n], diff --git a/Strata/Languages/Laurel/ContractPass.lean b/Strata/Languages/Laurel/ContractPass.lean new file mode 100644 index 0000000000..196b83d03d --- /dev/null +++ b/Strata/Languages/Laurel/ContractPass.lean @@ -0,0 +1,421 @@ +/- + 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 all inputs and all + outputs as parameters and returns the conjunction of postconditions. It is + marked as functional and does not call the original procedure. +- Insert `assume foo$pre(inputs)` at the start of the body. +- Insert `assert foo$post(inputs, outputs)` at the end of the body. + +For each call to a contracted procedure: +- Assign all input arguments to temporary variables before the call. +- Insert `assert foo$pre(temps)` before the call (precondition check). +- After the call, insert `assume foo$post(temps, outputs)` (postcondition assumption). +-/ + +namespace Strata.Laurel + +public section + +private def mkMd (e : StmtExpr) : StmtExprMd := { val := e, source := none } +private def mkVarMd (v : Variable) : VariableMd := { val := v, source := none } + +/-- 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 Condition := + 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 (.Var (.Local p.name)) + +/-- Build a helper function that returns the conjunction of the given conditions. -/ +private def mkConditionProc (name : String) (params : List Parameter) + (conditions : List Condition) : Procedure := + { name := mkId name + inputs := params + outputs := [⟨mkId "$result", { val := .TBool, source := none }⟩] + preconditions := [] + decreases := none + isFunctional := true + body := .Transparent (conjoin (conditions.map (·.condition))) } + +/-- Build a postcondition function that takes all inputs and all outputs as + parameters and returns the conjunction of postconditions. The function is + marked as functional and does not call the original procedure. + + For a procedure `foo(a, b) returns (x, y)` with postcondition `P(a, b, x, y)`, + generates: + ``` + function foo$post(a, b, x, y) returns ($result : bool) { + P(a, b, x, y) + } + ``` +-/ +private def mkPostConditionProc (name : String) + (inputParams : List Parameter) (outputParams : List Parameter) + (conditions : List Condition) : Procedure := + let allParams := inputParams ++ outputParams + { name := mkId name + inputs := allParams + outputs := [⟨mkId "$result", { val := .TBool, source := none }⟩] + preconditions := [] + decreases := none + isFunctional := false + body := .Transparent (conjoin (conditions.map (·.condition))) } + +/-- Extract a combined summary from a list of conditions. -/ +private def combinedSummary (clauses : List Condition) : Option String := + let summaries := clauses.filterMap (·.summary) + 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 + inputParams : List Parameter + 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 + let preAssume : List StmtExprMd := + if info.hasPreCondition then + let preSrc := match proc.preconditions.head? with + | some pc => pc.condition.source + | none => none + [⟨.Assume (mkCall info.preName inputArgs), preSrc⟩] + else [] + let postAssert : List StmtExprMd := + if info.hasPostCondition then + postconds.map fun pc => + let summary := pc.summary.getD "postcondition" + ⟨.Assert { condition := pc.condition, summary := some summary }, pc.condition.source⟩ + else [] + match proc.body with + | .Transparent body => + .Transparent ⟨.Block (preAssume ++ [body] ++ postAssert) none, body.source⟩ + | .Opaque _ (some impl) _ => + .Opaque [] (some ⟨.Block (preAssume ++ [impl] ++ postAssert) none, impl.source⟩) [] + | .Opaque _ none mods => + .Opaque [] none mods + | .Abstract _ => + .Abstract [] + | b => b + +/-- Generate temporary variable assignments for input arguments at a call site. + Returns (temp declarations+assignments, temp variable references). + Uses the parameter types from the procedure's contract info so that + resolution can type-check the generated temporaries. + `callIdx` distinguishes multiple calls to the same procedure. -/ +private def mkTempAssignments (args : List StmtExprMd) (calleeName : String) + (inputParams : List Parameter) (callIdx : Nat) (src : Option FileRange) + : List StmtExprMd × List StmtExprMd := + let indexed := args.zipIdx + let decls := indexed.map fun (arg, i) => + let tempName := s!"${calleeName}${callIdx}$arg{i}" + let paramType := match inputParams[i]? with + | some p => p.type + | none => { val := .Unknown, source := none } + let param : Parameter := { name := mkId tempName, type := paramType } + ⟨StmtExpr.Assign [mkVarMd (.Declare param)] arg, src⟩ + let refs := indexed.map fun (_, i) => + let tempName := s!"${calleeName}${callIdx}$arg{i}" + mkMd (.Var (.Local (mkId tempName))) + (decls, refs) + +/-- 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). + Takes and returns a call counter for generating unique temp variable names. + When `isFunctional` is true, precondition checks use `assume` instead of + `assert` since asserts are not supported in functions during Core translation. + + At call sites: + 1. Assign input arguments to temporary variables. + 2. Assert precondition using temps. + 3. Execute the call using temps as arguments. + 4. Assume postcondition using temps + output variables. -/ +private def rewriteStmt (contractInfoMap : Std.HashMap String ContractInfo) + (isFunctional : Bool) (callCounter : Nat) (e : StmtExprMd) : List StmtExprMd × Nat := + let src := e.source + let mkWithSrc (se : StmtExpr) : StmtExprMd := ⟨se, src⟩ + match e.val with + | .Assign targets (.mk (.StaticCall callee args) callSrc) => + match contractInfoMap.get? callee.text with + | some info => + let (tempDecls, tempRefs) := mkTempAssignments args callee.text info.inputParams callCounter src + let callWithTemps : StmtExprMd := ⟨.Assign targets ⟨.StaticCall callee tempRefs, callSrc⟩, src⟩ + let preCheck := if info.hasPreCondition then + if isFunctional then + [mkWithSrc (.Assume (mkCall info.preName tempRefs))] + else + [mkWithSrc (.Assert { condition := mkCall info.preName tempRefs, summary := some (info.preSummary.getD "precondition") })] + else [] + -- After the call, assume postcondition with temps (inputs) + output variables + let outputArgs := targets.filterMap fun t => + match t.val with + | .Local name => some (mkMd (.Var (.Local name))) + | .Declare param => some (mkMd (.Var (.Local param.name))) + | _ => none + let postAssume := if info.hasPostCondition + then [mkWithSrc (.Assume (mkCall info.postName (tempRefs ++ outputArgs)))] else [] + (tempDecls ++ preCheck ++ [callWithTemps] ++ postAssume, callCounter + 1) + | none => ([e], callCounter) + | .StaticCall callee args => + match contractInfoMap.get? callee.text with + | some info => + let (tempDecls, tempRefs) := mkTempAssignments args callee.text info.inputParams callCounter src + let preCheck := if info.hasPreCondition then + if isFunctional then + [mkWithSrc (.Assume (mkCall info.preName tempRefs))] + else + [mkWithSrc (.Assert { condition := mkCall info.preName tempRefs, summary := some (info.preSummary.getD "precondition") })] + else [] + -- For bare calls with postconditions, capture outputs in temp variables + -- so we can pass them to the $post function. + let (callStmt, postAssume, returnValue) := + if info.hasPostCondition && !info.outputParams.isEmpty then + let outputTempDecls := info.outputParams.zipIdx.map fun (p, i) => + let tempName := s!"${callee.text}${callCounter}$out{i}" + mkVarMd (.Declare { name := mkId tempName, type := p.type }) + let callWithOutputs : StmtExprMd := + ⟨.Assign outputTempDecls ⟨.StaticCall callee tempRefs, src⟩, src⟩ + let outputRefs := info.outputParams.zipIdx.map fun (_, i) => + let tempName := s!"${callee.text}${callCounter}$out{i}" + mkMd (.Var (.Local (mkId tempName))) + let assume := [mkWithSrc (.Assume (mkCall info.postName (tempRefs ++ outputRefs)))] + -- If the procedure has a single output, append the output variable + -- reference so the expanded block evaluates to the call result + -- (needed when the call appears in expression position). + let retVal : List StmtExprMd := match outputRefs with + | [single] => [single] + | _ => [] + (callWithOutputs, assume, retVal) + else + (mkWithSrc (.StaticCall callee tempRefs), [], []) + (tempDecls ++ preCheck ++ [callStmt] ++ postAssume ++ returnValue, callCounter + 1) + | none => ([e], callCounter) + | _ => ([e], callCounter) + +/-- Rewrite call sites in a statement/expression tree. Uses `mapStmtExprFlattenM`: + - `pre` intercepts `Assign targets (StaticCall ...)` to a contracted procedure, + handling it directly so the assignment targets are used as output variables + for the postcondition assume. + - `post` handles bare `StaticCall` to a contracted procedure anywhere in the + tree, returning the expanded list of statements (argument assignments, + precondition assert, call, postcondition assume, output variable reference). + For Block parents the list is flattened; for other parents it is wrapped + in a Block. -/ +private def rewriteCallSites (contractInfoMap : Std.HashMap String ContractInfo) + (isFunctional : Bool) (expr : StmtExprMd) : StmtExprMd := + let rewriteStaticCall (counter : Nat) (callee : Identifier) (args : List StmtExprMd) + (info : ContractInfo) (src : Option FileRange) + : List StmtExprMd × Nat := + let mkWithSrc (se : StmtExpr) : StmtExprMd := ⟨se, src⟩ + let (tempDecls, tempRefs) := mkTempAssignments args callee.text info.inputParams counter src + let preCheck := if info.hasPreCondition then + if isFunctional then + [mkWithSrc (.Assume (mkCall info.preName tempRefs))] + else + [mkWithSrc (.Assert { condition := mkCall info.preName tempRefs, summary := some (info.preSummary.getD "precondition") })] + else [] + let (callStmt, postAssume, returnValue) := + if info.hasPostCondition && !info.outputParams.isEmpty then + let outputTempDecls := info.outputParams.zipIdx.map fun (p, i) => + let tempName := s!"${callee.text}${counter}$out{i}" + mkVarMd (.Declare { name := mkId tempName, type := p.type }) + let callWithOutputs : StmtExprMd := + ⟨.Assign outputTempDecls ⟨.StaticCall callee tempRefs, src⟩, src⟩ + let outputRefs := info.outputParams.zipIdx.map fun (_, i) => + let tempName := s!"${callee.text}${counter}$out{i}" + mkMd (.Var (.Local (mkId tempName))) + let assume := [mkWithSrc (.Assume (mkCall info.postName (tempRefs ++ outputRefs)))] + let retVal : List StmtExprMd := match outputRefs with + | [single] => [single] + | _ => [] + (callWithOutputs, assume, retVal) + else + (mkWithSrc (.StaticCall callee tempRefs), [], []) + (tempDecls ++ preCheck ++ [callStmt] ++ postAssume ++ returnValue, counter + 1) + let (result, _) := StateT.run (s := (0 : Nat)) <| + mapStmtExprFlattenM (m := StateM Nat) + -- Pre: intercept Assign targets (StaticCall ...) before recursion + (fun e => do + match e.val with + | .Assign targets (.mk (.StaticCall callee args) callSrc) => + match contractInfoMap.get? callee.text with + | some info => + let counter ← get + let src := e.source + let mkWithSrc (se : StmtExpr) : StmtExprMd := ⟨se, src⟩ + -- Recurse into arguments using mapStmtExprM with the post logic + let args' ← args.mapM (mapStmtExprM (m := StateM Nat) (fun e' => do + match e'.val with + | .StaticCall callee' args' => + match contractInfoMap.get? callee'.text with + | some info' => + let counter' ← get + let (stmts, counter'') := rewriteStaticCall counter' callee' args' info' e'.source + set counter'' + return ⟨.Block stmts none, e'.source⟩ + | none => return e' + | _ => return e')) + let (tempDecls, tempRefs) := mkTempAssignments args' callee.text info.inputParams counter src + let callWithTemps : StmtExprMd := ⟨.Assign targets ⟨.StaticCall callee tempRefs, callSrc⟩, src⟩ + let preCheck := if info.hasPreCondition then + if isFunctional then + [mkWithSrc (.Assume (mkCall info.preName tempRefs))] + else + [mkWithSrc (.Assert { condition := mkCall info.preName tempRefs, summary := some (info.preSummary.getD "precondition") })] + else [] + let outputArgs := targets.filterMap fun t => + match t.val with + | .Local name => some (mkMd (.Var (.Local name))) + | .Declare param => some (mkMd (.Var (.Local param.name))) + | _ => none + let postAssume := if info.hasPostCondition + then [mkWithSrc (.Assume (mkCall info.postName (tempRefs ++ outputArgs)))] else [] + set (counter + 1) + return some (tempDecls ++ preCheck ++ [callWithTemps] ++ postAssume) + | none => return none + | _ => return none) + -- Post: handle bare StaticCall (not direct RHS of Assign to contracted proc) + (fun e => do + match e.val with + | .StaticCall callee args => + match contractInfoMap.get? callee.text with + | some info => + let counter ← get + let (stmts, counter') := rewriteStaticCall counter callee args info e.source + set counter' + return stmts + | none => return [e] + | _ => return [e]) expr + result + +/-- Rewrite call sites in all bodies of a procedure. -/ +private def rewriteCallSitesInProc (contractInfoMap : Std.HashMap String ContractInfo) + (proc : Procedure) : Procedure := + let rw := rewriteCallSites contractInfoMap proc.isFunctional + match proc.body with + | .Transparent body => + { proc with body := .Transparent (rw body) } + | .Opaque posts impl mods => + let body := Body.Opaque (posts.map (·.mapCondition 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 && ...)`. + The trigger controls when the SMT solver instantiates the axiom. -/ +private def mkInvokeOnAxiom (params : List Parameter) (trigger : StmtExprMd) + (postconds : List Condition) : StmtExprMd := + let body := conjoin (postconds.map (·.condition)) + -- 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 (.Quantifier .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.inputs proc.outputs postconds] + preProc ++ postProc + + -- Transform procedures: strip contracts, add assume/assert, rewrite call sites + let transformedProcs := program.staticProcedures.map fun proc => + 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 diff --git a/Strata/Languages/Laurel/CoreDefinitionsForLaurel.lean b/Strata/Languages/Laurel/CoreDefinitionsForLaurel.lean index 2df59b8ceb..23b8a2ec7b 100644 --- a/Strata/Languages/Laurel/CoreDefinitionsForLaurel.lean +++ b/Strata/Languages/Laurel/CoreDefinitionsForLaurel.lean @@ -27,16 +27,21 @@ program Laurel; datatype LaurelResolutionErrorPlaceholder {} datatype Float64IsNotSupportedYet {} +datatype LaurelUnit { MkLaurelUnit() } // The types for these Map functions are incorrect. // We'll fix them when Laurel supports polymorphism -function select(map: int, key: int) : int +// And then we can remove the datatype Box as well +// And remove the hacky filter in HeapParameterization +datatype Box { MkBox() } + +function select(map: int, key: int) : Box external; -function update(map: int, key: int, value: int) : int +function update(map: int, key: int, value: int) : Box external; -function const(value: int) : int +function const(value: int) : Box external; #end diff --git a/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean b/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean index 77b51d869f..df0a7bd11a 100644 --- a/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean +++ b/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean @@ -112,18 +112,18 @@ Build the procedure call graph, run Tarjan's SCC algorithm, and return each SCC as a list of procedures paired with a flag indicating whether the SCC is recursive. Results are in reverse topological order: dependencies before dependents. -Procedures with `invokeOn` are placed as early as possible — before +Procedures with axioms are placed as early as possible — before unrelated procedures without them — by stably partitioning them first before building the graph. Tarjan then naturally assigns them lower indices, causing them to appear earlier in the output. -/ public def computeSccDecls (program : UnorderedCoreWithLaurelTypes) : List (List Procedure × Bool) := - -- Stable partition: procedures with invokeOn come first, preserving relative + -- Stable partition: procedures with axioms come first, preserving relative -- order within each group. Tarjan then places them earlier in the topological output. let allProcs := program.functions ++ program.coreProcedures - let (withInvokeOn, withoutInvokeOn) := - allProcs.partition (fun p => p.invokeOn.isSome) - let orderedProcs : List Procedure := withInvokeOn ++ withoutInvokeOn + let (withAxioms, withoutAxioms) := + allProcs.partition (fun p => !p.axioms.isEmpty) + let orderedProcs : List Procedure := withAxioms ++ withoutAxioms -- Build a call-graph over all procedures. -- An edge proc → callee means proc's body/contracts contain a StaticCall to callee. @@ -141,7 +141,8 @@ public def computeSccDecls (program : UnorderedCoreWithLaurelTypes) : List (List | _ => [] let contractExprs : List StmtExprMd := proc.preconditions.map (·.condition) ++ - proc.invokeOn.toList + proc.invokeOn.toList ++ + proc.axioms (bodyExprs ++ contractExprs).flatMap collectStaticCallNames -- Build the OutGraph for Tarjan. diff --git a/Strata/Languages/Laurel/DatatypeTesters.lean b/Strata/Languages/Laurel/DatatypeTesters.lean new file mode 100644 index 0000000000..542546d642 --- /dev/null +++ b/Strata/Languages/Laurel/DatatypeTesters.lean @@ -0,0 +1,54 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ +module + +public import Strata.Languages.Laurel.Laurel + +/-! +## Datatype Tester Generation + +For each constructor of a datatype, generate an external testing function. +The tester function takes a single argument of the datatype's type and returns +`bool`. Its name is determined by `DatatypeDefinition.testerName`. + +This pass runs at the start of the Laurel pipeline, before resolution, so that +the tester functions are available as normal static procedures. +-/ + +namespace Strata.Laurel + +public section + +/-- Generate an external tester function for a single constructor of a datatype. -/ +private def mkTesterFunction (dt : DatatypeDefinition) (ctor : DatatypeConstructor) : Procedure := + let testerName := dt.testerName ctor + let inputParam : Parameter := { + name := mkId "value" + type := { val := .UserDefined dt.name, source := none } + } + let outputParam : Parameter := { + name := mkId "$result" + type := { val := .TBool, source := none } + } + { name := mkId testerName + inputs := [inputParam] + outputs := [outputParam] + preconditions := [] + decreases := none + isFunctional := true + body := .External } + +/-- Generate external tester functions for all constructors of all datatypes in the program. -/ +def generateDatatypeTesters (program : Program) : Program := + let testers := program.types.flatMap fun td => + match td with + | .Datatype dt => dt.constructors.map (mkTesterFunction dt) + | _ => [] + { program with staticProcedures := testers ++ program.staticProcedures } + +end -- public section + +end Strata.Laurel diff --git a/Strata/Languages/Laurel/DesugarShortCircuit.lean b/Strata/Languages/Laurel/DesugarShortCircuit.lean index ef5982430e..c5703f5910 100644 --- a/Strata/Languages/Laurel/DesugarShortCircuit.lean +++ b/Strata/Languages/Laurel/DesugarShortCircuit.lean @@ -24,11 +24,11 @@ namespace Strata.Laurel public section -private def bare (v : StmtExpr) : StmtExprMd := ⟨v, none⟩ /-- Local rewrite of a single short-circuit node. Recursion is handled by `mapStmtExpr`. -/ -private def desugarShortCircuitNode (model : SemanticModel) (expr : StmtExprMd) : StmtExprMd := +private def desugarShortCircuitNode (imperativeCallees : List String) (expr : StmtExprMd) : StmtExprMd := let source := expr.source + let wrap (v : StmtExpr) : StmtExprMd := ⟨v, source⟩ match expr.val with | .PrimitiveOp op args => match op, args with @@ -36,20 +36,21 @@ private def desugarShortCircuitNode (model : SemanticModel) (expr : StmtExprMd) -- short-circuits converted to IfThenElse). The check still works because -- `containsAssignmentOrImperativeCall` recurses into IfThenElse. | .AndThen, [a, b] | .Implies, [a, b] => - if containsAssignmentOrImperativeCall model b then + if containsAssignmentOrImperativeCall imperativeCallees b then let elseVal := match op with | .AndThen => false | _ => true - ⟨.IfThenElse a b (some (bare (.LiteralBool elseVal))), source⟩ + ⟨.IfThenElse a b (some (wrap (.LiteralBool elseVal))), source⟩ else expr | .OrElse, [a, b] => - if containsAssignmentOrImperativeCall model b then - ⟨.IfThenElse a (bare (.LiteralBool true)) (some b), source⟩ + if containsAssignmentOrImperativeCall imperativeCallees b then + ⟨.IfThenElse a (wrap (.LiteralBool true)) (some b), source⟩ else expr | _, _ => expr | _ => expr /-- Desugar short-circuit operators in a program. -/ -def desugarShortCircuit (model : SemanticModel) (program : Program) : Program := - mapProgram (mapStmtExpr (desugarShortCircuitNode model)) program +def desugarShortCircuit (program : Program) : Program := + let imperativeCallees := program.staticProcedures.map (fun p => p.name.text) + mapProgram (mapStmtExpr (desugarShortCircuitNode imperativeCallees)) program end -- public section end Strata.Laurel diff --git a/Strata/Languages/Laurel/EliminateMultipleOutputs.lean b/Strata/Languages/Laurel/EliminateMultipleOutputs.lean new file mode 100644 index 0000000000..ae932c9b7e --- /dev/null +++ b/Strata/Languages/Laurel/EliminateMultipleOutputs.lean @@ -0,0 +1,166 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ +module + +public import Strata.Languages.Laurel.TransparencyPass + +/-! +# Eliminate Multiple Outputs + +Transforms bodiless functions with multiple outputs into functions that return +a single synthesized result datatype. Call sites are rewritten to destructure +the result using the generated accessors. + +This pass operates on `UnorderedCoreWithLaurelTypes → UnorderedCoreWithLaurelTypes`. +-/ + +namespace Strata.Laurel + +public section + + +private def mkMd (e : StmtExpr) : StmtExprMd := { val := e, source := none } +private def mkVarMd (v : Variable) : VariableMd := { val := v, source := none } +private def mkTy (t : HighType) : HighTypeMd := { val := t, source := none } + +/-- Info about a function whose multiple outputs have been collapsed into a result datatype. -/ +private structure MultiOutInfo where + funcName : String + resultTypeName : String + constructorName : String + /-- Original output parameters (name, type). -/ + outputs : List Parameter + /-- Number of input parameters (used to detect implicit heap args at call sites). -/ + inputCount : Nat + +/-- Identify bodiless functions with multiple outputs and build info records. -/ +private def collectMultiOutFunctions (funcs : List Procedure) : List MultiOutInfo := + funcs.filterMap fun f => + if f.outputs.length > 1 && !f.body.isTransparent then + some { + funcName := f.name.text + resultTypeName := s!"{f.name.text}$result" + constructorName := s!"{f.name.text}$result$mk" + outputs := f.outputs + inputCount := f.inputs.length + } + else none + +/-- Generate a result datatype for a multi-output function. -/ +private def mkResultDatatype (info : MultiOutInfo) : DatatypeDefinition := + let args := info.outputs.zipIdx.map fun (p, i) => + { name := mkId s!"out{i}", type := p.type : Parameter } + { name := mkId info.resultTypeName + typeArgs := [] + constructors := [{ name := mkId info.constructorName, args := args }] } + +/-- Transform a multi-output function to return the result datatype. -/ +private def transformFunction (info : MultiOutInfo) (proc : Procedure) : Procedure := + let resultOutput : Parameter := + { name := mkId "$result", type := mkTy (.UserDefined (mkId info.resultTypeName)) } + { proc with outputs := [resultOutput] } + +/-- Destructor name for field `outN` of the result datatype. -/ +private def destructorName (info : MultiOutInfo) (idx : Nat) : String := + s!"{info.resultTypeName}..out{idx}" + +/-- Check whether a statement is an Assume node. -/ +private def isAssume (stmt : StmtExprMd) : Bool := + match stmt.val with + | .Assume _ => true + | _ => false + +/-- Rewrite a single multi-output Assign into a temp declaration + destructuring + assignments. Any `Assume` statements from `following` that appear immediately + after the call are collected and placed after the destructuring assignments, + so they observe the post-call variable values. + Returns the rewritten statements and the number of consumed following statements. -/ +private def rewriteAssign (infoMap : Std.HashMap String MultiOutInfo) + (targets : List VariableMd) (callee : Identifier) (args : List StmtExprMd) + (callSrc : Option FileRange) + (following : List StmtExprMd) (counter : Nat) : Option (List StmtExprMd × Nat) := + match infoMap.get? callee.text with + | some info => + if targets.length ≤ info.outputs.length then + let tempName := s!"${callee.text}$temp{counter}" + let fullArgs := args + let tempDecl := mkMd (.Assign [mkVarMd (.Declare ⟨mkId tempName, mkTy (.UserDefined (mkId info.resultTypeName))⟩)] + ⟨.StaticCall callee fullArgs, callSrc⟩) + let assigns := targets.zipIdx.map fun (tgt, i) => + mkMd (.Assign [tgt] + (mkMd (.StaticCall (mkId (destructorName info i)) + [mkMd (.Var (.Local (mkId tempName)))]))) + -- Collect any Assume statements that immediately follow the call. + -- These are placed after the destructuring assignments so they + -- observe the post-call values of output variables. + let assumes := following.takeWhile isAssume + let consumed := assumes.length + some (tempDecl :: assigns ++ assumes, consumed) + else none + | none => none + +/-- Rewrite a statement list, replacing multi-output call patterns. + When a multi-output Assign is followed by Assume statements (inserted by + the contract pass), the Assumes are placed after the destructuring + assignments so they reference post-call variable values. -/ +private def rewriteStmts (infoMap : Std.HashMap String MultiOutInfo) + (stmts : List StmtExprMd) : List StmtExprMd := + let rec go (remaining : List StmtExprMd) (acc : List StmtExprMd) (counter : Nat) : List StmtExprMd := + match remaining with + | [] => acc.reverse + | stmt :: rest => + match stmt.val with + | .Assign targets ⟨.StaticCall callee args, callSrc⟩ => + match rewriteAssign infoMap targets callee args callSrc rest counter with + | some (expanded, consumed) => go (rest.drop consumed) (expanded.reverse ++ acc) (counter + 1) + | none => go rest (stmt :: acc) counter + | _ => go rest (stmt :: acc) counter + termination_by remaining.length + go stmts [] 0 + +/-- Rewrite blocks in a StmtExprMd tree to handle multi-output calls. -/ +private def rewriteExpr (infoMap : Std.HashMap String MultiOutInfo) + (expr : StmtExprMd) : StmtExprMd := + mapStmtExpr (fun e => + match e.val with + | .Block stmts label => ⟨.Block (rewriteStmts infoMap stmts) label, e.source⟩ + | _ => e) expr + +/-- Rewrite all procedure bodies. -/ +private def rewriteProcedure (infoMap : Std.HashMap String MultiOutInfo) + (proc : Procedure) : Procedure := + match proc.body with + | .Transparent b => + -- Wrap in a block so rewriteStmts can process top-level statements + let wrapped := mkMd (.Block [b] none) + let rewritten := rewriteExpr infoMap wrapped + { proc with body := .Transparent rewritten } + | .Opaque posts (some impl) mods => + let wrapped := mkMd (.Block [impl] none) + let rewritten := rewriteExpr infoMap wrapped + { proc with body := .Opaque posts (some rewritten) mods } + | _ => proc + +/-- Eliminate multiple outputs from a UnorderedCoreWithLaurelTypes. -/ +def eliminateMultipleOutputs (program : UnorderedCoreWithLaurelTypes) + : UnorderedCoreWithLaurelTypes := + let infos := collectMultiOutFunctions program.functions + if infos.isEmpty then program else + let infoMap : Std.HashMap String MultiOutInfo := + infos.foldl (fun m info => m.insert info.funcName info) {} + let newDatatypes := infos.map mkResultDatatype + let functions := program.functions.map fun f => + match infoMap.get? f.name.text with + | some info => rewriteProcedure infoMap (transformFunction info f) + | none => rewriteProcedure infoMap f + let coreProcedures := program.coreProcedures.map fun p => rewriteProcedure infoMap p + { program with + functions := functions + coreProcedures := coreProcedures + datatypes := program.datatypes ++ newDatatypes } + +end -- public section +end Strata.Laurel diff --git a/Strata/Languages/Laurel/EliminateReturnStatements.lean b/Strata/Languages/Laurel/EliminateReturnStatements.lean new file mode 100644 index 0000000000..7846ec8263 --- /dev/null +++ b/Strata/Languages/Laurel/EliminateReturnStatements.lean @@ -0,0 +1,73 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ +module + +public import Strata.Languages.Laurel.MapStmtExpr + +/-! +# Eliminate Return Statements + +Replaces `return` statements in imperative procedure bodies with assignments +to the output parameters followed by an `exit` to a labelled block that wraps +the entire body. This ensures that code placed after the body block (e.g., +postcondition assertions inserted by the contract pass) is always reached. + +This pass should run after `EliminateReturnsInExpression` (which handles +functional/expression-position returns) and before the contract pass. +-/ + +namespace Strata.Laurel + +public section + +private def returnLabel : String := "$return" + + + + +/-- Transform a single procedure: wrap body in a labelled block and replace returns. -/ +private def eliminateReturnStmts (proc : Procedure) : Procedure := + match proc.body with + | .Opaque postconds (some impl) mods => + let impl' := replaceReturn proc.outputs impl + let wrapped := match impl'.val with + | .Block stmts none => ⟨.Block stmts (some returnLabel), impl'.source⟩ + | _ => mkMd (.Block [impl'] (some returnLabel)) + { proc with body := .Opaque postconds (some wrapped) mods } + | .Transparent body => + let body' := replaceReturn proc.outputs body + let wrapped := match body'.val with + | .Block stmts none => ⟨.Block stmts (some returnLabel), body'.source⟩ + | _ => mkMd (.Block [body'] (some returnLabel)) + { proc with body := .Transparent wrapped } + | _ => proc +where + + mkMd (e : StmtExpr) : StmtExprMd := { val := e, source := proc.name.source } + mkVarMd (v : Variable) : VariableMd := { val := v, source := proc.name.source } + + /-- Replace `Return val` with `output := val; exit "$return"` (or just `exit` + for valueless returns). Uses `mapStmtExpr` for bottom-up traversal. -/ + replaceReturn (outputs : List Parameter) (expr : StmtExprMd) : StmtExprMd := + mapStmtExpr (fun e => + match e.val with + | .Return (some val) => + match outputs with + | [out] => + let assign := mkMd (.Assign [mkVarMd (.Local out.name)] val) + let exit := mkMd (.Exit returnLabel) + ⟨.Block [assign, exit] none, e.source⟩ + | _ => mkMd (.Exit returnLabel) + | .Return none => mkMd (.Exit returnLabel) + | _ => e) expr + +/-- Transform a program by eliminating return statements in all procedure bodies. -/ +def eliminateReturnStatements (program : Program) : Program := + { program with staticProcedures := program.staticProcedures.map eliminateReturnStmts } + +end -- public section + +end Strata.Laurel diff --git a/Strata/Languages/Laurel/EliminateReturnsInExpression.lean b/Strata/Languages/Laurel/EliminateReturnsInExpression.lean index b400d3690f..b11fc580b6 100644 --- a/Strata/Languages/Laurel/EliminateReturnsInExpression.lean +++ b/Strata/Languages/Laurel/EliminateReturnsInExpression.lean @@ -11,101 +11,158 @@ import Strata.Util.Tactics /-! # Eliminate Returns in Expression Position -Rewrites functional procedure bodies so that `return` statements are removed -and early-return guard patterns become if-then-else expressions. This makes -the body a pure expression tree suitable for translation to a Core function. +Rewrites functional procedure bodies if possible, so they only get a single return on the outside of the body. +This depends on three transformations -The algorithm walks a block backwards (from last statement to first), -accumulating a result expression: +1) +``` +if then + -- The last statement is converted via `lastStmtToExpr` which strips `return`, - recurses into blocks, and handles if-then-else. -- Each preceding statement wraps around the accumulated result via `stmtsToExpr`: - - `if (cond) { body }` (no else) becomes `if cond then lastStmtToExpr(body) else acc` - - Other statements are kept in a two-element block with the accumulator. + +``` + +is converted into +``` +return if then else +``` + +Where stripped_ indicates the return has been stripped from + +2) +``` +if then else +``` + +is turned into +``` +return if then else +``` + +3) +``` +var x := + +``` + +is turned into + +``` +return + var x := + +``` -/ namespace Strata.Laurel -/-- Appending a singleton strictly increases `sizeOf`. -/ -private theorem List.sizeOf_lt_append_singleton [SizeOf α] (xs : List α) (y : α) : - sizeOf xs < sizeOf (xs ++ [y]) := by - induction xs with - | nil => simp_all; omega - | cons hd tl ih => simp_all [List.cons_append] - -/-- `dropLast` of a non-empty list has strictly smaller `sizeOf`. -/ -private theorem List.sizeOf_dropLast_lt [SizeOf α] {l : List α} (h_ne : l ≠ []) : - sizeOf l.dropLast < sizeOf l := by - have h_concat := List.dropLast_concat_getLast h_ne - have : sizeOf l = sizeOf (l.dropLast ++ [l.getLast h_ne]) := by rw [h_concat] - rw [this] - exact List.sizeOf_lt_append_singleton l.dropLast (l.getLast h_ne) +/-- Check whether a statement is "returning": all control-flow paths end with a `Return`. -/ +partial def isReturning (stmt : StmtExprMd) : Bool := + match stmt.val with + | .Return _ => true + | .Block stmts _ => + match stmts.getLast? with + | some last => isReturning last + | none => false + | .IfThenElse _ thenBr (some elseBr) => isReturning thenBr && isReturning elseBr + | .IfThenElse _ thenBr none => isReturning thenBr + | _ => false + +/-- Strip the outermost `Return` wrapper from a returning statement, yielding the expression. + Recurses into blocks (transforming the last statement) and if-then-else (transforming branches). -/ +partial def stripReturn (stmt : StmtExprMd) : StmtExprMd := + match stmt.val with + | .Return (some val) => val + | .Return none => ⟨.LiteralBool true, stmt.source⟩ + | .Block stmts label => + match stmts.dropLast, stmts.getLast? with + | init, some last => + let last' := stripReturn last + if init.isEmpty then last' + else ⟨.Block (init ++ [last']) label, stmt.source⟩ + | _, none => stmt + | .IfThenElse cond thenBr (some elseBr) => + ⟨.IfThenElse cond (stripReturn thenBr) (some (stripReturn elseBr)), stmt.source⟩ + | .IfThenElse cond thenBr none => + ⟨.IfThenElse cond (stripReturn thenBr) none, stmt.source⟩ + | _ => stmt mutual -/-- -Fold a list of preceding statements (right-to-left) around an accumulator -expression. Each `if-then` (no else) guard wraps as -`if cond then lastStmtToExpr(body) else acc`; other statements produce -`Block [stmt, acc]`. --/ -def stmtsToExpr (stmts : List StmtExprMd) (acc : StmtExprMd) - : StmtExprMd := +/-- Transform a block's statement list by folding guard-return patterns into if-then-else. + Scans left-to-right for `if cond then ` (no else) where the remaining + statements are also returning, and rewrites into `return if cond then ... else ...`. -/ +partial def transformStmtList (stmts : List StmtExprMd) : List StmtExprMd := match stmts with - | [] => acc - | s :: rest => - let acc' := stmtsToExpr rest acc - match s with - | ⟨.IfThenElse cond thenBr none, ssrc⟩ => - ⟨.IfThenElse cond (lastStmtToExpr thenBr) (some acc'), ssrc⟩ + | [] => [] + | [single] => [transformStmt single] + | stmt :: rest => + match stmt.val with + | .IfThenElse cond thenBr none => + if isReturning thenBr then + let rest' := transformStmtList rest + let restExpr : StmtExprMd := match rest' with + | [single] => single + | many => ⟨.Block many none, stmt.source⟩ + if isReturning restExpr then + let thenExpr := stripReturn (transformStmt thenBr) + let elseExpr := stripReturn restExpr + [⟨.Return (some ⟨.IfThenElse cond thenExpr (some elseExpr), stmt.source⟩), stmt.source⟩] + else + transformStmt stmt :: transformStmtList rest + else + transformStmt stmt :: transformStmtList rest + | .IfThenElse cond thenBr (some elseBr) => + if isReturning thenBr && isReturning elseBr then + let thenExpr := stripReturn (transformStmt thenBr) + let elseExpr := stripReturn (transformStmt elseBr) + let ite := ⟨.Return (some ⟨.IfThenElse cond thenExpr (some elseExpr), stmt.source⟩), stmt.source⟩ + ite :: transformStmtList rest + else + transformStmt stmt :: transformStmtList rest + | .Assign [⟨.Declare _, _⟩] _ => + -- Case 3: var x := expr; → return { var x := expr; } + let rest' := transformStmtList rest + let restExpr : StmtExprMd := match rest' with + | [single] => single + | many => ⟨.Block many none, stmt.source⟩ + if isReturning restExpr then + let stripped := stripReturn restExpr + [⟨.Return (some ⟨.Block [stmt, stripped] none, stmt.source⟩), stmt.source⟩] + else + stmt :: transformStmtList rest | _ => - { val := .Block [s, acc'] none, source := none } - termination_by (sizeOf stmts, 1) - -/-- -Convert the last statement of a block into an expression. -- `return expr` → `expr` -- A non-empty block → process last element, fold preceding statements -- `if cond then A else B` → recurse into both branches -- Anything else → kept as-is --/ -def lastStmtToExpr (stmt : StmtExprMd) : StmtExprMd := - match stmt with - | ⟨.Return (some val), _⟩ => val - | ⟨.Block stmts _, _⟩ => - match h_last : stmts.getLast? with - | some last => - have := List.mem_of_getLast? h_last - let lastExpr := lastStmtToExpr last - let dropped := stmts.dropLast - have h : sizeOf stmts.dropLast < sizeOf stmts := - List.sizeOf_dropLast_lt (by intro h; simp [h] at h_last) - stmtsToExpr dropped lastExpr - | none => stmt - | ⟨.IfThenElse cond thenBr (some elseBr), source⟩ => - ⟨.IfThenElse cond (lastStmtToExpr thenBr) (some (lastStmtToExpr elseBr)), source⟩ + transformStmt stmt :: transformStmtList rest + +/-- Recurse into a statement, applying the guard-return rewrite inside blocks and branches. -/ +partial def transformStmt (stmt : StmtExprMd) : StmtExprMd := + match stmt.val with + | .Block stmts label => + ⟨.Block (transformStmtList stmts) label, stmt.source⟩ + | .IfThenElse cond thenBr (some elseBr) => + if isReturning thenBr && isReturning elseBr then + let thenExpr := stripReturn (transformStmt thenBr) + let elseExpr := stripReturn (transformStmt elseBr) + ⟨.Return (some ⟨.IfThenElse cond thenExpr (some elseExpr), stmt.source⟩), stmt.source⟩ + else + ⟨.IfThenElse cond (transformStmt thenBr) (some (transformStmt elseBr)), stmt.source⟩ + | .IfThenElse cond thenBr none => + ⟨.IfThenElse cond (transformStmt thenBr) none, stmt.source⟩ + | .While cond invs dec body => + ⟨.While cond invs dec (transformStmt body), stmt.source⟩ | _ => stmt - termination_by (sizeOf stmt, 0) - decreasing_by - all_goals (simp_all; term_by_mem) end -/-- -Apply return elimination to a functional procedure's body. -The entire body is treated as an expression to be converted. --/ -def eliminateReturnsInExpression (proc : Procedure) : Procedure := - if !proc.isFunctional then proc - else - match proc.body with - | .Transparent bodyExpr => - { proc with body := .Transparent (lastStmtToExpr bodyExpr) } - | .Opaque postconds (some impl) modif => - { proc with body := .Opaque postconds (some (lastStmtToExpr impl)) modif } - | _ => proc +/-- Transform a single procedure by applying the guard-return elimination to its body. -/ +private def eliminateReturnsInExpression (proc : Procedure) : Procedure := + match proc.body with + | .Transparent body => + { proc with body := .Transparent (transformStmt body) } + | .Opaque postconds (some impl) mods => + { proc with body := .Opaque postconds (some (transformStmt impl)) mods } + | _ => proc public section diff --git a/Strata/Languages/Laurel/EliminateValueReturns.lean b/Strata/Languages/Laurel/EliminateValuesInReturns.lean similarity index 70% rename from Strata/Languages/Laurel/EliminateValueReturns.lean rename to Strata/Languages/Laurel/EliminateValuesInReturns.lean index f465c6055c..3be9357613 100644 --- a/Strata/Languages/Laurel/EliminateValueReturns.lean +++ b/Strata/Languages/Laurel/EliminateValuesInReturns.lean @@ -8,7 +8,7 @@ module public import Strata.Languages.Laurel.MapStmtExpr /-! -# Eliminate Value Returns +# Eliminate Values In Returns Rewrites `return expr` into `outParam := expr; return` for imperative (non-functional) procedures that have an output parameter. This decouples @@ -20,18 +20,19 @@ The pass is a Laurel-to-Laurel rewrite that runs before Core translation. namespace Strata.Laurel -/-- Rewrite a single `Return (some value)` node into - `Block [Assign [Identifier outParam] value, Return none]`. - Recursion into children is handled by `mapStmtExpr`. -/ -private def eliminateValueReturnNode (outParam : Identifier) (stmt : StmtExprMd) : StmtExprMd := +/-- Rewrite a single `Return (some value)` node into the list + `[Assign outParam value, Return none]`. + When used with `mapStmtExprFlattenM`, these statements are flattened + into the enclosing block rather than wrapped in a nested block. -/ +private def eliminateValueReturnNode (outParam : Identifier) (stmt : StmtExprMd) : Id (List StmtExprMd) := match stmt.val with | .Return (some value) => -- Synthesized nodes use default metadata since no diagnostics should be reported on them - let target : VariableMd := { val := .Local outParam, source := none } - let assign : StmtExprMd := { val := .Assign [target] value, source := none } + let target : VariableMd := { val := .Local outParam, source := stmt.source } + let assign : StmtExprMd := { val := .Assign [target] value, source := stmt.source } let ret : StmtExprMd := { val := .Return none, source := stmt.source } - { val := .Block [assign, ret] none, source := none } - | _ => stmt + [assign, ret] + | _ => [stmt] /-- Check whether a statement tree contains any `Return (some _)`. -/ def hasValuedReturn (stmt : StmtExprMd) : Bool := @@ -53,11 +54,19 @@ def hasValuedReturn (stmt : StmtExprMd) : Bool := /-- Apply value-return elimination to a single procedure. Only applies to non-functional procedures with exactly one output parameter. Emits an error if a valued return is used with multiple output parameters. -/ -def eliminateValueReturnsInProc (proc : Procedure) : Procedure × Array DiagnosticModel := - if proc.isFunctional then (proc, #[]) - else match proc.outputs with +def eliminateValuesInReturnsInProc (proc : Procedure) : Procedure × Array DiagnosticModel := + match proc.outputs with | [outParam] => - let rewrite := mapStmtExpr (eliminateValueReturnNode outParam.name) + let pre (stmt : StmtExprMd) : Id (Option (List StmtExprMd)) := + match stmt.val with + | .Return (some value) => + let target : VariableMd := { val := .Local outParam.name, source := stmt.source } + let assign : StmtExprMd := { val := .Assign [target] value, source := stmt.source } + let ret : StmtExprMd := { val := .Return none, source := stmt.source } + some [assign, ret] + | _ => none + let post (stmt : StmtExprMd) : Id (List StmtExprMd) := pure [stmt] + let rewrite := mapStmtExprFlattenM (m := Id) pre post match proc.body with | .Transparent body => ({ proc with body := .Transparent (rewrite body) }, #[]) @@ -80,9 +89,9 @@ def eliminateValueReturnsInProc (proc : Procedure) : Procedure × Array Diagnost public section /-- Transform a program by eliminating value returns in all imperative procedures. -/ -def eliminateValueReturnsTransform (program : Program) : Program × Array DiagnosticModel := +def eliminateValuesInReturnsTransform (program : Program) : Program × Array DiagnosticModel := let (procs, diags) := program.staticProcedures.foldl (fun (ps, ds) proc => - let (proc', procDiags) := eliminateValueReturnsInProc proc + let (proc', procDiags) := eliminateValuesInReturnsInProc proc (proc' :: ps, ds ++ procDiags) ) ([], #[]) ({ program with staticProcedures := procs.reverse }, diags) diff --git a/Strata/Languages/Laurel/FilterPrelude.lean b/Strata/Languages/Laurel/FilterPrelude.lean index 06534dbef0..7b11f3f47f 100644 --- a/Strata/Languages/Laurel/FilterPrelude.lean +++ b/Strata/Languages/Laurel/FilterPrelude.lean @@ -216,8 +216,6 @@ private def buildDependencyMap (prog : Laurel.Program) for c in dt.constructors do insertNew c.name.text (deps.insert name) s!"constructor '{c.name.text}' of datatype '{name}'" - insertNew (dt.testerName c) (deps.insert name) - s!"tester '{dt.testerName c}' of datatype '{name}'" for a in c.args do insertNew (dt.destructorName a) (deps.insert name) s!"destructor '{dt.destructorName a}'" diff --git a/Strata/Languages/Laurel/Grammar/AbstractToConcreteTreeTranslator.lean b/Strata/Languages/Laurel/Grammar/AbstractToConcreteTreeTranslator.lean index ecc23beda1..8eff076d8b 100644 --- a/Strata/Languages/Laurel/Grammar/AbstractToConcreteTreeTranslator.lean +++ b/Strata/Languages/Laurel/Grammar/AbstractToConcreteTreeTranslator.lean @@ -240,7 +240,14 @@ private def procedureToOp (proc : Procedure) : Strata.Operation := laurelOp "invokeOnClause" #[stmtExprToArg e]) let (opaqueSpecArg, bodyArg) := match proc.body with | .Transparent body => - (optionArg none, optionArg (some (laurelOp "body" #[stmtExprToArg body]))) + -- For functions, the body is implicitly wrapped in a Return by ConcreteToAbstract; + -- unwrap it here so the concrete output doesn't show an explicit `return`. + let emitBody := if proc.isFunctional then + match body.val with + | .Return (some inner) => inner + | _ => body + else body + (optionArg none, optionArg (some (laurelOp "body" #[stmtExprToArg emitBody]))) | .Opaque postconds impl modifies => let ens := postconds.map ensuresClauseToArg |>.toArray let mods := if modifies.isEmpty then #[] else modifiesClausesToArgs modifies diff --git a/Strata/Languages/Laurel/Grammar/ConcreteToAbstractTreeTranslator.lean b/Strata/Languages/Laurel/Grammar/ConcreteToAbstractTreeTranslator.lean index cf8545d95d..a01a7ad5d4 100644 --- a/Strata/Languages/Laurel/Grammar/ConcreteToAbstractTreeTranslator.lean +++ b/Strata/Languages/Laurel/Grammar/ConcreteToAbstractTreeTranslator.lean @@ -520,6 +520,11 @@ def parseProcedure (arg : Arg) : TransM Procedure := do | _, _ => TransM.error s!"Expected body or externalBody operation, got {repr bodyOp.name}" | .option _ none => pure none | _ => TransM.error s!"Expected body, got {repr bodyArg}" + -- For functions, wrap the body in a Return so the last expression + -- is treated as the return value by downstream passes. + let body := if op.name == q`Laurel.function then + body.map fun b => ⟨.Return (some b), b.source⟩ + else body -- Determine procedure body kind let procBody := if isExternal then Body.External diff --git a/Strata/Languages/Laurel/Grammar/LaurelGrammar.lean b/Strata/Languages/Laurel/Grammar/LaurelGrammar.lean index c185d90edc..e4a061b934 100644 --- a/Strata/Languages/Laurel/Grammar/LaurelGrammar.lean +++ b/Strata/Languages/Laurel/Grammar/LaurelGrammar.lean @@ -9,7 +9,7 @@ module -- Laurel dialect definition, loaded from LaurelGrammar.st -- NOTE: Changes to LaurelGrammar.st are not automatically tracked by the build system. -- Update this file (e.g. this comment) to trigger a recompile after modifying LaurelGrammar.st. --- Last grammar change: block format uses indent(2) with leading spaces for vertical layout. +-- Last grammar change: ifThenElse prints then/else branches on new lines. public import Strata.DDM.Integration.Lean public meta import Strata.DDM.Integration.Lean diff --git a/Strata/Languages/Laurel/Grammar/LaurelGrammar.st b/Strata/Languages/Laurel/Grammar/LaurelGrammar.st index 5ddc8609f5..b6166444ad 100644 --- a/Strata/Languages/Laurel/Grammar/LaurelGrammar.st +++ b/Strata/Languages/Laurel/Grammar/LaurelGrammar.st @@ -98,10 +98,10 @@ op errorSummary(msg: Str): ErrorSummary => " summary " msg; // If-else category ElseBranch; -op elseBranch(stmts : StmtExpr) : ElseBranch => @[prec(0)] " else " stmts; +op elseBranch(stmts : StmtExpr) : ElseBranch => @[prec(0)] "\nelse " stmts; op ifThenElse (cond: StmtExpr, thenBranch: StmtExpr, elseBranch: Option ElseBranch): StmtExpr => - @[prec(20)] "if " cond " then " thenBranch:0 elseBranch:0; + @[prec(20)] "if " cond "\nthen " thenBranch:0 elseBranch:0; op assert (cond : StmtExpr, errorMessage: Option ErrorSummary) : StmtExpr => @[prec(0)] "assert " cond:0 errorMessage:0; op assume (cond : StmtExpr) : StmtExpr => @[prec(0)] "assume " cond:0; diff --git a/Strata/Languages/Laurel/HeapParameterization.lean b/Strata/Languages/Laurel/HeapParameterization.lean index 0243113aed..4c83d2c9f8 100644 --- a/Strata/Languages/Laurel/HeapParameterization.lean +++ b/Strata/Languages/Laurel/HeapParameterization.lean @@ -566,10 +566,17 @@ def heapParameterization (model: SemanticModel) (program : Program) : Program := ([], state1) -- Generate Box datatype from all constructors used during transformation let boxDatatype : TypeDefinition := - .Datatype { name := "Box", typeArgs := [], constructors := state2.usedBoxConstructors } + .Datatype { + name := "Box", typeArgs := [], constructors := state2.usedBoxConstructors } + + let types := fieldDatatype :: boxDatatype :: heapConstants.types ++ + -- The filter is a hack to deal with another hack, + -- the box that was added in CoreDefinitionsForLaurel.lean + -- because Laurel does not support polymorphism yet + types'.filter (fun td => td.name.text != "Box") { program with staticProcedures := heapConstants.staticProcedures ++ procs', - types := fieldDatatype :: boxDatatype :: heapConstants.types ++ types' } + types } end Strata.Laurel diff --git a/Strata/Languages/Laurel/InlineLocalVariablesInExpressions.lean b/Strata/Languages/Laurel/InlineLocalVariablesInExpressions.lean new file mode 100644 index 0000000000..65c2787286 --- /dev/null +++ b/Strata/Languages/Laurel/InlineLocalVariablesInExpressions.lean @@ -0,0 +1,83 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ +module + +public import Strata.Languages.Laurel.MapStmtExpr +public import Strata.Languages.Laurel.TransparencyPass +import Strata.Util.Tactics + +/-! +# Inline Local Variables in Expression Position + +Replaces local variable declarations in functional procedure bodies with +direct substitution of the initializer into the remaining statements of +the block. This eliminates `LocalVariable` nodes from expression contexts +so the Core translator does not need to handle let-bindings in expressions. + +Example: +``` +function f() returns (r: int) { + var x: int := 1; + var y: int := x + 1; + y +} +``` +becomes: +``` +function f() returns (r: int) { + 0 + 1 +} +``` +-/ + +namespace Strata.Laurel + +public section + +/-- Substitute all occurrences of local variable `name` with `replacement` in `expr`. -/ +private def substIdentifier (name : Identifier) (replacement : StmtExprMd) (expr : StmtExprMd) + : StmtExprMd := + mapStmtExpr (fun e => + match e.val with + | .Var (.Local n) => if n == name then replacement else e + | _ => e) expr + +/-- Inline initialized local variables in a block, substituting their + initializers into the remaining statements. Non-Assign/Declare + statements are kept as-is. -/ +private def inlineLocalsInStmts (stmts : List StmtExprMd) : List StmtExprMd := + match stmts with + | [] => [] + | ⟨.Assign [⟨.Declare parameter, _⟩] initializer, _⟩ :: rest => + let rest' := rest.map (substIdentifier parameter.name initializer) + inlineLocalsInStmts rest' + | s :: rest => s :: inlineLocalsInStmts rest +termination_by stmts.length + +/-- Rewrite a single node: if it is a Block, inline any LocalVariable + declarations. Recursion into children is handled by `mapStmtExpr`. -/ +private def inlineLocalsNode (expr : StmtExprMd) : StmtExprMd := + match expr.val with + | .Block stmts label => + let stmts' := inlineLocalsInStmts stmts + match stmts', label with + | [single], none => single + | _, _ => ⟨.Block stmts' label, expr.source⟩ + | _ => expr + +/-- Apply local-variable inlining to all functional procedure bodies. -/ +def inlineLocalVariablesInExpressions (program : UnorderedCoreWithLaurelTypes) : UnorderedCoreWithLaurelTypes := + { program with functions := program.functions.map fun proc => + match proc.body with + | .Transparent body => + { proc with body := .Transparent (mapStmtExpr inlineLocalsNode body) } + | .Opaque postconds (some impl) modif => + { proc with body := .Opaque postconds (some (mapStmtExpr inlineLocalsNode impl)) modif } + | _ => proc + } + +end -- public section +end Strata.Laurel diff --git a/Strata/Languages/Laurel/Laurel.lean b/Strata/Languages/Laurel/Laurel.lean index 1a851785c9..4e96774dc2 100644 --- a/Strata/Languages/Laurel/Laurel.lean +++ b/Strata/Languages/Laurel/Laurel.lean @@ -200,6 +200,9 @@ structure Procedure : Type where whose body is the ensures clause universally quantified over the procedure's inputs, with this expression as the SMT trigger. -/ invokeOn : Option (AstNode StmtExpr) := none + /-- Axioms to emit alongside this procedure. Populated by the contract pass from + `invokeOn` and ensures clauses. -/ + axioms : List (AstNode StmtExpr) := [] /-- A typed parameter for a procedure. diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index 2e84bd7902..9b0d8e4572 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -6,12 +6,15 @@ module public import Strata.Languages.Laurel.LaurelToCoreTranslator +import Strata.Languages.Laurel.DatatypeTesters import Strata.Languages.Laurel.DesugarShortCircuit import Strata.Languages.Laurel.EliminateReturnsInExpression - -import Strata.Languages.Laurel.EliminateValueReturns +import Strata.Languages.Laurel.EliminateReturnStatements +import Strata.Languages.Laurel.EliminateValuesInReturns +import Strata.Languages.Laurel.InlineLocalVariablesInExpressions import Strata.Languages.Laurel.ConstrainedTypeElim - +import Strata.Languages.Laurel.ContractPass +import Strata.Languages.Laurel.EliminateMultipleOutputs import Strata.Languages.Laurel.TypeAliasElim import Strata.Languages.Core.Verifier import Strata.Util.Statistics @@ -25,7 +28,7 @@ to Strata Core. The pipeline is: 1. Prepend core definitions for Laurel. 2. Run a sequence of Laurel-to-Laurel lowering passes (resolution, heap parameterization, type hierarchy, modifies clauses, hole inference, - desugaring, lifting, constrained type elimination). + desugaring, lifting, constrained type elimination, contract pass). 3. Run the transparency pass to produce an `UnorderedCoreWithLaurelTypes`. 4. Group and order declarations into a `CoreWithLaurelTypes`. 5. Translate the `CoreWithLaurelTypes` to a `Core.Program`. @@ -96,9 +99,13 @@ private def laurelPipeline : Array LaurelPass := #[ run := fun p m => let (p', diags) := filterNonCompositeModifies m p (p', diags, {}) }, - { name := "EliminateValueReturns" + { name := "EliminateReturnsInExpressions" + needsResolves := true run := fun p _m => - let (p', diags) := eliminateValueReturnsTransform p + (eliminateReturnsInExpressionTransform p, [], {}) }, + { name := "EliminateValuesInReturns" + run := fun p _m => + let (p', diags) := eliminateValuesInReturnsTransform p (p', diags.toList, {}) }, { name := "HeapParameterization" needsResolves := true @@ -122,20 +129,26 @@ private def laurelPipeline : Array LaurelPass := #[ let (p', stats) := eliminateHoles p (p', [], stats) }, { name := "DesugarShortCircuit" - run := fun p m => - (desugarShortCircuit m p, [], {}) }, - { name := "LiftExpressionAssignments" - run := fun p m => - (liftExpressionAssignments m p, [], {}) }, - { name := "EliminateReturns" - needsResolves := true - run := fun p _m => - (eliminateReturnsInExpressionTransform p, [], {}) }, + run := fun p _ => + (desugarShortCircuit p, [], {}) }, + -- { name := "LiftExpressionAssignments" + -- run := fun p m => + -- (liftExpressionAssignments p m [], [], {}) }, { name := "ConstrainedTypeElim" needsResolves := true run := fun p m => let (p', diags) := constrainedTypeElim m p - (p', diags, {}) } + (p', diags, {}) }, + { name := "EliminateReturnStatements" + needsResolves := false + run := fun p _ => + let (p') := eliminateReturnStatements p + (p', [], {}) }, + { name := "ContractPass" + needsResolves := true + run := fun p _ => + let (p') := contractPass p + (p', [], {}) } ] /-- @@ -154,6 +167,9 @@ private def runLaurelPasses (options : LaurelTranslateOptions) types := coreDefinitionsForLaurel.types ++ program.types } + -- Generate external tester functions for datatype constructors + let program := generateDatatypeTesters program + -- Step 0: the input program before any passes emit "Initial" "laurel.st" program @@ -184,13 +200,61 @@ private def runLaurelPasses (options : LaurelTranslateOptions) let result := resolve program (some model) let newErrors := result.errors.filter fun e => !resolutionErrors.contains e if !newErrors.isEmpty then + let newDiags := newErrors.toList.map fun d => + { d with + message := + s!"Internal error: resolution after '{pass.name}' introduced this diagnostic: {d.message}" + type := .StrataBug } emit pass.name "laurel.st" program + return (program, model, allDiags ++ newDiags, allStats) program := result.program model := result.model emit pass.name "laurel.st" program return (program, model, allDiags, allStats) +/-- +Apply `liftExpressionAssignments` to the core (non-functional) procedures in an +`UnorderedCoreWithLaurelTypes`. Only procedures whose names appear in the core +procedure list are transformed; functions are left unchanged. +-/ +def liftImperativeExpressionsInCore (uc : UnorderedCoreWithLaurelTypes) + (model : SemanticModel) : UnorderedCoreWithLaurelTypes := + let imperativeCallees := uc.coreProcedures.map (·.name.text) + if imperativeCallees.isEmpty then uc + else + let liftedProgram := liftExpressionAssignments + { staticProcedures := uc.coreProcedures, staticFields := [], types := [], constants := [] } + model imperativeCallees + let liftedProcs := liftedProgram.staticProcedures + { uc with + functions := uc.functions + coreProcedures := liftedProcs + } + +/-- A single pass on the unordered Core representation. Each pass receives the + current `UnorderedCoreWithLaurelTypes` and the semantic model and returns + the (possibly modified) program. -/ +structure CorePass where + /-- Human-readable name, used for profiling and file emission. -/ + name : String + /-- Whether `resolveUnorderedCore` should be run after the pass. -/ + needsResolves : Bool := false + /-- The pass action. -/ + run : UnorderedCoreWithLaurelTypes → SemanticModel → UnorderedCoreWithLaurelTypes + +/-- The ordered sequence of passes on the unordered Core representation. -/ +private def corePipeline : Array CorePass := #[ + { name := "EliminateMultipleOutputs" + run := fun uc _m => eliminateMultipleOutputs uc }, + { name := "InlineLocalVariablesInExpressions" + needsResolves := true + run := fun uc _m => inlineLocalVariablesInExpressions uc }, + { name := "LiftImperativeExpressionsInCore" + needsResolves := true + run := fun uc m => liftImperativeExpressionsInCore uc m } +] + /-- Translate Laurel Program to Core Program, also returning the lowered Laurel program. @@ -204,32 +268,48 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) | some ctx => pure ctx | none => Strata.Pipeline.PipelineContext.create (outputMode := .quiet) runPipelineM options.keepAllFilesPrefix do - let (program, model, passDiags, stats) ← runLaurelPasses options pctx program - let unorderedCore := transparencyPass program - emit "transparencyPass" "core.st" unorderedCore - - -- Resolve so that identifiers introduced by earlier passes get uniqueIds. - let compositeTypes := program.types.filter (fun t => match t with | .Composite _ => true | _ => false) - let (unorderedCore, model) := resolveUnorderedCore unorderedCore (existingModel := some model) (additionalTypes := compositeTypes) + let (program, model, passDiags, stats) ← runLaurelPasses options pctx program + if ! passDiags.isEmpty then + return (none, passDiags, program, stats) - let coreWithLaurelTypes := orderFunctionsAndProcedures unorderedCore + let unorderedCore := transparencyPass program + emit "transparencyPass" "core.st" unorderedCore + let mut unorderedCore := unorderedCore + let mut fnModel := model - -- This early return is a simple way to protect against duplicative errors. Without this return, - -- resolution errors reported by Laurel would also be reported by Core. - -- There might be better solution that allows getting some resolution errors from Laurel and some verification errors from Core, - -- but that would need more consideration. - if passDiags.any (·.type != .Warning) then - return (none, passDiags, program, stats) - - emit "CoreWithLaurelTypes" "core.st" coreWithLaurelTypes - let initState : TranslateState := { model := model, overflowChecks := options.overflowChecks } + for pass in corePipeline do + unorderedCore := pass.run unorderedCore fnModel + if pass.needsResolves then + let compositeTypes := program.types.filter (fun t => match t with | .Composite _ => true | _ => false) + let (uc', m', errors) := resolveUnorderedCore unorderedCore (some fnModel) compositeTypes + if !errors.isEmpty then + let newDiags := errors.toList.map fun d => + { d with message := + s!"Internal error: resolution after '{pass.name}' introduced this diagnostic: {d.message}" } + emit pass.name "core.st" unorderedCore + return (none, passDiags ++ newDiags, program, stats) + unorderedCore := uc' + fnModel := m' + emit pass.name "unorderedCoreWithLaurelTypes.st" unorderedCore + + let coreWithLaurelTypes := orderFunctionsAndProcedures unorderedCore + -- This early return is a simple way to protect against duplicative errors. Without this return, + -- resolution errors reported by Laurel would also be reported by Core. + -- There might be better solution that allows getting some resolution errors from Laurel and some verification errors from Core, + -- but that would need more consideration. + if ! passDiags.isEmpty then + return (none, passDiags, program, stats) + else + emit "CoreWithLaurelTypes" "core.st" coreWithLaurelTypes + let initState : TranslateState := { model := fnModel, overflowChecks := options.overflowChecks } let (coreProgramOption, translateState) := runTranslateM initState (translateLaurelToCore options program coreWithLaurelTypes) - -- Because of the duplication between functions and procedures, this translation is liable to create duplicate diagnostics + -- Because of the duplication between functions and proofs, this translation is liable to create duplicate diagnostics -- User errors should be checked in an earlier phase, and all dumb translation errors are Strata bugs - let mut allDiagnostics: List DiagnosticModel := passDiags ++ translateState.diagnostics.eraseDups; - + let mut allDiagnostics := translateState.diagnostics.eraseDups if translateState.coreDiagnostics.length > 0 && allDiagnostics.isEmpty then + -- The program was suppressed but no diagnostics explain why — report the core diagnostics + -- that have a known source location (those without one are not actionable for the user). allDiagnostics := allDiagnostics ++ translateState.coreDiagnostics if coreProgramOption.isSome then diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index 4209435b01..90fa4f9756 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -16,7 +16,7 @@ import Strata.Languages.Laurel.DesugarShortCircuit public import Strata.Languages.Laurel.InferHoleTypes public import Strata.Languages.Laurel.EliminateHoles import Strata.Languages.Laurel.EliminateReturnsInExpression -import Strata.Languages.Laurel.EliminateValueReturns +import Strata.Languages.Laurel.EliminateValuesInReturns public import Strata.Languages.Laurel.HeapParameterization public import Strata.Languages.Laurel.TypeHierarchy public import Strata.Languages.Laurel.LaurelTypes @@ -68,6 +68,11 @@ structure TranslateState where why the program was deemed invalid so that if no other diagnostics explain the suppression, these can be surfaced to the user. -/ coreDiagnostics : List DiagnosticModel := [] + /-- When `true`, use safe division (`intSafeDivOp`) and safe datatype selectors + (with preconditions). When `false`, use unsafe division (`intDivOp`) and + unsafe datatype selectors (without preconditions). + Set to `true` for proof procedures and `false` for functions. -/ + proof : Bool := false /-- The translation monad: state over Except, allowing both accumulated diagnostics and hard failures -/ @[expose] abbrev TranslateM := OptionT (StateM TranslateState) @@ -76,6 +81,25 @@ structure TranslateState where def emitDiagnostic (d : DiagnosticModel) : TranslateM Unit := modify fun s => { s with diagnostics := s.diagnostics ++ [d] } +/-- Adjust a datatype selector (destructor) name based on the `proof` flag. + Destructor names contain `..` (e.g. `IntList..head`, `IntList..head!`). + Tester names also contain `..` but start with `is` after the separator. + - `proof = true` → use safe selectors (strip `!` suffix) + - `proof = false` → use unsafe selectors (add `!` suffix) -/ +private def adjustSelectorName (name : String) (proof : Bool) : String := + -- Only adjust destructor names (contain ".." but are not testers) + match name.splitOn ".." with + | [_, suffix] => + if suffix.startsWith "is" then name -- tester, leave unchanged + else if proof then + name + -- Safe: strip trailing "!" + -- if name.endsWith "!" then (name.dropEnd 1).toString else name + else + -- Unsafe: add trailing "!" if not already present + if name.endsWith "!" then name else name ++ "!" + | _ => name -- not a destructor name, leave unchanged + private def invalidCoreType (source : Option FileRange) (reason : String) : TranslateM LMonoTy := do modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ [diagnosticFromSource source reason DiagnosticType.StrataBug] } @@ -155,11 +179,9 @@ def translateExpr (expr : StmtExprMd) let s ← get let model := s.model let md := astNodeToCoreMd expr + let proof := (← get).proof let disallowed (source : Option FileRange) (msg : String) : TranslateM Core.Expression.Expr := do - if isPureContext then throwExprDiagnostic $ diagnosticFromSource source msg - else - throwExprDiagnostic $ diagnosticFromSource source s!"{msg} (should have been lifted)" DiagnosticType.StrataBug match h: expr.val with | .LiteralBool b => return .const () (.boolConst b) @@ -241,7 +263,8 @@ def translateExpr (expr : StmtExprMd) if isPureContext && !model.isFunction callee then disallowed expr.source s!"calls to procedures are not supported in functions or contracts" else - let fnOp : Core.Expression.Expr := .op () ⟨callee.text, ()⟩ none + let calleeName := adjustSelectorName callee.text (← get).proof + let fnOp : Core.Expression.Expr := .op () ⟨calleeName, ()⟩ none args.attach.foldlM (fun acc ⟨arg, _⟩ => do let re ← translateExpr arg boundVars isPureContext return .app () acc re) fnOp @@ -289,9 +312,6 @@ def translateExpr (expr : StmtExprMd) | .Block (⟨ .IfThenElse cond thenBranch (some elseBranch), innerSrc⟩ :: rest) label => disallowed innerSrc "if-then-else only supported as the last statement in a block" - | .IsType _ _ => - throwExprDiagnostic $ diagnosticFromSource expr.source "IsType should have been lowered" DiagnosticType.StrataBug - | .New _ => throwExprDiagnostic $ diagnosticFromSource expr.source s!"New should have been eliminated by typeHierarchyTransform" DiagnosticType.StrataBug | .Var (.Field target fieldId) => -- Field selects should have been eliminated by heap parameterization -- If we see one here, it's an error in the pipeline @@ -303,7 +323,9 @@ def translateExpr (expr : StmtExprMd) | .Block [] _ => throwExprDiagnostic $ diagnosticFromSource expr.source "empty block expression should have been lowered in a separate pass" DiagnosticType.StrataBug | .Return _ => disallowed expr.source "return expression should be lowered in a separate pass" - + | .IsType _ _ => + throwExprDiagnostic $ diagnosticFromSource expr.source "IsType should have been lowered" DiagnosticType.StrataBug + | .New _ => throwExprDiagnostic $ diagnosticFromSource expr.source s!"New should have been eliminated by typeHierarchyTransform" DiagnosticType.StrataBug | .AsType target _ => throwExprDiagnostic $ diagnosticFromSource expr.source "AsType expression translation" DiagnosticType.NotYetImplemented | .Assigned _ => throwExprDiagnostic $ diagnosticFromSource expr.source "assigned expression translation" DiagnosticType.NotYetImplemented | .Old value => throwExprDiagnostic $ diagnosticFromSource expr.source "old expression translation" DiagnosticType.NotYetImplemented @@ -503,7 +525,7 @@ def translateStmt (stmt : StmtExprMd) | none => return [.exit "$body" md] | some _ => - let d := md.toDiagnostic "Return statement with value should have been eliminated by EliminateValueReturns pass" DiagnosticType.StrataBug + let d := md.toDiagnostic "Return statement with value should have been eliminated by EliminateValuesInReturns pass" DiagnosticType.StrataBug modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ [d] } return [.exit "$body" md] | .While cond invariants decreasesExpr body => @@ -679,10 +701,19 @@ def translateProcedureToFunction (options: LaurelTranslateOptions) (isRecursive: | none => if options.inlineFunctionsWhenPossible then #[.inline] else #[] let body ← match proc.body with - | .Transparent bodyExpr => some <$> translateExpr bodyExpr [] (isPureContext := true) + | .Transparent bodyExpr => + -- Unwrap the pattern produced by EliminateValuesInReturns + EliminateReturnStatements: + -- { result := ; exit "$return" } $return → + let unwrapped := match bodyExpr.val with + | .Block [⟨.Assign [⟨.Local _, _⟩] value, _⟩, ⟨.Exit "$return", _⟩] (some "$return") => value + | _ => bodyExpr + some <$> translateExpr unwrapped [] (isPureContext := true) | .Opaque _ (some bodyExpr) _ => emitDiagnostic (diagnosticFromSource proc.name.source "functions with postconditions are not yet supported") - some <$> translateExpr bodyExpr [] (isPureContext := true) + let unwrapped := match bodyExpr.val with + | .Block [⟨.Assign [⟨.Local _, _⟩] value, _⟩, ⟨.Exit "$return", _⟩] (some "$return") => value + | _ => bodyExpr + some <$> translateExpr unwrapped [] (isPureContext := true) | _ => pure none let f : Core.Function := { name := ⟨proc.name.text, ()⟩ @@ -741,13 +772,11 @@ def translateLaurelToCore (options: LaurelTranslateOptions) (program : Program) return coreFuncs | .procedure proc => do let procDecl ← translateProcedure proc - -- Translate axioms from invokeOn - let invokeOnDecls ← match proc.invokeOn with - | some trigger => do - let axDecl? ← translateInvokeOnAxiom proc trigger - pure axDecl?.toList - | none => pure [] - return [Core.Decl.proc procDecl (identifierToCoreMd proc.name)] ++ invokeOnDecls + -- Translate axioms (populated by the contract pass from invokeOn + ensures) + let axiomDecls ← proc.axioms.mapM fun ax => do + let coreExpr ← translateExpr ax [] (isPureContext := true) + return Core.Decl.ax { name := s!"invokeOn_{proc.name.text}", e := coreExpr } (identifierToCoreMd proc.name) + return [Core.Decl.proc procDecl (identifierToCoreMd proc.name)] ++ axiomDecls | .datatypes dts => do let ldatatypes ← dts.mapM translateDatatypeDefinition return [Core.Decl.type (.data ldatatypes) mdWithUnknownLoc] diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index 4a033cfc2d..498a4a1a7a 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -82,6 +82,8 @@ structure LiftState where condCounter : Nat := 0 /-- All procedures in the program, used to look up return types of imperative calls -/ procedures : List Procedure := [] + /-- Names of callees whose calls should be treated as imperative (lifted) -/ + imperativeCallees : List String := [] @[expose] abbrev LiftM := StateM LiftState @@ -96,7 +98,7 @@ private def freshTempFor (varName : Identifier) : LiftM Identifier := do private def freshCondVar : LiftM Identifier := do let n := (← get).condCounter modify fun s => { s with condCounter := n + 1 } - return s!"$c_{n}" + return s!"$cndtn_{n}" private def prepend (stmt : StmtExprMd) : LiftM Unit := modify fun s => { s with prependedStmts := stmt :: s.prependedStmts } @@ -105,24 +107,16 @@ private def onlyKeepSideEffectStmtsAndLast (stmts : List StmtExprMd) : LiftM (Li match stmts with | [] => return [] | _ => + -- return stmts let last := stmts.getLast! let nonLast ← stmts.dropLast.flatMapM (fun s => match s.val with | .Var (.Declare ..) | .Assign ([⟨.Declare .., _⟩]) _ => do - -- This addPrepend is a hack to work around Core not having let expressions - -- Otherwise we could keep them in the block - prepend s - pure [] - | .Assert _ => do - -- Hack to work around Core not supporting assert expressions - -- Otherwise we could keep them in the block - prepend s - pure [] - | .Assume _ => do - -- Hack to work around Core not supporting assume expressions - -- Otherwise we could keep them in the block - prepend s - pure [] + pure [s] + -- | .Assert _ => do + -- pure [s] + -- | .Assume _ => do + -- pure [s] /- Any other impure StmtExpr, like .Assign, .Exit or .Return, @@ -151,22 +145,53 @@ private def computeType (expr : StmtExprMd) : LiftM HighTypeMd := do let s ← get return computeExprType s.model expr -/-- Check if an expression contains any assignments (recursively). -/ -def containsAssignment (expr : StmtExprMd) : Bool := +/-- Check if an expression contains any assignments or imperative calls (recursively). -/ +def containsAssignmentOrImperativeCall (imperativeCallees : List String) (expr : StmtExprMd) : Bool := match expr with | AstNode.mk val _ => match val with | .Assign .. => true - | .StaticCall _ args => args.attach.any (fun x => containsAssignment x.val) - | .PrimitiveOp _ args => args.attach.any (fun x => containsAssignment x.val) - | .Block stmts _ => stmts.attach.any (fun x => containsAssignment x.val) + | .StaticCall name args1 => + imperativeCallees.contains name.text || + args1.attach.any (fun x => containsAssignmentOrImperativeCall imperativeCallees x.val) + | .PrimitiveOp _ args2 => args2.attach.any (fun x => containsAssignmentOrImperativeCall imperativeCallees x.val) + | .Block stmts _ => stmts.attach.any (fun x => containsAssignmentOrImperativeCall imperativeCallees x.val) | .IfThenElse cond th el => - containsAssignment cond || containsAssignment th || - match el with | some e => containsAssignment e | none => false + containsAssignmentOrImperativeCall imperativeCallees cond || + containsAssignmentOrImperativeCall imperativeCallees th || + match el with | some e => containsAssignmentOrImperativeCall imperativeCallees e | none => false + | .Assume cond => containsAssignmentOrImperativeCall imperativeCallees cond + | .Assert cond => containsAssignmentOrImperativeCall imperativeCallees cond.condition + | .InstanceCall target _ args => + containsAssignmentOrImperativeCall imperativeCallees target || + args.attach.any (fun x => containsAssignmentOrImperativeCall imperativeCallees x.val) + | .Quantifier _ _ trigger body => + containsAssignmentOrImperativeCall imperativeCallees body || + match trigger with | some t => containsAssignmentOrImperativeCall imperativeCallees t | none => false + | .Old value => containsAssignmentOrImperativeCall imperativeCallees value + | .Fresh value => containsAssignmentOrImperativeCall imperativeCallees value + | .ProveBy value proof => + containsAssignmentOrImperativeCall imperativeCallees value || + containsAssignmentOrImperativeCall imperativeCallees proof + | .ReferenceEquals lhs rhs => + containsAssignmentOrImperativeCall imperativeCallees lhs || + containsAssignmentOrImperativeCall imperativeCallees rhs + | .PureFieldUpdate target _ newValue => + containsAssignmentOrImperativeCall imperativeCallees target || + containsAssignmentOrImperativeCall imperativeCallees newValue + | .AsType target _ => containsAssignmentOrImperativeCall imperativeCallees target + | .IsType target _ => containsAssignmentOrImperativeCall imperativeCallees target + | .Assigned name => containsAssignmentOrImperativeCall imperativeCallees name + | .ContractOf _ func => containsAssignmentOrImperativeCall imperativeCallees func + | .Return (some v) => containsAssignmentOrImperativeCall imperativeCallees v | _ => false termination_by expr decreasing_by - all_goals ((try cases x); simp_all; try term_by_mem) + all_goals (try cases x) + all_goals (try simp_all) + all_goals (try have := Condition.sizeOf_condition_lt ‹_›) + all_goals (try term_by_mem) + all_goals omega /-- Like containsAssignment but does NOT recurse into Blocks (treats them as opaque). Used by assert/assume handlers to allow generated Block wrappers through. -/ @@ -207,10 +232,6 @@ def containsImperativeCall (model : SemanticModel) (expr : StmtExprMd) : Bool := decreasing_by all_goals ((try cases x); simp_all; try term_by_mem) -/-- Check if an expression contains any assignments or non-functional procedure calls (recursively). -/ -def containsAssignmentOrImperativeCall (model : SemanticModel) (expr : StmtExprMd) : Bool := - containsAssignment expr || containsImperativeCall model expr - /-- Shared logic for lifting an assignment in expression position: prepends the assignment, creates before-snapshots for all targets, @@ -225,7 +246,7 @@ private def liftAssignExpr (targets : List VariableMd) (seqValue : StmtExprMd) match target.val with | .Local varName => let snapshotName ← freshTempFor varName - let varType ← computeType (⟨ .Var (.Local varName), source⟩) + let varType ← computeType ⟨ .Var (.Local varName), source ⟩ -- Snapshot goes before the assignment (cons pushes to front) prepend (⟨.Assign [⟨.Declare ⟨snapshotName, varType⟩, source⟩] (⟨.Var (.Local varName), source⟩), source⟩) setSubst varName snapshotName @@ -266,7 +287,7 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do if hasSubst then pure (⟨.Var (.Local (← getSubst param.name)), source⟩) else - return expr + pure (⟨.Var (.Local param.name), source⟩) | _ => dbg_trace "Strata bug: non-identifier targets should have been removed before the lift expression phase"; return expr @@ -284,9 +305,10 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do | .StaticCall callee args => let model := (← get).model + let imperativeCallees := (← get).imperativeCallees let seqArgs ← args.reverse.mapM transformExpr let seqCall := ⟨.StaticCall callee seqArgs.reverse, source⟩ - if model.isFunction callee then + if !imperativeCallees.contains callee.text then return seqCall else -- Imperative call in expression position: lift to an assignment. @@ -303,49 +325,61 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do | [single] => pure single.type | _ => computeType expr let liftedCall := [ - ⟨.Var (.Declare ⟨callResultVar, callResultType⟩), source⟩, - ⟨.Assign [⟨.Local callResultVar, source⟩] seqCall, source⟩ + ⟨ (.Var (.Declare ⟨callResultVar, callResultType⟩)), source ⟩, + ⟨.Assign [⟨ .Local callResultVar, source⟩] seqCall, source⟩ ] modify fun s => { s with prependedStmts := s.prependedStmts ++ liftedCall} return ⟨.Var (.Local callResultVar), source⟩ | .IfThenElse cond thenBranch elseBranch => - let model := (← get).model - let thenHasAssign := containsAssignmentOrImperativeCall model thenBranch + let imperativeCallees := (← get).imperativeCallees + let thenHasAssign := containsAssignmentOrImperativeCall imperativeCallees thenBranch let elseHasAssign := match elseBranch with - | some e => containsAssignmentOrImperativeCall model e + | some e => containsAssignmentOrImperativeCall imperativeCallees e | none => false if thenHasAssign || elseHasAssign then + + -- Infer type from the ORIGINAL then-branch (not the transformed one), + -- because the transformed expression may reference freshly generated + -- variables (e.g. $c_2) that don't exist in the SemanticModel yet. + let condType ← computeType thenBranch + let needsCondVar := condType.val != .TVoid + -- Lift the entire if-then-else. Introduce a fresh variable for the result. let condVar ← freshCondVar - let seqCond ← transformExpr cond -- Save outer state let savedSubst := (← get).subst let savedPrepends := (← get).prependedStmts + + let seqCond ← transformExpr cond + let condPrepends := (← get).prependedStmts -- Process then-branch from scratch modify fun s => { s with prependedStmts := [], subst := [] } let seqThen ← transformExpr thenBranch let thenPrepends ← takePrepends - let thenBlock := ⟨.Block (thenPrepends ++ [⟨.Assign [⟨ .Local condVar, source⟩] seqThen, source⟩]) none, source⟩ + let assignStmts := if needsCondVar then [⟨.Assign [⟨ .Local condVar, source⟩] seqThen, source⟩] else [seqThen] + let thenBlock := ⟨.Block (thenPrepends ++ assignStmts) none, source ⟩ -- Process else-branch from scratch modify fun s => { s with prependedStmts := [], subst := [] } let seqElse ← match elseBranch with | some e => do let se ← transformExpr e let elsePrepends ← takePrepends - pure (some ⟨.Block (elsePrepends ++ [⟨.Assign [⟨ .Local condVar, source⟩] se, source⟩]) none, source⟩) + let assignStmts: List StmtExprMd := if needsCondVar then [⟨.Assign [⟨ .Local condVar, source⟩] se, source⟩] else [se]; + pure (some (⟨.Block (elsePrepends ++ assignStmts) none, source ⟩)) | none => pure none -- Restore outer state modify fun s => { s with subst := savedSubst, prependedStmts := savedPrepends } - -- Infer type from the ORIGINAL then-branch (not the transformed one), - -- because the transformed expression may reference freshly generated - -- variables (e.g. $c_2) that don't exist in the SemanticModel yet. - let condType ← computeType thenBranch -- IfThenElse added first (cons puts it deeper), then declaration (cons puts it on top) -- Output order: declaration, then if-then-else prepend (⟨.IfThenElse seqCond thenBlock seqElse, source⟩) - prepend ⟨.Var (.Declare ⟨condVar, condType⟩), source⟩ - return ⟨.Var (.Local condVar), source⟩ + if needsCondVar then + prepend ⟨.Var (.Declare ⟨condVar, condType⟩), source ⟩ + modify fun s => { s with prependedStmts := condPrepends ++ s.prependedStmts } + return ⟨.Var (.Local condVar), source⟩ + else + modify fun s => { s with prependedStmts := condPrepends ++ s.prependedStmts } + return default else -- No assignments in branches — recurse normally let seqCond ← transformExpr cond @@ -393,10 +427,89 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do else return expr + | .Assume cond => + let prepends ← takePrepends + let seqCond ← transformExpr cond + let argPrepends ← takePrepends + modify fun s => { s with prependedStmts := argPrepends ++ [⟨.Assume seqCond, source⟩] ++ prepends } + default + + | .Assert cond => + let prepends ← takePrepends + let seqCond ← transformExpr cond.condition + let argPrepends ← takePrepends + modify fun s => { s with prependedStmts := argPrepends ++ [⟨.Assert { cond with condition := seqCond }, source⟩] ++ prepends } + default + + | .Return (some retExpr) => + let seqRet ← transformExpr retExpr + return ⟨.Return (some seqRet), source⟩ + + | .While cond invs dec body => + let seqCond ← transformExpr cond + let seqInvs ← invs.mapM transformExpr + let seqDec ← match dec with + | some d => pure (some (← transformExpr d)) + | none => pure none + let seqBody ← transformExpr body + return ⟨.While seqCond seqInvs seqDec seqBody, source⟩ + + | .PureFieldUpdate target fieldName newValue => + let seqTarget ← transformExpr target + let seqNewValue ← transformExpr newValue + return ⟨.PureFieldUpdate seqTarget fieldName seqNewValue, source⟩ + + | .ReferenceEquals lhs rhs => + let seqRhs ← transformExpr rhs + let seqLhs ← transformExpr lhs + return ⟨.ReferenceEquals seqLhs seqRhs, source⟩ + + | .AsType target ty => + let seqTarget ← transformExpr target + return ⟨.AsType seqTarget ty, source⟩ + + | .IsType target ty => + let seqTarget ← transformExpr target + return ⟨.IsType seqTarget ty, source⟩ + + | .InstanceCall target callee args => + let seqArgs ← args.reverse.mapM transformExpr + let seqTarget ← transformExpr target + return ⟨.InstanceCall seqTarget callee seqArgs.reverse, source⟩ + + | .Quantifier mode param trigger body => + let seqBody ← transformExpr body + let seqTrigger ← match trigger with + | some t => pure (some (← transformExpr t)) + | none => pure none + return ⟨.Quantifier mode param seqTrigger seqBody, source⟩ + + | .Old value => + let seqValue ← transformExpr value + return ⟨.Old seqValue, source⟩ + + | .Fresh value => + let seqValue ← transformExpr value + return ⟨.Fresh seqValue, source⟩ + + | .Assigned name => + let seqName ← transformExpr name + return ⟨.Assigned seqName, source⟩ + + | .ProveBy value proof => + let seqValue ← transformExpr value + let seqProof ← transformExpr proof + return ⟨.ProveBy seqValue seqProof, source⟩ + + | .ContractOf ty func => + let seqFunc ← transformExpr func + return ⟨.ContractOf ty seqFunc, source⟩ + | _ => return expr termination_by (sizeOf expr, 0) decreasing_by - all_goals (simp_all; try term_by_mem) + all_goals (simp_all; try have := Condition.sizeOf_condition_lt ‹_›; try term_by_mem) + all_goals (apply Prod.Lex.left; try term_by_mem; try omega) /-- Process a statement, handling any assignments in its sub-expressions. @@ -407,26 +520,24 @@ def transformStmt (stmt : StmtExprMd) : LiftM (List StmtExprMd) := do | AstNode.mk val source => match val with | .Assert cond => - -- Do not transform assert conditions with bare assignments — they are - -- semantic errors that should be rejected downstream. - -- But Blocks with assignments (generated multi-output call wrappers) - -- are handled by the Block case in transformExpr above. - if !containsBareAssignment cond.condition then + -- Do not transform assert conditions with assignments — they must be rejected. + -- But nondeterministic holes need to be lifted. + -- if containsNondetHole cond.condition && !containsAssignmentOrImperativeCall (← get).model cond.condition then let seqCond ← transformExpr cond.condition let prepends ← takePrepends modify fun s => { s with subst := [] } return prepends ++ [⟨.Assert { cond with condition := seqCond }, source⟩] - else - return [stmt] + -- else + -- return [stmt] | .Assume cond => - if !containsBareAssignment cond then + -- if containsNondetHole cond && !containsAssignmentOrImperativeCall (← get).model cond then let seqCond ← transformExpr cond let prepends ← takePrepends modify fun s => { s with subst := [] } return prepends ++ [⟨.Assume seqCond, source⟩] - else - return [stmt] + -- else + -- return [stmt] | .Block stmts metadata => let seqStmts ← stmts.mapM transformStmt @@ -442,8 +553,8 @@ def transformStmt (stmt : StmtExprMd) : LiftM (List StmtExprMd) := do | AstNode.mk value _ => match _: value with | .StaticCall callee args => - let model := (← get).model - if model.isFunction callee then + let imperativeCallees := (← get).imperativeCallees + if !imperativeCallees.contains callee.text then let seqValue ← transformExpr valueMd let prepends ← takePrepends modify fun s => { s with subst := [] } @@ -464,11 +575,11 @@ def transformStmt (stmt : StmtExprMd) : LiftM (List StmtExprMd) := do let condPrepends ← takePrepends let seqThen ← do let stmts ← transformStmt thenBranch - pure ⟨.Block stmts none, source⟩ + pure ⟨ .Block stmts none, source ⟩ let seqElse ← match elseBranch with | some e => do let se ← transformStmt e - pure $ some ⟨.Block se none, source⟩ + pure (some (⟨.Block se none, source ⟩)) | none => pure none return condPrepends ++ [⟨.IfThenElse seqCond seqThen seqElse, source⟩] @@ -499,6 +610,10 @@ def transformStmt (stmt : StmtExprMd) : LiftM (List StmtExprMd) := do modify fun s => { s with subst := [] } return prepends ++ [⟨.Return (some seqRet), source⟩] + | .PrimitiveOp name args => + let seqArgs ← args.mapM transformExpr + let prepends ← takePrepends + return prepends ++ [⟨.PrimitiveOp name seqArgs, source⟩] | _ => return [stmt] termination_by (sizeOf stmt, 0) @@ -507,20 +622,20 @@ def transformStmt (stmt : StmtExprMd) : LiftM (List StmtExprMd) := do all_goals (apply Prod.Lex.left; try term_by_mem) end -def transformProcedureBody (proc : Procedure) (body : StmtExprMd) : LiftM StmtExprMd := do +def transformProcedureBody (source: Option FileRange) (body : StmtExprMd) : LiftM StmtExprMd := do let stmts ← transformStmt body match stmts with | [single] => pure single - | multiple => pure ⟨.Block multiple none, proc.name.source⟩ + | multiple => pure ⟨.Block multiple none, source ⟩ def transformProcedure (proc : Procedure) : LiftM Procedure := do modify fun s => { s with subst := [], prependedStmts := [], varCounters := [] } match proc.body with | .Transparent bodyExpr => - let seqBody ← transformProcedureBody proc bodyExpr + let seqBody ← transformProcedureBody proc.name.source bodyExpr pure { proc with body := .Transparent seqBody } | .Opaque postconds impl modif => - let impl' ← impl.mapM (transformProcedureBody proc) + let impl' ← impl.mapM (transformProcedureBody proc.name.source) pure { proc with body := .Opaque postconds impl' modif } | .Abstract _ => pure proc @@ -529,10 +644,15 @@ def transformProcedure (proc : Procedure) : LiftM Procedure := do /-- Transform a program to lift all assignments that occur in an expression context. +When `procedureNames` is non-empty, only procedures whose name appears in the +list are transformed; all others are left unchanged. When `procedureNames` is +empty, no procedures are transformed. -/ -def liftExpressionAssignments (model: SemanticModel) (program : Program) : Program := - let initState : LiftState := { model := model } - let (seqProcedures, _) := (program.staticProcedures.mapM transformProcedure).run initState +def liftExpressionAssignments (program : Program) + (model : SemanticModel) (imperativeCallees : List String) : Program := + let initState : LiftState := { model := model, imperativeCallees := imperativeCallees } + let transform := program.staticProcedures.mapM transformProcedure + let (seqProcedures, _) := transform.run initState { program with staticProcedures := seqProcedures } end -- public section diff --git a/Strata/Languages/Laurel/MapStmtExpr.lean b/Strata/Languages/Laurel/MapStmtExpr.lean index 10ca4756b7..94fbdb5f9f 100644 --- a/Strata/Languages/Laurel/MapStmtExpr.lean +++ b/Strata/Languages/Laurel/MapStmtExpr.lean @@ -106,6 +106,175 @@ decreasing_by def mapStmtExpr (f : StmtExprMd → StmtExprMd) (expr : StmtExprMd) : StmtExprMd := (mapStmtExprM (m := Id) f expr) +/-- +Bottom-up monadic traversal with a pre-filter. Before recursing into a node's +children, `pre` is called. If `pre` returns `some result`, that result is used +directly (children are NOT recursed into). If `pre` returns `none`, normal +bottom-up recursion proceeds and `post` is applied after children are rebuilt. +-/ +def mapStmtExprPrePostM [Monad m] (pre : StmtExprMd → m (Option StmtExprMd)) + (post : StmtExprMd → m StmtExprMd) (expr : StmtExprMd) : m StmtExprMd := do + match ← pre expr with + | some result => return result + | none => + let source := expr.source + let rebuilt ← match _h : expr.val with + | .IfThenElse cond th el => + pure ⟨.IfThenElse (← mapStmtExprPrePostM pre post cond) (← mapStmtExprPrePostM pre post th) + (← el.attach.mapM fun ⟨e, _⟩ => mapStmtExprPrePostM pre post e), source⟩ + | .Block stmts label => + pure ⟨.Block (← stmts.attach.mapM fun ⟨e, _⟩ => mapStmtExprPrePostM pre post e) label, source⟩ + | .While cond invs dec body => + pure ⟨.While (← mapStmtExprPrePostM pre post cond) + (← invs.attach.mapM fun ⟨e, _⟩ => mapStmtExprPrePostM pre post e) + (← dec.attach.mapM fun ⟨e, _⟩ => mapStmtExprPrePostM pre post e) + (← mapStmtExprPrePostM pre post body), source⟩ + | .Return v => + pure ⟨.Return (← v.attach.mapM fun ⟨e, _⟩ => mapStmtExprPrePostM pre post e), source⟩ + | .Assign targets value => + let targets' ← targets.attach.mapM fun ⟨v, _⟩ => do + let ⟨vv, vs⟩ := v + match vv with + | .Field target fieldName => + pure ⟨Variable.Field (← mapStmtExprPrePostM pre post target) fieldName, vs⟩ + | .Local _ | .Declare _ => pure v + pure ⟨.Assign targets' (← mapStmtExprPrePostM pre post value), source⟩ + | .Var (.Field target fieldName) => + pure ⟨.Var (.Field (← mapStmtExprPrePostM pre post target) fieldName), source⟩ + | .PureFieldUpdate target fieldName newValue => + pure ⟨.PureFieldUpdate (← mapStmtExprPrePostM pre post target) fieldName (← mapStmtExprPrePostM pre post newValue), source⟩ + | .StaticCall callee args => + pure ⟨.StaticCall callee (← args.attach.mapM fun ⟨e, _⟩ => mapStmtExprPrePostM pre post e), source⟩ + | .PrimitiveOp op args skipProofs => + pure ⟨.PrimitiveOp op (← args.attach.mapM fun ⟨e, _⟩ => mapStmtExprPrePostM pre post e) skipProofs, source⟩ + | .ReferenceEquals lhs rhs => + pure ⟨.ReferenceEquals (← mapStmtExprPrePostM pre post lhs) (← mapStmtExprPrePostM pre post rhs), source⟩ + | .AsType target ty => + pure ⟨.AsType (← mapStmtExprPrePostM pre post target) ty, source⟩ + | .IsType target ty => + pure ⟨.IsType (← mapStmtExprPrePostM pre post target) ty, source⟩ + | .InstanceCall target callee args => + pure ⟨.InstanceCall (← mapStmtExprPrePostM pre post target) callee + (← args.attach.mapM fun ⟨e, _⟩ => mapStmtExprPrePostM pre post e), source⟩ + | .Quantifier mode param trigger body => + pure ⟨.Quantifier mode param (← trigger.attach.mapM fun ⟨e, _⟩ => mapStmtExprPrePostM pre post e) + (← mapStmtExprPrePostM pre post body), source⟩ + | .Assigned name => + pure ⟨.Assigned (← mapStmtExprPrePostM pre post name), source⟩ + | .Old value => + pure ⟨.Old (← mapStmtExprPrePostM pre post value), source⟩ + | .Fresh value => + pure ⟨.Fresh (← mapStmtExprPrePostM pre post value), source⟩ + | .Assert cond => + pure ⟨.Assert { cond with condition := ← mapStmtExprPrePostM pre post cond.condition }, source⟩ + | .Assume cond => + pure ⟨.Assume (← mapStmtExprPrePostM pre post cond), source⟩ + | .ProveBy value proof => + pure ⟨.ProveBy (← mapStmtExprPrePostM pre post value) (← mapStmtExprPrePostM pre post proof), source⟩ + | .ContractOf ty func => + pure ⟨.ContractOf ty (← mapStmtExprPrePostM pre post func), source⟩ + | .Exit _ | .LiteralInt _ | .LiteralBool _ | .LiteralString _ | .LiteralDecimal _ + | .Var (.Local _) | .Var (.Declare _) | .New _ | .This | .Abstract | .All | .Hole .. => pure expr + post rebuilt +termination_by sizeOf expr +decreasing_by + all_goals simp_wf + all_goals (try have := AstNode.sizeOf_val_lt expr) + all_goals (try have := Condition.sizeOf_condition_lt ‹_›) + all_goals (try term_by_mem) + all_goals (cases expr; simp_all; omega) + +/-- +Bottom-up monadic traversal where `post` returns a list of statements. +- For `Block` nodes: each child is processed and the resulting lists are + flattened into the block's statement list. +- For all other nodes: if `post` returns a single element, that element is + used directly. If it returns multiple elements, they are wrapped in a new + `Block none`. + +`pre` works the same as in `mapStmtExprPrePostM`: returning `some` skips +recursion into children. +-/ +def mapStmtExprFlattenM [Monad m] (pre : StmtExprMd → m (Option (List StmtExprMd))) + (post : StmtExprMd → m (List StmtExprMd)) (expr : StmtExprMd) : m StmtExprMd := do + let collapse (results : List StmtExprMd) (src : Option FileRange) : StmtExprMd := + match results with + | [single] => single + | many => ⟨.Block many none, src⟩ + let rec go (e : StmtExprMd) : m StmtExprMd := do + match ← pre e with + | some results => return collapse results e.source + | none => + let source := e.source + let rebuilt ← match _h : e.val with + | .IfThenElse cond th el => + pure ⟨.IfThenElse (← go cond) (← go th) + (← el.attach.mapM fun ⟨x, _⟩ => go x), source⟩ + | .Block stmts label => + let flatStmts ← stmts.attach.flatMapM fun ⟨x, _⟩ => do + match ← pre x with + | some results => return results + | none => + let r ← go x + post r + pure ⟨.Block flatStmts label, source⟩ + | .While cond invs dec body => + pure ⟨.While (← go cond) + (← invs.attach.mapM fun ⟨x, _⟩ => go x) + (← dec.attach.mapM fun ⟨x, _⟩ => go x) + (← go body), source⟩ + | .Return v => + pure ⟨.Return (← v.attach.mapM fun ⟨x, _⟩ => go x), source⟩ + | .Assign targets value => + let targets' ← targets.attach.mapM fun ⟨v, _⟩ => do + let ⟨vv, vs⟩ := v + match vv with + | .Field target fieldName => + pure ⟨Variable.Field (← go target) fieldName, vs⟩ + | .Local _ | .Declare _ => pure v + pure ⟨.Assign targets' (← go value), source⟩ + | .Var (.Field target fieldName) => + pure ⟨.Var (.Field (← go target) fieldName), source⟩ + | .PureFieldUpdate target fieldName newValue => + pure ⟨.PureFieldUpdate (← go target) fieldName (← go newValue), source⟩ + | .StaticCall callee args => + pure ⟨.StaticCall callee (← args.attach.mapM fun ⟨x, _⟩ => go x), source⟩ + | .PrimitiveOp op args skipProofs => + pure ⟨.PrimitiveOp op (← args.attach.mapM fun ⟨x, _⟩ => go x) skipProofs, source⟩ + | .ReferenceEquals lhs rhs => + pure ⟨.ReferenceEquals (← go lhs) (← go rhs), source⟩ + | .AsType target ty => + pure ⟨.AsType (← go target) ty, source⟩ + | .IsType target ty => + pure ⟨.IsType (← go target) ty, source⟩ + | .InstanceCall target callee args => + pure ⟨.InstanceCall (← go target) callee + (← args.attach.mapM fun ⟨x, _⟩ => go x), source⟩ + | .Quantifier mode param trigger body => + pure ⟨.Quantifier mode param (← trigger.attach.mapM fun ⟨x, _⟩ => go x) + (← go body), source⟩ + | .Assigned name => pure ⟨.Assigned (← go name), source⟩ + | .Old value => pure ⟨.Old (← go value), source⟩ + | .Fresh value => pure ⟨.Fresh (← go value), source⟩ + | .Assert cond => + pure ⟨.Assert { cond with condition := ← go cond.condition }, source⟩ + | .Assume cond => pure ⟨.Assume (← go cond), source⟩ + | .ProveBy value proof => + pure ⟨.ProveBy (← go value) (← go proof), source⟩ + | .ContractOf ty func => pure ⟨.ContractOf ty (← go func), source⟩ + | .Exit _ | .LiteralInt _ | .LiteralBool _ | .LiteralString _ | .LiteralDecimal _ + | .Var (.Local _) | .Var (.Declare _) | .New _ | .This | .Abstract | .All | .Hole .. => pure e + let results ← post rebuilt + return collapse results source + termination_by sizeOf e + decreasing_by + all_goals simp_wf + all_goals (try have := AstNode.sizeOf_val_lt e) + all_goals (try have := Condition.sizeOf_condition_lt ‹_›) + all_goals (try term_by_mem) + all_goals (cases e; simp_all; omega) + go expr + /-- Apply a monadic transformation to all procedure bodies. -/ def mapProcedureBodiesM [Monad m] (f : StmtExprMd → m StmtExprMd) (proc : Procedure) : m Procedure := do match proc.body with @@ -116,13 +285,14 @@ def mapProcedureBodiesM [Monad m] (f : StmtExprMd → m StmtExprMd) (proc : Proc | .External => return proc /-- Apply a monadic transformation to all `StmtExprMd` nodes in a procedure - (preconditions, decreases, body, and invokeOn). -/ + (preconditions, decreases, body, invokeOn, and axioms). -/ def mapProcedureM [Monad m] (f : StmtExprMd → m StmtExprMd) (proc : Procedure) : m Procedure := do let proc ← mapProcedureBodiesM f proc return { proc with preconditions := ← proc.preconditions.mapM (·.mapM f) decreases := ← proc.decreases.mapM f - invokeOn := ← proc.invokeOn.mapM f } + invokeOn := ← proc.invokeOn.mapM f + axioms := ← proc.axioms.mapM f } /-- Apply a monadic transformation to procedure bodies in a program. Does **not** traverse preconditions, decreases, or invokeOn — use diff --git a/Strata/Languages/Laurel/Resolution.lean b/Strata/Languages/Laurel/Resolution.lean index 2efa70dc3b..93184230c8 100644 --- a/Strata/Languages/Laurel/Resolution.lean +++ b/Strata/Languages/Laurel/Resolution.lean @@ -178,7 +178,16 @@ def SemanticModel.get? (model: SemanticModel) (iden: Identifier): Option Resolve iden.uniqueId.bind model.refToDef.get? def SemanticModel.get (model: SemanticModel) (iden: Identifier): ResolvedNode := - (model.get? iden).getD default + match iden.uniqueId with + | some key => + match model.refToDef.get? key with + | some node => node + | none => + -- An ID was assigned during Phase 1 but the reference was never registered in + -- Phase 2 (buildRefToDef). This is a bug in the resolution pass itself. + dbg_trace s!"SOUND BUG: identifier '{iden.text}' (id={key}) has a uniqueId but is missing from refToDef" + .unresolved iden.source + | none => .unresolved iden.source def SemanticModel.isFunction (model: SemanticModel) (id: Identifier): Bool := match model.get id with @@ -584,10 +593,12 @@ def resolveProcedure (proc : Procedure) : ResolveM Procedure := do let dec' ← proc.decreases.mapM resolveStmtExpr let body' ← resolveBody proc.body let invokeOn' ← proc.invokeOn.mapM resolveStmtExpr + let axioms' ← proc.axioms.mapM resolveStmtExpr return { name := procName', inputs := inputs', outputs := outputs', isFunctional := proc.isFunctional, preconditions := pres', decreases := dec', invokeOn := invokeOn', + axioms := axioms', body := body' } /-- Resolve a field: define its name under the qualified key (OwnerType.fieldName) and resolve its type. -/ @@ -612,16 +623,14 @@ def resolveInstanceProcedure (typeName : Identifier) (proc : Procedure) : Resolv let pres' ← proc.preconditions.mapM (·.mapM resolveStmtExpr) let dec' ← proc.decreases.mapM resolveStmtExpr let body' ← resolveBody proc.body - if !proc.isFunctional && body'.isTransparent && !proc.name.text.any (· == '$') then - let diag := diagnosticFromSource proc.name.source - s!"transparent statement bodies are not supported. Add 'opaque' to make the procedure opaque" - modify fun s => { s with errors := s.errors.push diag } let invokeOn' ← proc.invokeOn.mapM resolveStmtExpr modify fun s => { s with instanceTypeName := savedInstType } + let axioms' ← proc.axioms.mapM resolveStmtExpr return { name := procName', inputs := inputs', outputs := outputs', isFunctional := proc.isFunctional, preconditions := pres', decreases := dec', invokeOn := invokeOn', + axioms := axioms', body := body' } /-- Resolve a type definition. -/ @@ -884,13 +893,7 @@ private def preRegisterTopLevel (program : Program) : ResolveM Unit := do | .Datatype dt => let _ ← defineNameCheckDup dt.name (.datatypeDefinition dt) for ctor in dt.constructors do - -- Register the tester override first; the second call reuses the - -- returned Identifier (now carrying a uniqueId) so the unprefixed - -- constructor name and the `TypeName..isCtor` tester name resolve to - -- the same uniqueId, which `buildRefToDef` in turn maps to - -- `.datatypeConstructor`. - let ctorName ← defineNameCheckDup ctor.name (.datatypeConstructor dt.name ctor) (some (dt.testerName ctor)) - let _ ← defineNameCheckDup ctorName (.datatypeConstructor dt.name ctor) + let _ ← defineNameCheckDup ctor.name (.datatypeConstructor dt.name ctor) for p in ctor.args do -- Same chaining trick for the safe and unsafe destructor names: both -- point to the same uniqueId so `IntList..head` and `IntList..head!` @@ -983,9 +986,9 @@ but they are because certain type references have incorrectly not been updated. def resolveUnorderedCore (uc : UnorderedCoreWithLaurelTypes) (existingModel : Option SemanticModel := none) (additionalTypes : List TypeDefinition := []) - : UnorderedCoreWithLaurelTypes × SemanticModel := + : UnorderedCoreWithLaurelTypes × SemanticModel × Array DiagnosticModel := let fnProgram := unorderedCoreToProgram uc additionalTypes let fnResolveResult := resolve fnProgram existingModel - (fromResolvedProgram fnResolveResult.program, fnResolveResult.model) + (fromResolvedProgram fnResolveResult.program, fnResolveResult.model, fnResolveResult.errors) end diff --git a/Strata/Languages/Laurel/TransparencyPass.lean b/Strata/Languages/Laurel/TransparencyPass.lean index 8b30043007..275ade0862 100644 --- a/Strata/Languages/Laurel/TransparencyPass.lean +++ b/Strata/Languages/Laurel/TransparencyPass.lean @@ -85,6 +85,34 @@ private def rewriteCallsToFunctional (asFunctionNames : List String) (expr : Stm | .PrimitiveOp operator arguments _ => ⟨ .PrimitiveOp operator arguments true, e.source⟩ | _ => e) expr +/-- Rewrite quantifier bodies like function bodies: strip assert/assume and + rewrite calls to their `$asFunction` variants. This ensures that calls + inside quantifiers (e.g. in modifies frame conditions) reference the + pure functional version and are not treated as imperative by later passes. -/ +private def rewriteQuantifierBodies (nonExternalNames : List String) (expr : StmtExprMd) : StmtExprMd := + mapStmtExpr (fun e => + match e.val with + | .Quantifier mode param trigger body => + let body' := rewriteCallsToFunctional nonExternalNames (stripAssertAssume body) + let trigger' := trigger.map (rewriteCallsToFunctional nonExternalNames) + ⟨.Quantifier mode param trigger' body', e.source⟩ + | _ => e) expr + +/-- Apply quantifier body rewriting to all postconditions and the implementation + of a procedure. -/ +private def rewriteQuantifierBodiesInProc (nonExternalNames : List String) (proc : Procedure) : Procedure := + let rewrite := rewriteQuantifierBodies nonExternalNames + match proc.body with + | .Opaque postconds impl modif => + let postconds' := postconds.map fun c => { c with condition := rewrite c.condition } + let impl' := impl.map rewrite + { proc with body := .Opaque postconds' impl' modif } + | .Transparent body => + { proc with body := .Transparent (rewrite body) } + | .Abstract postconds => + let postconds' := postconds.map fun c => { c with condition := rewrite c.condition } + { proc with body := .Abstract postconds' } + | .External => proc /-- Build a free postcondition equating the procedure's output to its functional version. For a procedure `foo(a, b) returns (r)`, produces: `r == foo$asFunction(a, b)` -/ @@ -141,25 +169,23 @@ For each procedure: - If the function has a body, add a free postcondition equating the procedure output to the function -/ def transparencyPass (program : Program) : UnorderedCoreWithLaurelTypes := - let (skipped, notSkipped) := program.staticProcedures.partition (fun p => p.body.isExternal || - -- Skip functions until we introduce a contract pass, - -- which enables lifting procedure calls from contracts - p.isFunctional) - let asFunctionNames := notSkipped.map (fun p => p.name.text) - let asFunctions := notSkipped.map (mkFunctionCopy asFunctionNames) - + let nonExternal := program.staticProcedures.filter (fun p => !p.body.isExternal) + let nonExternalNames := nonExternal.map (fun p => p.name.text) + -- $asFunction copies for non-external procedures + let asFunctions := nonExternal.map (mkFunctionCopy nonExternalNames) -- External procedures get a plain function copy (they have no $asFunction version) - let (skippedFunctions, skippedProcedures) := skipped.partition (fun p => p.isFunctional) - let functions := skippedFunctions ++ asFunctions - let coreProcedures := notSkipped.map fun p => - let freePostcondition := mkFreePostcondition p - let p := addFreePostcondition p freePostcondition - { p with isFunctional := false } + let externalFunctions := program.staticProcedures.filter (fun p => p.body.isExternal) + |>.map fun proc => { proc with isFunctional := true } + let functions := externalFunctions ++ asFunctions + let coreProcedures := nonExternal.map fun proc => + let freePostcondition := mkFreePostcondition proc + let proc := { proc with isFunctional := false, axioms := proc.axioms.map (rewriteCallsToFunctional nonExternalNames) } + let proc := rewriteQuantifierBodiesInProc nonExternalNames proc + addFreePostcondition proc freePostcondition let datatypes := program.types.filterMap fun td => match td with | .Datatype dt => some dt | _ => none - let procs: List Procedure := skippedProcedures ++ coreProcedures - { functions, coreProcedures := procs, datatypes, constants := program.constants } + { functions, coreProcedures, datatypes, constants := program.constants } open Std (Format ToFormat) diff --git a/Strata/Languages/Python/PythonToLaurel.lean b/Strata/Languages/Python/PythonToLaurel.lean index 0c1a030899..e272c562fd 100644 --- a/Strata/Languages/Python/PythonToLaurel.lean +++ b/Strata/Languages/Python/PythonToLaurel.lean @@ -2061,7 +2061,7 @@ partial def translateStmt (ctx : TranslationContext) (s : Python.stmt SourceRang let assumeInRange := mkStmtExprMdWithLoc (.Assume inRangeExpr) md pure [assumeTypeInt, assumeInRange] | _ => - let targetInIter := mkStmtExprMd (.StaticCall "PIn" [targetVar, iterExpr]) + let targetInIter := mkStmtExprMdWithLoc (.StaticCall "PIn" [targetVar, iterExpr]) md let assumeInStmt := mkStmtExprMdWithLoc (.Assume (Any_to_bool targetInIter)) md pure [assumeInStmt] | _ => pure [] diff --git a/StrataTest/Languages/Core/Examples/TypeDecl.lean b/StrataTest/Languages/Core/Examples/TypeDecl.lean index 3903c29331..4509cee4c4 100644 --- a/StrataTest/Languages/Core/Examples/TypeDecl.lean +++ b/StrataTest/Languages/Core/Examples/TypeDecl.lean @@ -127,7 +127,7 @@ error: ❌ Type checking error. This type declaration's name is reserved! int := bool KnownTypes' names: -[arrow, Sequence, TriggerGroup, real, string, bitvec, Triggers, int, bool, Map, regex] +[arrow, Sequence, TriggerGroup, real, string, bitvec, Triggers, int, bool, Map, errorVoid, regex] -/ #guard_msgs in #eval verify typeDeclPgm4 diff --git a/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean b/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean index f6352c4487..71d5ec2e9d 100644 --- a/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean +++ b/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean @@ -106,7 +106,9 @@ composite Point { info: procedure test(x: int): int opaque { - if x > 0 then x else 0 - x + if x > 0 + then x + else 0 - x }; -/ #guard_msgs in diff --git a/StrataTest/Languages/Laurel/ConstrainedTypeElimTest.lean b/StrataTest/Languages/Laurel/ConstrainedTypeElimTest.lean index 2e5fb6c3f1..a791b1f523 100644 --- a/StrataTest/Languages/Laurel/ConstrainedTypeElimTest.lean +++ b/StrataTest/Languages/Laurel/ConstrainedTypeElimTest.lean @@ -97,7 +97,8 @@ info: function pos$constraint(v: int): bool procedure test(b: bool) opaque { - if b then { + if b + then { var x: int := 1; assert pos$constraint(x) }; diff --git a/StrataTest/Languages/Laurel/DivisionByZeroCheckTest.lean b/StrataTest/Languages/Laurel/DivisionByZeroCheckTest.lean index d5d5671ade..dc12ef17af 100644 --- a/StrataTest/Languages/Laurel/DivisionByZeroCheckTest.lean +++ b/StrataTest/Languages/Laurel/DivisionByZeroCheckTest.lean @@ -54,7 +54,7 @@ procedure callPureDivUnsafe(x: int) opaque { var z: int := pureDiv(10, x) -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion does not hold +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: precondition could not be proved // Error ranges are too wide because Core does not use expression locations }; " diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T10_ConstrainedTypes.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T10_ConstrainedTypes.lean index 80d21eb4d6..dffe4a296a 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T10_ConstrainedTypes.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T10_ConstrainedTypes.lean @@ -32,7 +32,7 @@ procedure outputValid(): nat // Output constraint — invalid return fails procedure outputInvalid(): nat -// ^^^ error: assertion does not hold +// ^^^ error: postcondition could not be proved opaque { return -1 @@ -60,7 +60,7 @@ procedure assignInvalid() opaque { var y: nat := -1 -//^^^^^^^^^^^^^^^^ error: assertion does not hold +//^^^^^^^^^^^^^^^^ error: assertion could not be proved }; // Reassignment to constrained-typed variable — invalid @@ -69,7 +69,7 @@ procedure reassignInvalid() { var y: nat := 5; y := -1 -//^^^^^^^ error: assertion does not hold +//^^^^^^^ error: assertion could not be proved }; // Argument to constrained-typed parameter — valid @@ -88,7 +88,7 @@ procedure argInvalid() returns (r: int) opaque { var x: int := takesNat(-1); -//^^^^^^^^^^^^^^^^^^^^^^^^^^ error: precondition does not hold +//^^^^^^^^^^^^^^^^^^^^^^^^^^ error: precondition could not be proved return x }; @@ -129,7 +129,7 @@ procedure constrainedInExpr() // Invalid witness — witness -1 does not satisfy x > 0 constrained bad = x: int where x > 0 witness -1 -// ^^ error: assertion does not hold +// ^^ error: assertion could not be proved // Uninitialized constrained variable — havoc + assume constraint procedure uninitNat() @@ -154,14 +154,12 @@ procedure uninitNotWitness() { var y: posnat; assert y == 1 -//^^^^^^^^^^^^^ error: assertion does not hold +//^^^^^^^^^^^^^ error: assertion could not be proved }; // Quantifier constraint injection — forall // n + 1 > 0 is only provable with n >= 0 injected; false for all int -procedure forallNat() - opaque -{ +procedure forallNat() opaque { var b: bool := forall(n: nat) => n + 1 > 0; assert b }; @@ -169,9 +167,7 @@ procedure forallNat() // Quantifier constraint injection — exists // n == -1 is satisfiable for int, but not when n >= 0 is required // n == 42 works because 42 >= 0 -procedure existsNat() - opaque -{ +procedure existsNat() opaque { var b: bool := exists(n: nat) => n == 42; assert b }; @@ -192,7 +188,7 @@ procedure captureTest(y: haslarger) opaque { assert false -//^^^^^^^^^^^^ error: assertion does not hold +//^^^^^^^^^^^^ error: assertion could not be proved }; " diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T14_Quantifiers.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T14_Quantifiers.lean index c60edd889c..50ae574f18 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T14_Quantifiers.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T14_Quantifiers.lean @@ -49,7 +49,7 @@ procedure triggers() " -#guard_msgs(drop info, error) in +#guard_msgs (drop info, error) in #eval testInputWithOffset "Quantifiers" quantifiersProgram 14 processLaurelFile end Laurel diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T15_ShortCircuit.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T15_ShortCircuit.lean index 2e5b46a8ab..fb39c59ce2 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T15_ShortCircuit.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T15_ShortCircuit.lean @@ -25,7 +25,6 @@ procedure mustNotCallProc(): int }; // Pure path: function with requires false - procedure testAndThenFunc() opaque { diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T16_PropertySummary.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T16_PropertySummary.lean index b9d25ce265..7ceb3aea9b 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T16_PropertySummary.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T16_PropertySummary.lean @@ -19,7 +19,7 @@ procedure divide(x: int, y: int) returns (result: int) opaque { assert y == 0 summary "divisor is zero"; -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: divisor is zero does not hold +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: divisor is zero could not be proved return x }; @@ -27,7 +27,7 @@ procedure checkPositive(n: int) returns (ok: bool) opaque { var x: int := divide(3, 0) -//^^^^^^^^^^^^^^^^^^^^^^^^^^ error: divisor is non-zero does not hold +//^^^^^^^^^^^^^^^^^^^^^^^^^^ error: divisor is non-zero could not be proved }; "# diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T18_RecursiveFunction.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T18_RecursiveFunction.lean index 23da15a520..7257ec2df3 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T18_RecursiveFunction.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T18_RecursiveFunction.lean @@ -22,7 +22,8 @@ datatype IntList { Cons(head: int, tail: IntList) } -function listLen(xs: IntList): int { +function listLen(xs: IntList): int +{ if IntList..isNil(xs) then 0 else 1 + listLen(IntList..tail!(xs)) }; @@ -35,12 +36,14 @@ procedure testListLen() }; // Mutual recursion -function listLenEven(xs: IntList): bool { +function listLenEven(xs: IntList): bool +{ if IntList..isNil(xs) then true else listLenOdd(IntList..tail!(xs)) }; -function listLenOdd(xs: IntList): bool { +function listLenOdd(xs: IntList): bool +{ if IntList..isNil(xs) then false else listLenEven(IntList..tail!(xs)) }; diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T19_InvokeOn.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T19_InvokeOn.lean index a5eb37f347..43b2aaeb11 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T19_InvokeOn.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T19_InvokeOn.lean @@ -69,7 +69,7 @@ procedure badPostcondition(x: int) invokeOn R(x) opaque ensures R(x) -// ^^^^ error: assertion could not be proved +// ^^^^ error: postcondition could not be proved { }; diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean index 282c0d520d..323b42ed60 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean @@ -13,6 +13,7 @@ namespace Strata namespace Laurel def transparentBodyProgram := r" +<<<<<<< HEAD procedure transparentBodyMultipleOuts() returns (q: int, r: int) { assert true; @@ -22,10 +23,15 @@ procedure transparentBodyMultipleOuts() returns (q: int, r: int) }; procedure transparentBodyNoOuts() +======= +procedure transparentBody(): int +>>>>>>> 5e61ec87a (Add contract pass) { - assert true + assert true; + 3 }; +<<<<<<< HEAD procedure transparentProcedureCaller() opaque { assign var x: int, var y: int := transparentBodyMultipleOuts(); assert x == 3; @@ -33,6 +39,13 @@ procedure transparentProcedureCaller() opaque { transparentBodyNoOuts() }; +======= +// No support for transparent void procedures yet +// procedure transparentBody() +// { +// assert true +// }; +>>>>>>> 5e61ec87a (Add contract pass) " #guard_msgs(drop info, error) in diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T22_ArityMismatch.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T22_ArityMismatch.lean index 94c0f22371..a796ab2fb5 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T22_ArityMismatch.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T22_ArityMismatch.lean @@ -22,10 +22,7 @@ procedure caller() }; " -/-- -error: ArityMismatch(79-100) ❌ Type checking error. -Impossible to unify int with (arrow int $__ty35). --/ +/-- error: input length and args length mismatch -/ #guard_msgs(drop info, error) in #eval testInputWithOffset "ArityMismatch" arityMismatchProgram 14 processLaurelFile diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean index e170ce5dd1..217239db1f 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean @@ -138,10 +138,13 @@ procedure addProcCaller(): int { var x: int := 0; var y: int := addProc({x := 1; x}, {x := x + 10; x}); - assert y == 11 // TOOD should be 12. Fix in other PR + assert y == 12; - // var z: int := addProc({x := 1; x}, {x := x + 10; x}) + (x := 3); - // assert z == 15 + // The next statement is not translated correctly. + // I think it's a bug in the handling of StaticCall + // Where a reference is substituted when it should not be + var z: int := addProc({x := 1; x}, {x := x + 10; x}) + (x := 3); + assert z == 15 }; " diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean index 390c0e6c62..6b275dbc46 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean @@ -52,7 +52,6 @@ procedure impureContractIsNotLegal2(x: int) opaque { assert (x := 2) == 2 -// ^^^^^^ error: destructive assignments are not supported in transparent bodies or contracts }; " diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean index b58d8daa4e..d5832e0cb9 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean @@ -13,7 +13,27 @@ open Strata namespace Strata.Laurel def program := r" -function returnAtEnd(x: int) returns (r: int) +function letsInFunction() returns (r: int) { + var x: int := 0; + var y: int := x + 1; + var z: int := y + 1; + z +}; + +procedure callLetsInFunction() opaque { + var x: int := letsInFunction(); + assert x == 2 +}; + +function assertAndAssumeInFunctions(a: int) returns (r: int) +{ + assert 2 == 3; +//^^^^^^^^^^^^^ error: assertion does not hold + assume true; + a +}; + +procedure returnAtEnd(x: int) returns (r: int) { if x > 0 then { if x == 1 then { @@ -26,11 +46,13 @@ function returnAtEnd(x: int) returns (r: int) } }; -function elseWithCall(): int { +function elseWithCall(): int +{ if true then 3 else returnAtEnd(3) }; -function guardInFunction(x: int) returns (r: int) { +procedure guardInFunction(x: int) returns (r: int) +{ if x > 0 then { if x == 1 then { return 1 @@ -47,15 +69,14 @@ procedure testFunctions() { assert returnAtEnd(1) == 1; assert returnAtEnd(1) == 2; -//^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion does not hold +//^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion could not be proved assert guardInFunction(1) == 1; assert guardInFunction(1) == 2 -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion does not hold +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion could not be proved }; procedure guards(a: int) returns (r: int) - opaque { var b: int := a + 2; if b > 2 then { diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean index 6fe07c0d4b..a566ca08fa 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean @@ -16,12 +16,11 @@ def program := r" function assertAndAssumeInFunctions(a: int) returns (r: int) { assert 2 == 3; -//^^^^^^^^^^^^^ error: asserts are not YET supported in functions or contracts assume true; -//^^^^^^^^^^^ error: assumes are not YET supported in functions or contracts a }; + function letsInFunction() returns (r: int) { var x: int := 0; var y: int := x + 1; @@ -40,7 +39,7 @@ function localVariableWithoutInitializer(): int { 3 }; -function deadCodeAfterIfElse(x: int) returns (r: int) { +procedure deadCodeAfterIfElse(x: int) returns (r: int) { if x > 0 then { return 1 } else { return 2 }; //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: if-then-else only supported as the last statement in a block return 3 diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T6_Preconditions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T6_Preconditions.lean index 9776411a4d..a300094b89 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T6_Preconditions.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T6_Preconditions.lean @@ -22,7 +22,7 @@ procedure hasRequires(x: int) returns (r: int) { assert x > 0; assert x > 3; -//^^^^^^^^^^^^ error: assertion does not hold +//^^^^^^^^^^^^ error: assertion could not be proved x + 1 }; @@ -30,7 +30,7 @@ procedure caller() opaque { var x: int := hasRequires(1); -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: precondition does not hold +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: precondition could not be proved var y: int := hasRequires(3) }; @@ -44,7 +44,7 @@ procedure aFunctionWithPreconditionCaller() opaque { var x: int := aFunctionWithPrecondition(0) -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion does not hold +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: precondition could not be proved // Error ranges are too wide because Core does not use expression locations }; @@ -76,7 +76,7 @@ procedure funcMultipleRequiresCaller() { var a: int := funcMultipleRequires(1, 2); var b: int := funcMultipleRequires(1, -1) -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion does not hold +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: precondition could not be proved }; " diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T7_Decreases.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T7_Decreases.lean index 1fd5cdfbca..8fceb15b09 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T7_Decreases.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T7_Decreases.lean @@ -23,23 +23,29 @@ procedure noDecreases(x: int): boolean; procedure caller(x: int) requires noDecreases(x) // ^ error: noDecreases can not be called from a pure context, because it is not proven to terminate + opaque ; procedure noCyclicCalls() + opaque decreases [] { leaf(); }; -procedure leaf() decreases [1] { }; +procedure leaf() decreases [1] + opaque +{ }; procedure mutualRecursionA(x: nat) + opaque decreases [x, 1] { mutualRecursionB(x); }; procedure mutualRecursionB(x: nat) + opaque decreases [x, 0] { if x != 0 { mutualRecursionA(x-1); } diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean index 4c0321a009..a9b269567f 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean @@ -13,6 +13,24 @@ open Strata namespace Strata.Laurel def program := r" + +function opaqueFunction(x: int) returns (r: int) + requires x > 0 + opaque + ensures r > 0 +{ + x +}; + +procedure callerOfOpaqueFunction() + opaque +{ + var x: int := opaqueFunction(3); + assert x > 0; + assert x == 3 +//^^^^^^^^^^^^^ error: assertion could not be proved +}; + procedure opaqueBody(x: int) returns (r: int) opaque ensures r > 0 @@ -31,12 +49,13 @@ procedure callerOfOpaqueProcedure() }; procedure invalidPostcondition(x: int) + returns (r: int) // TODO, removing this returns triggers a latent bug opaque ensures false -// ^^^^^ error: assertion does not hold +// ^^^^^ error: postcondition does not hold { }; " #guard_msgs (drop info, error) in -#eval testInputWithOffset "Postconditions" program 14 processLaurelFile +#eval testInputWithOffset "Postconditions" program 17 processLaurelFileKeepIntermediates diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_PostconditionsErrors.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_PostconditionsErrors.lean deleted file mode 100644 index d61c5849da..0000000000 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_PostconditionsErrors.lean +++ /dev/null @@ -1,39 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ - -import StrataTest.Util.TestDiagnostics -import StrataTest.Languages.Laurel.TestExamples - -open StrataTest.Util -open Strata - -namespace Strata.Laurel - -def program := r" - -function opaqueFunction(x: int) returns (r: int) -// ^^^^^^^^^^^^^^ error: functions with postconditions are not yet supported -// The above limitation is because Core does not yet support functions with postconditions - requires x > 0 - opaque - ensures r > 0 -// The above limitation is because functions in Core do not support postconditions -{ - x -}; - -procedure callerOfOpaqueFunction() - opaque -{ - var x: int := opaqueFunction(3); - assert x > 0; -// The following assertion should fail but does not - assert x == 3 -}; -" - -#guard_msgs (drop info, error) in -#eval testInputWithOffset "Postconditions" program 14 processLaurelFile diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8b_EarlyReturnPostconditions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8b_EarlyReturnPostconditions.lean index 964fc7dfb0..f8f4b8089b 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8b_EarlyReturnPostconditions.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8b_EarlyReturnPostconditions.lean @@ -26,7 +26,7 @@ procedure earlyReturnCorrect(x: int) returns (r: int) procedure earlyReturnBuggy(x: int) returns (r: int) opaque ensures r >= 0 -// ^^^^^^ error: assertion does not hold +// ^^^^^^ error: postcondition does not hold { if x < 0 then { return x diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8d_HeapMutatingValueReturn.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8d_HeapMutatingValueReturn.lean index 0a8321d945..0e2a397570 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8d_HeapMutatingValueReturn.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8d_HeapMutatingValueReturn.lean @@ -29,7 +29,7 @@ procedure setAndReturn(c: Container, x: int) returns (r: int) procedure setAndReturnBuggy(c: Container, x: int) returns (r: int) opaque ensures r == x + 1 -// ^^^^^^^^^^ error: assertion does not hold +// ^^^^^^^^^^ error: postcondition could not be proved modifies c { c#value := x; diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T9_Nondeterministic.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T9_Nondeterministic.lean index 1be2b5405c..d62478790b 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T9_Nondeterministic.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T9_Nondeterministic.lean @@ -21,6 +21,10 @@ nondet procedure nonDeterministic(x: int): (r: int) }; procedure caller() +<<<<<<< HEAD +======= + opaque +>>>>>>> 5e61ec87a (Add contract pass) { var x = nonDeterministic(1) assert x > 0; @@ -30,11 +34,13 @@ procedure caller() }; nondet procedure nonDeterminsticTransparant(x: int): (r: int) + opaque { nonDeterministic(x + 1) }; procedure nonDeterministicCaller(x: int): int + opaque { nonDeterministic(x) }; diff --git a/StrataTest/Languages/Laurel/Examples/Objects/T1_MutableFields.lean b/StrataTest/Languages/Laurel/Examples/Objects/T1_MutableFields.lean index b75a63c5de..f7d20310eb 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/T1_MutableFields.lean +++ b/StrataTest/Languages/Laurel/Examples/Objects/T1_MutableFields.lean @@ -67,9 +67,7 @@ procedure updatesAndAliasing() assert dAlias#intValue == d#intValue }; -procedure subsequentHeapMutations() - opaque -{ +procedure subsequentHeapMutations() opaque { var c: Container := new Container; // The additional parenthesis on the next line are needed to let the parser succeed. Joe, any idea why this is needed? @@ -125,9 +123,7 @@ composite Pixel { var color: Color } -procedure datatypeField() - opaque -{ +procedure datatypeField() opaque { var p: Pixel := new Pixel; p#color := Red(); assert Color..isRed(p#color); diff --git a/StrataTest/Languages/Laurel/Examples/Objects/T3_ReadsClauses.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/T3_ReadsClauses.lr.st index 5732c7d40c..210a95c155 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/T3_ReadsClauses.lr.st +++ b/StrataTest/Languages/Laurel/Examples/Objects/T3_ReadsClauses.lr.st @@ -46,7 +46,8 @@ type Composite; type Field; val value: Field; -function opaqueProcedure_ensures(heap: Heap, c: Container, r: int): boolean { +function opaqueProcedure_ensures(heap: Heap, c: Container, r: int): boolean +{ true } diff --git a/StrataTest/Languages/Laurel/Examples/Objects/T6_Datatypes.lean b/StrataTest/Languages/Laurel/Examples/Objects/T6_Datatypes.lean index 9bb51c2d13..c716bb8359 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/T6_Datatypes.lean +++ b/StrataTest/Languages/Laurel/Examples/Objects/T6_Datatypes.lean @@ -19,17 +19,13 @@ datatype IntList { } // Construction and destructor access -procedure testConstruction() - opaque -{ +procedure testConstruction() opaque { var xs: IntList := Cons(42, Nil()); assert IntList..head(xs) == 42 }; // Constructor testing -procedure testConstructorTest() - opaque -{ +procedure testConstructorTest() opaque { var xs: IntList := Cons(1, Nil()); assert IntList..isCons(xs); assert !IntList..isNil(xs); @@ -40,9 +36,7 @@ procedure testConstructorTest() }; // Nested construction and deconstruction -procedure testNested() - opaque -{ +procedure testNested() opaque { var xs: IntList := Cons(1, Cons(2, Nil())); assert IntList..isCons(xs); assert IntList..head(xs) == 1; @@ -51,9 +45,7 @@ procedure testNested() assert IntList..isNil(IntList..tail(IntList..tail(xs))) }; -procedure unsafeDestructor() - opaque -{ +procedure unsafeDestructor() opaque { var nil: IntList := Nil(); var noError: int := IntList..head!(nil); var error: int := IntList..head(nil) @@ -67,18 +59,14 @@ function listHead(xs: IntList): int IntList..head(xs) }; -procedure testFunction() - opaque -{ +procedure testFunction() opaque { var xs: IntList := Cons(10, Nil()); var h: int := listHead(xs); assert h == 10 }; // Failing assertion -procedure testFailing() - opaque -{ +procedure testFailing() opaque { var xs: IntList := Nil(); assert IntList..isCons(xs) //^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion does not hold @@ -94,9 +82,7 @@ datatype OddList { OCons(head: int, tail: EvenList) } -procedure testMutualConstruction() - opaque -{ +procedure testMutualConstruction() opaque { var even: EvenList := ENil(); assert EvenList..isENil(even); var odd: OddList := OCons(1, ENil()); diff --git a/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Allocation.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Allocation.lr.st index dd02787340..3dc57127ed 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Allocation.lr.st +++ b/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Allocation.lr.st @@ -13,6 +13,7 @@ composite Immutable { invariant x + y >= 5 procedure construct() + opaque constructor requires contructing == {this} ensures constructing == {} @@ -50,6 +51,7 @@ composite ImmutableChainOfTwo { // only used privately procedure allocate() + opaque constructor ensures constructing = {this} { // empty body diff --git a/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Constructors.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Constructors.lr.st index 2d9a5afaa4..6d33df854c 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Constructors.lr.st +++ b/StrataTest/Languages/Laurel/Examples/Objects/WIP/5. Constructors.lr.st @@ -18,6 +18,7 @@ composite Immutable { // fields of Immutable are considered mutable inside this procedure // and invariants of Immutable are not visible // can only call procedures that are also constructing Immutable + opaque constructs Immutable modifies this { diff --git a/StrataTest/Languages/Laurel/Examples/Objects/WIP/7. InstanceCallables.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/WIP/7. InstanceCallables.lr.st index fe4c5756d6..95cf829196 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/WIP/7. InstanceCallables.lr.st +++ b/StrataTest/Languages/Laurel/Examples/Objects/WIP/7. InstanceCallables.lr.st @@ -5,12 +5,14 @@ */ composite Base { procedure foo(): int + opaque ensures result > 3 { abstract }; } composite Extender1 extends Base { procedure foo(): int + opaque ensures result > 4 // ^^^^^^^ error: could not prove ensures clause guarantees that of extended method 'Base.foo' { abstract }; @@ -19,6 +21,7 @@ composite Extender1 extends Base { composite Extender2 extends Base { value: int procedure foo(): int + opaque ensures result > 2 { this.value + 2 // 'this' is an implicit variable inside instance callables diff --git a/StrataTest/Languages/Laurel/Examples/Objects/WIP/9. Closures.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/WIP/9. Closures.lr.st index 212df76ab8..c5e7f94c87 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/WIP/9. Closures.lr.st +++ b/StrataTest/Languages/Laurel/Examples/Objects/WIP/9. Closures.lr.st @@ -87,6 +87,7 @@ so in affect there no longer are any variables captured by a closure. // Option A: first class procedures procedure hasClosure() returns (r: int) + opaque ensures r == 7 { var x = 3; @@ -101,6 +102,10 @@ procedure hasClosure() returns (r: int) // Option B: type closures composite ATrait { procedure foo() returns (r: int) ensures r > 0 +<<<<<<< HEAD +======= + opaque +>>>>>>> 5e61ec87a (Add contract pass) { abstract }; @@ -112,7 +117,12 @@ procedure hasClosure() returns (r: int) { var x = 3; var aClosure := closure extends ATrait { +<<<<<<< HEAD procedure foo() returns (r: int) +======= + procedure foo() returns (r: int) + opaque +>>>>>>> 5e61ec87a (Add contract pass) { r = x + 4; }; diff --git a/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean b/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean index 98e1706a3f..65d86b9e4f 100644 --- a/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean +++ b/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean @@ -41,14 +41,16 @@ def parseLaurelAndLift (input : String) : IO Program := do | .ok program => let result := resolve program let (program, model) := (result.program, result.model) - pure (liftExpressionAssignments model program) + let imperativeCallees := program.staticProcedures.filter (fun p => !p.isFunctional) + |>.map (fun p => p.name.text) + pure (liftExpressionAssignments program model imperativeCallees) /-- info: procedure assertInBlockExpr() opaque { var x: int := 0; - assert x == 0; + assert $x_0 == 0; var $x_0: int := x; x := 1; var y: int := { diff --git a/StrataTest/Languages/Laurel/LiftHolesTest.lean b/StrataTest/Languages/Laurel/LiftHolesTest.lean index cecc2d6cd5..019e953feb 100644 --- a/StrataTest/Languages/Laurel/LiftHolesTest.lean +++ b/StrataTest/Languages/Laurel/LiftHolesTest.lean @@ -138,7 +138,8 @@ info: function $hole_0() procedure test() opaque { - if $hole_0() then { + if $hole_0() + then { assert true } }; @@ -158,7 +159,9 @@ info: function $hole_0() procedure test() opaque { - var x: int := if true then $hole_0() else 0 + var x: int := if true + then $hole_0() + else 0 }; -/ #guard_msgs in @@ -316,7 +319,8 @@ info: function $hole_0() procedure test() opaque { - if 1 + $hole_0() > 0 then { + if 1 + $hole_0() > 0 + then { assert true } }; @@ -492,13 +496,13 @@ info: function $hole_0() opaque; procedure test() { - assert IntList..isCons($hole_0()) + assert IntList..head($hole_0()) }; -/ #guard_msgs in #eval! parseElimAndPrint r" datatype IntList { Nil(), Cons(head: int, tail: IntList) } -procedure test() { assert IntList..isCons() }; +procedure test() { assert IntList..head() }; " end Laurel diff --git a/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean b/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean index f44067848c..635365b8f5 100644 --- a/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean +++ b/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean @@ -31,7 +31,7 @@ private def parseLaurelAndLift (input : String) : IO Program := do | .ok program => let result := resolve program let (program, model) := (result.program, result.model) - pure (liftExpressionAssignments model program) + pure (liftExpressionAssignments program model ["impure", "multi_out"]) private def printLifted (input : String) : IO Unit := do let program ← parseLaurelAndLift input @@ -49,9 +49,9 @@ info: procedure impure(): int }; procedure test() { - var $c_0: int; - $c_0 := impure(); - assert $c_0 == 1 + var $cndtn_0: int; + $cndtn_0 := impure(); + assert $cndtn_0 == 1 }; -/ #guard_msgs in @@ -72,7 +72,9 @@ procedure test() { info: procedure test() { var x: int := 0; - assert (x := 2) == 2 + var $x_0: int := x; + x := 2; + assert x == 2 }; -/ #guard_msgs in @@ -94,9 +96,9 @@ info: procedure impure(): int }; procedure test() { - var $c_0: int; - $c_0 := impure(); - assume $c_0 == 1 + var $cndtn_0: int; + $cndtn_0 := impure(); + assume $cndtn_0 == 1 }; -/ #guard_msgs in @@ -124,9 +126,9 @@ info: procedure multi_out(x: int) }; procedure test() { - var $c_0: BUG_MultiValuedExpr; - $c_0 := multi_out(5); - assert $c_0 == 6 + var $cndtn_0: BUG_MultiValuedExpr; + $cndtn_0 := multi_out(5); + assert $cndtn_0 == 6 }; -/ #guard_msgs in diff --git a/StrataTest/Languages/Python/ToLaurelTest.lean b/StrataTest/Languages/Python/ToLaurelTest.lean index bbf7e437a5..5220c6b975 100644 --- a/StrataTest/Languages/Python/ToLaurelTest.lean +++ b/StrataTest/Languages/Python/ToLaurelTest.lean @@ -673,7 +673,7 @@ info: errors: 1 -- Regression test for issue #800: nested dict access `kwargs["Outer"]["Inner"]` -- should generate `Any_get` (dict lookup), not `FieldSelect`. /-- -info: body contains Any_get: true +info: preconditions contain Any_get: true body contains FieldSelect: false -/ #guard_msgs in @@ -694,11 +694,13 @@ body contains FieldSelect: false assert! result.errors.size = 0 match result.program.staticProcedures with | proc :: _ => + let precondStr := proc.preconditions.map (fun (p : Strata.Laurel.Condition) => toString (Strata.Laurel.formatStmtExpr p.condition)) + |> String.intercalate ", " let bodyStr := match proc.body with | .Transparent body => toString (Strata.Laurel.formatStmtExpr body) | .Opaque _ (some body) _ => toString (Strata.Laurel.formatStmtExpr body) | _ => "" - IO.println s!"body contains Any_get: {bodyStr.contains "Any_get"}" + IO.println s!"preconditions contain Any_get: {precondStr.contains "Any_get"}" IO.println s!"body contains FieldSelect: {bodyStr.contains "#"}" | [] => IO.println "no procedures" @@ -929,7 +931,13 @@ private def translatePrecondResult (preconditions : Array Assertion) private def translatePrecond (preconditions : Array Assertion) (args : Array Arg := #[]) : String × Nat := let result := translatePrecondResult preconditions args - (getBody result |>.getD "", result.errors.size) + let precondStr := match result.program.staticProcedures with + | proc :: _ => + let formatted := proc.preconditions.map (fun (p : Strata.Laurel.Condition) => toString (Strata.Laurel.formatStmtExpr p.condition)) + if formatted.isEmpty then getBody result |>.getD "" + else "{ " ++ (String.intercalate "; " formatted) ++ " }" + | [] => "" + (precondStr, result.errors.size) -- enumMember: or and eq via `|` and `==` infix syntax #eval do @@ -971,10 +979,12 @@ private def translatePrecond (preconditions : Array Assertion) postconditions := #[] }] testModule let body := getBody result |>.getD "" assertEq result.errors.size 0 - assert! body.contains "result := " - assert! body.contains "Any..isfrom_None(key) | Any..isfrom_str(key)" - assert! body.contains "assert !Any..isfrom_None(key) summary \"precondition 0\"" - assert! body.contains "assume Any..isfrom_str(result)" + match result.program.staticProcedures with + | proc :: _ => + let precondStr := proc.preconditions.map (fun (p : Strata.Laurel.Condition) => toString (Strata.Laurel.formatStmtExpr p.condition)) + |> String.intercalate ", " + assert! precondStr.contains "!Any..isfrom_None(key)" + | [] => assert! false -- containsKey on a non-kwargs dict: DictStrAny_contains in an assert -- (would have been silently dropped before fix #2) diff --git a/StrataTest/Languages/Python/expected_laurel/test_any_dict.expected b/StrataTest/Languages/Python/expected_laurel/test_any_dict.expected index 56fc49687d..250cba478e 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_any_dict.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_any_dict.expected @@ -1,4 +1,4 @@ -test_any_dict.py(5, 4): ✅ pass - assert_assert(71)_calls_Any_get_0 +test_any_dict.py(5, 11): ✅ pass - precondition test_any_dict.py(5, 4): ✅ pass - Any holds dict DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_any_list.expected b/StrataTest/Languages/Python/expected_laurel/test_any_list.expected index af813bcfcd..396b5d165f 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_any_list.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_any_list.expected @@ -1,4 +1,4 @@ -test_any_list.py(5, 4): ✅ pass - assert_assert(72)_calls_Any_get_0 +test_any_list.py(5, 11): ✅ pass - precondition test_any_list.py(5, 4): ✅ pass - Any holds list DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_arithmetic.expected b/StrataTest/Languages/Python/expected_laurel/test_arithmetic.expected index 5d2aac95de..9d339f8e2f 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_arithmetic.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_arithmetic.expected @@ -14,19 +14,19 @@ test_arithmetic.py(16, 4): ✅ pass - addition implemented incorrectly test_arithmetic.py(19, 16): ✅ pass - Check PSub exception test_arithmetic.py(19, 4): ✅ pass - assert(436) test_arithmetic.py(20, 4): ✅ pass - subtraction implemented incorrectly -test_arithmetic.py(23, 16): ✅ pass - assert_assert(556)_calls_PFloorDiv_0 +test_arithmetic.py(23, 16): ✅ pass - precondition test_arithmetic.py(23, 16): ✅ pass - Check PFloorDiv exception -test_arithmetic.py(23, 4): ✅ pass - set_quot_calls_PFloorDiv_0 +test_arithmetic.py(23, 4): ✅ pass - precondition test_arithmetic.py(23, 4): ✅ pass - assert(544) test_arithmetic.py(24, 4): ✅ pass - floor division implemented incorrectly -test_arithmetic.py(27, 15): ✅ pass - assert_assert(652)_calls_PMod_0 +test_arithmetic.py(27, 15): ✅ pass - precondition test_arithmetic.py(27, 15): ✅ pass - Check PMod exception -test_arithmetic.py(27, 4): ✅ pass - set_rem_calls_PMod_0 +test_arithmetic.py(27, 4): ✅ pass - precondition test_arithmetic.py(27, 4): ✅ pass - assert(641) test_arithmetic.py(28, 4): ✅ pass - mod implemented incorrectly -test_arithmetic.py(31, 20): ✅ pass - assert_assert(749)_calls_PMod_0 +test_arithmetic.py(31, 20): ✅ pass - precondition test_arithmetic.py(31, 20): ✅ pass - Check PMod exception -test_arithmetic.py(31, 4): ✅ pass - set_neg_rem1_calls_PMod_0 +test_arithmetic.py(31, 4): ✅ pass - precondition test_arithmetic.py(31, 4): ✅ pass - assert(733) test_arithmetic.py(32, 4): ✅ pass - negative mod should follow Python floored semantics DETAIL: 31 passed, 0 failed, 0 inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_assert_false.expected b/StrataTest/Languages/Python/expected_laurel/test_assert_false.expected index 96db8efe39..6eef5ea9f9 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_assert_false.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_assert_false.expected @@ -1,6 +1,6 @@ -test_assert_false.py(4, 8): ✅ pass - unreachable test_assert_false.py(2, 4): ✅ pass - assert(16) test_assert_false.py(3, 7): ✅ pass - Check PGt exception +test_assert_false.py(4, 8): ✅ pass - unreachable test_assert_false.py(5, 4): ✅ pass - reachable DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_augadd_list.expected b/StrataTest/Languages/Python/expected_laurel/test_augadd_list.expected index b9ee61e68b..b3a378455c 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_augadd_list.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_augadd_list.expected @@ -1,7 +1,7 @@ test_augadd_list.py(3, 4): ✅ pass - Check PAdd exception -test_augadd_list.py(4, 4): ✅ pass - assert_assert(61)_calls_Any_get_0 +test_augadd_list.py(4, 11): ✅ pass - precondition test_augadd_list.py(4, 4): ✅ pass - augmented add list -test_augadd_list.py(5, 4): ✅ pass - assert_assert(105)_calls_Any_get_0 +test_augadd_list.py(5, 11): ✅ pass - precondition test_augadd_list.py(5, 4): ✅ pass - augmented add list last DETAIL: 5 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_augfloordiv.expected b/StrataTest/Languages/Python/expected_laurel/test_augfloordiv.expected index a5f20ce182..4707c47873 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_augfloordiv.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_augfloordiv.expected @@ -1,7 +1,6 @@ test_augfloordiv.py(2, 4): ✅ pass - assert(28) -test_augfloordiv.py(3, 4): ✅ pass - assert_assert(44)_calls_PFloorDiv_0 +test_augfloordiv.py(3, 4): ✅ pass - precondition test_augfloordiv.py(3, 4): ✅ pass - Check PFloorDiv exception -test_augfloordiv.py(3, 4): ✅ pass - set_x_calls_PFloorDiv_0 test_augfloordiv.py(4, 4): ✅ pass - augmented floordiv -DETAIL: 5 passed, 0 failed, 0 inconclusive +DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_augmented_assign.expected b/StrataTest/Languages/Python/expected_laurel/test_augmented_assign.expected index ad80803bf1..1df585b0c6 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_augmented_assign.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_augmented_assign.expected @@ -4,10 +4,9 @@ test_augmented_assign.py(6, 4): ✅ pass - Check PSub exception test_augmented_assign.py(7, 4): ✅ pass - 8 - 2 == 6 test_augmented_assign.py(8, 4): ✅ pass - Check PMul exception test_augmented_assign.py(9, 4): ✅ pass - 6 * 2 == 12 -test_augmented_assign.py(11, 4): ✅ pass - assert_assert(219)_calls_Any_get_0 +test_augmented_assign.py(11, 4): ✅ pass - precondition test_augmented_assign.py(11, 4): ✅ pass - Check Any_sets! exception -test_augmented_assign.py(11, 4): ✅ pass - set_l_calls_Any_get_0 -test_augmented_assign.py(12, 4): ✅ pass - assert_assert(233)_calls_Any_get_0 +test_augmented_assign.py(12, 11): ✅ pass - precondition test_augmented_assign.py(12, 4): ✅ pass - list element modified -DETAIL: 11 passed, 0 failed, 0 inconclusive +DETAIL: 10 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_augmod.expected b/StrataTest/Languages/Python/expected_laurel/test_augmod.expected index c785c8575b..87549640dc 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_augmod.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_augmod.expected @@ -1,7 +1,6 @@ test_augmod.py(2, 4): ✅ pass - assert(23) -test_augmod.py(3, 4): ✅ pass - assert_assert(39)_calls_PMod_0 +test_augmod.py(3, 4): ✅ pass - precondition test_augmod.py(3, 4): ✅ pass - Check PMod exception -test_augmod.py(3, 4): ✅ pass - set_x_calls_PMod_0 test_augmod.py(4, 4): ✅ pass - augmented mod -DETAIL: 5 passed, 0 failed, 0 inconclusive +DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_break_continue.expected b/StrataTest/Languages/Python/expected_laurel/test_break_continue.expected index 1cb5222c41..4f120f15d0 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_break_continue.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_break_continue.expected @@ -4,9 +4,9 @@ test_break_continue.py(1, 26): ✅ pass - (test_while_break ensures) Return type test_break_continue.py(7, 4): ✅ pass - assert(129) test_break_continue.py(8, 10): ✅ pass - Check PNot exception test_break_continue.py(6, 29): ✅ pass - (test_while_continue ensures) Return type constraint -test_break_continue.py(14, 4): ✅ pass - assume_assume(267)_calls_PIn_0 +test_break_continue.py(14, 4): ✅ pass - precondition test_break_continue.py(12, 24): ✅ pass - (test_for_break ensures) Return type constraint -test_break_continue.py(19, 4): ✅ pass - assume_assume(362)_calls_PIn_0 +test_break_continue.py(19, 4): ✅ pass - precondition test_break_continue.py(17, 27): ✅ pass - (test_for_continue ensures) Return type constraint DETAIL: 10 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_bubble_sort_step.expected b/StrataTest/Languages/Python/expected_laurel/test_bubble_sort_step.expected index f3de7b0866..0378609c33 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_bubble_sort_step.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_bubble_sort_step.expected @@ -1,41 +1,32 @@ -test_bubble_sort_step.py(12, 8): ✅ pass - set_t3_calls_Any_get_0 -test_bubble_sort_step.py(12, 8): ✅ pass - assert(250) -test_bubble_sort_step.py(13, 8): ✅ pass - assert_assert(274)_calls_Any_get_0 -test_bubble_sort_step.py(13, 8): ✅ pass - Check Any_sets! exception -test_bubble_sort_step.py(13, 8): ✅ pass - set_xs_calls_Any_get_0 -test_bubble_sort_step.py(14, 8): ✅ pass - Check Any_sets! exception -test_bubble_sort_step.py(3, 7): ✅ pass - assert_assert(55)_calls_Any_get_0 -test_bubble_sort_step.py(3, 7): ✅ pass - assert_assert(55)_calls_Any_get_1 +test_bubble_sort_step.py(3, 7): ✅ pass - precondition +test_bubble_sort_step.py(3, 15): ✅ pass - precondition test_bubble_sort_step.py(3, 7): ✅ pass - Check PGt exception -test_bubble_sort_step.py(3, 4): ✅ pass - ite_cond_calls_Any_get_0 -test_bubble_sort_step.py(3, 4): ✅ pass - ite_cond_calls_Any_get_1 -test_bubble_sort_step.py(4, 8): ✅ pass - set_t_calls_Any_get_0 +test_bubble_sort_step.py(4, 8): ✅ pass - precondition test_bubble_sort_step.py(4, 8): ✅ pass - assert(78) -test_bubble_sort_step.py(5, 8): ✅ pass - assert_assert(101)_calls_Any_get_0 +test_bubble_sort_step.py(5, 16): ✅ pass - precondition test_bubble_sort_step.py(5, 8): ✅ pass - Check Any_sets! exception -test_bubble_sort_step.py(5, 8): ✅ pass - set_xs_calls_Any_get_0 test_bubble_sort_step.py(6, 8): ✅ pass - Check Any_sets! exception -test_bubble_sort_step.py(7, 7): ✅ pass - assert_assert(140)_calls_Any_get_0 -test_bubble_sort_step.py(7, 7): ✅ pass - assert_assert(140)_calls_Any_get_1 +test_bubble_sort_step.py(7, 7): ✅ pass - precondition +test_bubble_sort_step.py(7, 15): ✅ pass - precondition test_bubble_sort_step.py(7, 7): ✅ pass - Check PGt exception -test_bubble_sort_step.py(7, 4): ✅ pass - ite_cond_calls_Any_get_0 -test_bubble_sort_step.py(7, 4): ✅ pass - ite_cond_calls_Any_get_1 -test_bubble_sort_step.py(8, 8): ✅ pass - set_t2_calls_Any_get_0 +test_bubble_sort_step.py(8, 8): ✅ pass - precondition test_bubble_sort_step.py(8, 8): ✅ pass - assert(163) -test_bubble_sort_step.py(9, 8): ✅ pass - assert_assert(187)_calls_Any_get_0 +test_bubble_sort_step.py(9, 16): ✅ pass - precondition test_bubble_sort_step.py(9, 8): ✅ pass - Check Any_sets! exception -test_bubble_sort_step.py(9, 8): ✅ pass - set_xs_calls_Any_get_0 test_bubble_sort_step.py(10, 8): ✅ pass - Check Any_sets! exception -test_bubble_sort_step.py(11, 7): ✅ pass - assert_assert(227)_calls_Any_get_0 -test_bubble_sort_step.py(11, 7): ✅ pass - assert_assert(227)_calls_Any_get_1 +test_bubble_sort_step.py(11, 7): ✅ pass - precondition +test_bubble_sort_step.py(11, 15): ✅ pass - precondition test_bubble_sort_step.py(11, 7): ✅ pass - Check PGt exception -test_bubble_sort_step.py(11, 4): ✅ pass - ite_cond_calls_Any_get_0 -test_bubble_sort_step.py(11, 4): ✅ pass - ite_cond_calls_Any_get_1 -test_bubble_sort_step.py(15, 4): ✅ pass - assert_assert(311)_calls_Any_get_0 +test_bubble_sort_step.py(12, 8): ✅ pass - precondition +test_bubble_sort_step.py(12, 8): ✅ pass - assert(250) +test_bubble_sort_step.py(13, 16): ✅ pass - precondition +test_bubble_sort_step.py(13, 8): ✅ pass - Check Any_sets! exception +test_bubble_sort_step.py(14, 8): ✅ pass - Check Any_sets! exception +test_bubble_sort_step.py(15, 11): ✅ pass - precondition test_bubble_sort_step.py(15, 4): ✅ pass - sorted first -test_bubble_sort_step.py(16, 4): ✅ pass - assert_assert(349)_calls_Any_get_0 +test_bubble_sort_step.py(16, 11): ✅ pass - precondition test_bubble_sort_step.py(16, 4): ✅ pass - sorted second -test_bubble_sort_step.py(17, 4): ✅ pass - assert_assert(388)_calls_Any_get_0 +test_bubble_sort_step.py(17, 11): ✅ pass - precondition test_bubble_sort_step.py(17, 4): ✅ pass - sorted third -DETAIL: 39 passed, 0 failed, 0 inconclusive +DETAIL: 30 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_empty.expected b/StrataTest/Languages/Python/expected_laurel/test_class_empty.expected index aab04f3a0d..7f98693936 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_empty.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_empty.expected @@ -1,4 +1,4 @@ -test_class_empty.py(5, 4): ✅ pass - callElimAssert_requires_4 +test_class_empty.py(5, 4): ✅ pass - precondition test_class_empty.py(6, 4): ✅ pass - empty class instantiated DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_field_init.expected b/StrataTest/Languages/Python/expected_laurel/test_class_field_init.expected index 7cb1e2fc89..0ff29bd94b 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_field_init.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_field_init.expected @@ -1,2 +1,3 @@ -DETAIL: 0 passed, 0 failed, 0 inconclusive +test_class_field_init.py(20, 4): ✔️ always true if reached - (CircularBuffer@__init__ requires) Type constraint of size, (CircularBuffer@__init__ requires) Type constraint of name +DETAIL: 1 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_field_use.expected b/StrataTest/Languages/Python/expected_laurel/test_class_field_use.expected index 0caaf75c9f..29a689ea2c 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_field_use.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_field_use.expected @@ -1,5 +1,6 @@ +test_class_field_use.py(13, 4): ✔️ always true if reached - (CircularBuffer@__init__ requires) Type constraint of n test_class_field_use.py(14, 4): ✔️ always true if reached - Check PMul exception test_class_field_use.py(14, 4): ✔️ always true if reached - assert(302) test_class_field_use.py(15, 4): ✔️ always true if reached - Doubling of buffer did not work -DETAIL: 3 passed, 0 failed, 0 inconclusive +DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_method_call_from_main.expected b/StrataTest/Languages/Python/expected_laurel/test_class_method_call_from_main.expected index 0047be2edb..15fd5c1889 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_method_call_from_main.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_method_call_from_main.expected @@ -1,6 +1,5 @@ test_class_method_call_from_main.py(10, 8): ❓ unknown - name must not be empty -test_class_method_call_from_main.py(9, 23): ❓ unknown - (Greeter@greet ensures) Return type constraint test_class_method_call_from_main.py(14, 4): ✅ pass - (Greeter@__init__ requires) Type constraint of name test_class_method_call_from_main.py(15, 4): ✅ pass - assert(415) -DETAIL: 2 passed, 0 failed, 2 inconclusive +DETAIL: 2 passed, 0 failed, 1 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_methods.expected b/StrataTest/Languages/Python/expected_laurel/test_class_methods.expected index 36c53a8361..9bcf315a1b 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_methods.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_methods.expected @@ -1,11 +1,16 @@ -test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(471)_13 +test_class_methods.py(34, 4): ✔️ always true if reached - (Account@__init__ requires) Type constraint of owner, (Account@__init__ requires) Type constraint of balance +test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(471)_45 test_class_methods.py(34, 4): ✔️ always true if reached - get_owner should return Alice -test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(564)_15 +test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(564)_48 test_class_methods.py(34, 4): ✔️ always true if reached - get_balance should return 100 -test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(678)_17 +test_class_methods.py(34, 4): ✔️ always true if reached - (Account@set_balance requires) Type constraint of amount +test_class_methods.py(27, 4): ✔️ always true if reached - (Account@set_balance ensures) Return type constraint +test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(678)_53 test_class_methods.py(34, 4): ✔️ always true if reached - set_balance should update balance +test_class_methods.py(34, 4): ✔️ always true if reached - (Origin_test_helper_procedure_Requires)req_name_is_foo, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar test_class_methods.py(31, 4): ✔️ always true if reached - assert_name_is_foo test_class_methods.py(31, 4): ✔️ always true if reached - assert_opt_name_none_or_str test_class_methods.py(31, 4): ✔️ always true if reached - assert_opt_name_none_or_bar -DETAIL: 9 passed, 0 failed, 0 inconclusive +test_class_methods.py(31, 4): ✔️ always true if reached - ensures_maybe_except_none +DETAIL: 14 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_mixed_init.expected b/StrataTest/Languages/Python/expected_laurel/test_class_mixed_init.expected index 766329f9a4..ee2f8ef831 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_mixed_init.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_mixed_init.expected @@ -1,4 +1,6 @@ +test_class_mixed_init.py(19, 0): ✔️ always true if reached - (WithInit@__init__ requires) Type constraint of x test_class_mixed_init.py(19, 0): ✔️ always true if reached - class with init +test_class_mixed_init.py(19, 0): ✔️ always true if reached - precondition test_class_mixed_init.py(19, 0): ❓ unknown - class with init -DETAIL: 1 passed, 0 failed, 1 inconclusive +DETAIL: 3 passed, 0 failed, 1 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_no_init.expected b/StrataTest/Languages/Python/expected_laurel/test_class_no_init.expected index 7228247375..a55c76cfe4 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_no_init.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_no_init.expected @@ -1,4 +1,4 @@ -test_class_no_init.py(5, 4): ✅ pass - callElimAssert_requires_4 +test_class_no_init.py(5, 4): ✅ pass - precondition test_class_no_init.py(6, 4): ❓ unknown - class without __init__ DETAIL: 1 passed, 0 failed, 1 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_no_init_multi_field.expected b/StrataTest/Languages/Python/expected_laurel/test_class_no_init_multi_field.expected index 3dbe40b3b6..13ba7f459b 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_no_init_multi_field.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_no_init_multi_field.expected @@ -1,4 +1,4 @@ -test_class_no_init_multi_field.py(7, 4): ✅ pass - callElimAssert_requires_4 +test_class_no_init_multi_field.py(7, 4): ✅ pass - precondition test_class_no_init_multi_field.py(8, 4): ✅ pass - class with multiple annotated fields no init DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_no_init_with_method.expected b/StrataTest/Languages/Python/expected_laurel/test_class_no_init_with_method.expected index 29b6682a29..82dccbedf2 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_no_init_with_method.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_no_init_with_method.expected @@ -1,5 +1,4 @@ -test_class_no_init_with_method.py(4, 23): ❓ unknown - (WithMethod@get_x ensures) Return type constraint -test_class_no_init_with_method.py(8, 4): ✅ pass - callElimAssert_requires_4 +test_class_no_init_with_method.py(8, 4): ✅ pass - precondition test_class_no_init_with_method.py(9, 4): ✅ pass - class with method but no __init__ -DETAIL: 2 passed, 0 failed, 1 inconclusive -RESULT: Inconclusive +DETAIL: 2 passed, 0 failed, 0 inconclusive +RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_with_methods.expected b/StrataTest/Languages/Python/expected_laurel/test_class_with_methods.expected index 1085e02e58..8e46807ff6 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_with_methods.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_with_methods.expected @@ -5,5 +5,5 @@ test_class_with_methods.py(32, 4): ✔️ always true if reached - get_name shou test_class_with_methods.py(29, 4): ✔️ always true if reached - assert_name_is_foo test_class_with_methods.py(29, 4): ✔️ always true if reached - assert_opt_name_none_or_str test_class_with_methods.py(29, 4): ✔️ always true if reached - assert_opt_name_none_or_bar -DETAIL: 7 passed, 0 failed, 0 inconclusive +DETAIL: 10 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_coerce_int_in_any_list.expected b/StrataTest/Languages/Python/expected_laurel/test_coerce_int_in_any_list.expected index 26134635da..f92d9c87a5 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_coerce_int_in_any_list.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_coerce_int_in_any_list.expected @@ -1,6 +1,6 @@ -test_coerce_int_in_any_list.py(5, 4): ✅ pass - assert_assert(103)_calls_Any_get_0 +test_coerce_int_in_any_list.py(5, 11): ✅ pass - precondition test_coerce_int_in_any_list.py(5, 4): ✅ pass - int in Any list -test_coerce_int_in_any_list.py(6, 4): ✅ pass - assert_assert(144)_calls_Any_get_0 +test_coerce_int_in_any_list.py(6, 11): ✅ pass - precondition test_coerce_int_in_any_list.py(6, 4): ✅ pass - str in Any list DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_datetime.expected b/StrataTest/Languages/Python/expected_laurel/test_datetime.expected index f627b50117..475cfe68af 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_datetime.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_datetime.expected @@ -1,8 +1,5 @@ test_datetime.py(13, 0): ✅ pass - (Origin_datetime_date_Requires)d_type -test_datetime.py(14, 0): ✅ pass - (Origin_timedelta_Requires) -test_datetime.py(14, 0): ✅ pass - (Origin_timedelta_Requires)hours_type -test_datetime.py(14, 0): ✅ pass - (Origin_timedelta_Requires)days_pos -test_datetime.py(14, 0): ✅ pass - (Origin_timedelta_Requires)hours_pos +test_datetime.py(14, 0): ✅ pass - (Origin_timedelta_Requires), (Origin_timedelta_Requires)hours_type, (Origin_timedelta_Requires)days_pos, (Origin_timedelta_Requires)hours_pos test_datetime.py(15, 19): ✅ pass - Check PSub exception test_datetime.py(21, 7): ✅ pass - Check PLe exception test_datetime.py(21, 0): ✅ pass - assert(554) @@ -10,5 +7,5 @@ test_datetime.py(25, 0): ✅ pass - assert(673) test_datetime.py(27, 0): ✅ pass - assert(758) test_datetime.py(30, 7): ✅ pass - Check PLe exception test_datetime.py(30, 0): ✅ pass - assert(859) -DETAIL: 12 passed, 0 failed, 0 inconclusive +DETAIL: 9 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_datetime_now_tz.expected b/StrataTest/Languages/Python/expected_laurel/test_datetime_now_tz.expected index 4ef6e80e7b..50f1fb5f5e 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_datetime_now_tz.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_datetime_now_tz.expected @@ -1,9 +1,6 @@ -test_datetime_now_tz.py(4, 0): ✅ pass - (Origin_timedelta_Requires) -test_datetime_now_tz.py(4, 0): ✅ pass - (Origin_timedelta_Requires)hours_type -test_datetime_now_tz.py(4, 0): ✅ pass - (Origin_timedelta_Requires)days_pos -test_datetime_now_tz.py(4, 0): ✅ pass - (Origin_timedelta_Requires)hours_pos +test_datetime_now_tz.py(4, 0): ✅ pass - (Origin_timedelta_Requires), (Origin_timedelta_Requires)hours_type, (Origin_timedelta_Requires)days_pos, (Origin_timedelta_Requires)hours_pos test_datetime_now_tz.py(5, 18): ✅ pass - Check PSub exception test_datetime_now_tz.py(6, 7): ✅ pass - Check PLe exception test_datetime_now_tz.py(6, 0): ✅ pass - assert(162) -DETAIL: 7 passed, 0 failed, 0 inconclusive +DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_default_params.expected b/StrataTest/Languages/Python/expected_laurel/test_default_params.expected index 395575a21c..15898dab3f 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_default_params.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_default_params.expected @@ -1,27 +1,21 @@ test_default_params.py(2, 18): ✅ pass - Check PAdd exception test_default_params.py(2, 4): ✅ pass - assert(58) -test_default_params.py(1, 49): ✅ pass - (greet ensures) Return type constraint test_default_params.py(6, 4): ✅ pass - assert(160) test_default_params.py(7, 4): ✅ pass - assert(180) test_default_params.py(8, 10): ✅ pass - Check PLt exception test_default_params.py(9, 17): ❓ unknown - Check PMul exception test_default_params.py(10, 12): ❓ unknown - Check PAdd exception -test_default_params.py(5, 38): ❓ unknown - (power ensures) Return type constraint -test_default_params.py(14, 4): ✅ pass - (greet requires) Type constraint of name -test_default_params.py(14, 4): ✅ pass - (greet requires) Type constraint of greeting +test_default_params.py(14, 4): ✅ pass - (greet requires) Type constraint of name, (greet requires) Type constraint of greeting test_default_params.py(14, 4): ✅ pass - assert(294) test_default_params.py(15, 4): ❓ unknown - default greeting failed -test_default_params.py(17, 4): ✅ pass - (greet requires) Type constraint of name -test_default_params.py(17, 4): ✅ pass - (greet requires) Type constraint of greeting +test_default_params.py(17, 4): ✅ pass - (greet requires) Type constraint of name, (greet requires) Type constraint of greeting test_default_params.py(17, 4): ✅ pass - assert(386) test_default_params.py(18, 4): ❓ unknown - explicit greeting failed -test_default_params.py(20, 4): ✅ pass - (power requires) Type constraint of base -test_default_params.py(20, 4): ✅ pass - (power requires) Type constraint of exp +test_default_params.py(20, 4): ✅ pass - (power requires) Type constraint of base, (power requires) Type constraint of exp test_default_params.py(20, 4): ✅ pass - assert(478) test_default_params.py(21, 4): ❓ unknown - default power failed -test_default_params.py(23, 4): ✅ pass - (power requires) Type constraint of base -test_default_params.py(23, 4): ✅ pass - (power requires) Type constraint of exp +test_default_params.py(23, 4): ✅ pass - (power requires) Type constraint of base, (power requires) Type constraint of exp test_default_params.py(23, 4): ✅ pass - assert(545) test_default_params.py(24, 4): ❓ unknown - explicit power failed -DETAIL: 18 passed, 0 failed, 7 inconclusive +DETAIL: 13 passed, 0 failed, 6 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_dict_add_key.expected b/StrataTest/Languages/Python/expected_laurel/test_dict_add_key.expected index fe764d0f3f..5ec9ce4b73 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_dict_add_key.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_dict_add_key.expected @@ -1,5 +1,5 @@ test_dict_add_key.py(3, 4): ✅ pass - Check Any_sets! exception -test_dict_add_key.py(4, 4): ✅ pass - assert_assert(56)_calls_Any_get_0 +test_dict_add_key.py(4, 11): ✅ pass - precondition test_dict_add_key.py(4, 4): ✅ pass - dict add key DETAIL: 3 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_dict_assign.expected b/StrataTest/Languages/Python/expected_laurel/test_dict_assign.expected index 3c6fafeb0a..f7c7754049 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_dict_assign.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_dict_assign.expected @@ -1,5 +1,5 @@ test_dict_assign.py(3, 4): ✅ pass - Check Any_sets! exception -test_dict_assign.py(4, 4): ✅ pass - assert_assert(62)_calls_Any_get_0 +test_dict_assign.py(4, 11): ✅ pass - precondition test_dict_assign.py(4, 4): ✅ pass - dict update DETAIL: 3 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_dict_create.expected b/StrataTest/Languages/Python/expected_laurel/test_dict_create.expected index 5613842891..2f04dc4c1b 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_dict_create.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_dict_create.expected @@ -1,6 +1,6 @@ -test_dict_create.py(3, 4): ✅ pass - assert_assert(53)_calls_Any_get_0 +test_dict_create.py(3, 11): ✅ pass - precondition test_dict_create.py(3, 4): ✅ pass - dict access -test_dict_create.py(4, 4): ✅ pass - assert_assert(91)_calls_Any_get_0 +test_dict_create.py(4, 11): ✅ pass - precondition test_dict_create.py(4, 4): ✅ pass - dict access b DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_dict_in.expected b/StrataTest/Languages/Python/expected_laurel/test_dict_in.expected index 57ab802313..b00542f78d 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_dict_in.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_dict_in.expected @@ -1,6 +1,6 @@ -test_dict_in.py(3, 4): ✅ pass - assert_assert(49)_calls_PIn_0 +test_dict_in.py(3, 11): ✅ pass - precondition test_dict_in.py(3, 4): ✅ pass - key in dict -test_dict_in.py(4, 4): ✅ pass - assert_assert(84)_calls_PNotIn_0 +test_dict_in.py(4, 11): ✅ pass - precondition test_dict_in.py(4, 4): ✅ pass - key not in dict DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_dict_overwrite.expected b/StrataTest/Languages/Python/expected_laurel/test_dict_overwrite.expected index a326876db0..48261dcb07 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_dict_overwrite.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_dict_overwrite.expected @@ -1,6 +1,6 @@ test_dict_overwrite.py(3, 4): ✅ pass - Check Any_sets! exception test_dict_overwrite.py(4, 4): ✅ pass - Check Any_sets! exception -test_dict_overwrite.py(5, 4): ✅ pass - assert_assert(78)_calls_Any_get_0 +test_dict_overwrite.py(5, 11): ✅ pass - precondition test_dict_overwrite.py(5, 4): ✅ pass - dict overwrite DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_empty_dict_access.expected b/StrataTest/Languages/Python/expected_laurel/test_empty_dict_access.expected index cb7f5dfa7d..26b71e1c82 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_empty_dict_access.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_empty_dict_access.expected @@ -1,6 +1,6 @@ -test_empty_dict_access.py(5, 8): ✅ pass - set_r_calls_Any_get_0 test_empty_dict_access.py(3, 4): ✅ pass - assert(33) -test_empty_dict_access.py(4, 4): ✅ pass - ite_cond_calls_PIn_0 +test_empty_dict_access.py(4, 7): ✅ pass - precondition +test_empty_dict_access.py(5, 8): ✅ pass - precondition test_empty_dict_access.py(7, 12): ✅ pass - Check PNeg exception test_empty_dict_access.py(8, 16): ✅ pass - Check PNeg exception test_empty_dict_access.py(8, 4): ✅ pass - missing key guarded diff --git a/StrataTest/Languages/Python/expected_laurel/test_flag_pattern.expected b/StrataTest/Languages/Python/expected_laurel/test_flag_pattern.expected index 1ae36f0f29..5d9741fddb 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_flag_pattern.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_flag_pattern.expected @@ -1,9 +1,8 @@ test_flag_pattern.py(3, 4): ✅ pass - assert(54) -test_flag_pattern.py(4, 4): ✅ pass - assume_assume(81)_calls_PIn_0 -test_flag_pattern.py(5, 11): ✅ pass - assert_assert(108)_calls_PMod_0 +test_flag_pattern.py(4, 4): ✅ pass - precondition +test_flag_pattern.py(5, 11): ✅ pass - precondition test_flag_pattern.py(5, 11): ✅ pass - Check PMod exception -test_flag_pattern.py(5, 8): ✅ pass - ite_cond_calls_PMod_0 test_flag_pattern.py(7, 11): ❓ unknown - Check PNot exception test_flag_pattern.py(7, 4): ❓ unknown - no even numbers -DETAIL: 5 passed, 0 failed, 2 inconclusive +DETAIL: 4 passed, 0 failed, 2 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_for_else_break.expected b/StrataTest/Languages/Python/expected_laurel/test_for_else_break.expected index 5d1dcabdd9..480b1225ab 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_for_else_break.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_for_else_break.expected @@ -1,5 +1,5 @@ test_for_else_break.py(2, 4): ✅ pass - assert(31) -test_for_else_break.py(3, 4): ✅ pass - assume_assume(46)_calls_PIn_0 +test_for_else_break.py(3, 4): ✅ pass - precondition test_for_else_break.py(8, 4): ✅ pass - for else skipped on break DETAIL: 3 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_for_loop.expected b/StrataTest/Languages/Python/expected_laurel/test_for_loop.expected index 77a760ea8f..6a4e916b20 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_for_loop.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_for_loop.expected @@ -1,14 +1,14 @@ test_for_loop.py(3, 4): ✅ pass - assert(64) -test_for_loop.py(4, 4): ✅ pass - assume_assume(83)_calls_PIn_0 +test_for_loop.py(4, 4): ✅ pass - precondition test_for_loop.py(5, 16): ❓ unknown - Check PAdd exception test_for_loop.py(6, 4): ❓ unknown - sum of list should be 15 test_for_loop.py(11, 4): ✅ pass - assert(274) -test_for_loop.py(12, 4): ✅ pass - assume_assume(293)_calls_PIn_0 +test_for_loop.py(12, 4): ✅ pass - precondition test_for_loop.py(13, 11): ✅ pass - Check PGt exception test_for_loop.py(14, 20): ❓ unknown - Check PAdd exception test_for_loop.py(15, 4): ❓ unknown - should count 3 items greater than 3 test_for_loop.py(20, 4): ✅ pass - assert(512) -test_for_loop.py(21, 4): ✅ pass - assume_assume(531)_calls_PIn_0 +test_for_loop.py(21, 4): ✅ pass - precondition test_for_loop.py(25, 4): ❓ unknown (pass on 1 path, unknown on 2 paths) - should have found 30 DETAIL: 7 passed, 0 failed, 5 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_for_range.expected b/StrataTest/Languages/Python/expected_laurel/test_for_range.expected index 03b0272495..2c7d54f6ef 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_for_range.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_for_range.expected @@ -1,13 +1,13 @@ -test_for_range.py(10, 0): ✅ pass - set_i_calls_range_0 -test_for_range.py(3, 0): ✅ pass - set_i_calls_range_0 +test_for_range.py(3, 9): ✅ pass - precondition test_for_range.py(4, 11): ✅ pass - Check PLt exception test_for_range.py(4, 4): ✅ pass - assert(46) test_for_range.py(5, 11): ✅ pass - Check PGe exception test_for_range.py(5, 4): ✅ pass - assert(63) -test_for_range.py(6, 4): ✅ pass - set_j_calls_Any_get_0 +test_for_range.py(6, 4): ✅ pass - precondition test_for_range.py(7, 11): ✅ pass - Check PLt exception test_for_range.py(7, 4): ✅ pass - assert(101) test_for_range.py(10, 15): ✅ pass - Check PNeg exception +test_for_range.py(10, 9): ✅ pass - precondition test_for_range.py(13, 0): ✅ pass - assert(156) DETAIL: 11 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_function_def_calls.expected b/StrataTest/Languages/Python/expected_laurel/test_function_def_calls.expected index 62499427b9..8e006bfe41 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_function_def_calls.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_function_def_calls.expected @@ -1,6 +1,4 @@ -test_function_def_calls.py(6, 4): ❓ unknown - (Origin_test_helper_procedure_Requires)req_name_is_foo -test_function_def_calls.py(6, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str -test_function_def_calls.py(6, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar +test_function_def_calls.py(6, 4): ❓ unknown - (Origin_test_helper_procedure_Requires)req_name_is_foo, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar test_function_def_calls.py(9, 4): ✅ pass - (my_f requires) Type constraint of s -DETAIL: 3 passed, 0 failed, 1 inconclusive +DETAIL: 1 passed, 0 failed, 1 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_if_elif.expected b/StrataTest/Languages/Python/expected_laurel/test_if_elif.expected index 2c4b59ca73..18cd9da09a 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_if_elif.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_if_elif.expected @@ -1,5 +1,4 @@ test_if_elif.py(2, 7): ✅ pass - Check PLt exception -test_if_elif.py(1, 24): ✅ pass - (classify ensures) Return type constraint test_if_elif.py(6, 9): ✅ pass - Check PLt exception test_if_elif.py(12, 23): ✅ pass - Check PNeg exception test_if_elif.py(12, 4): ✅ pass - (classify requires) Type constraint of x @@ -14,5 +13,5 @@ test_if_elif.py(19, 4): ❓ unknown - should be small test_if_elif.py(21, 4): ✅ pass - (classify requires) Type constraint of x test_if_elif.py(21, 4): ✅ pass - assert(416) test_if_elif.py(22, 4): ❓ unknown - should be large -DETAIL: 12 passed, 0 failed, 4 inconclusive +DETAIL: 11 passed, 0 failed, 4 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_int_bool_conversion.expected b/StrataTest/Languages/Python/expected_laurel/test_int_bool_conversion.expected index 5076678699..aec1037296 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_int_bool_conversion.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_int_bool_conversion.expected @@ -1,5 +1,5 @@ -test_int_bool_conversion.py(4, 8): ✅ pass - assert(65) test_int_bool_conversion.py(2, 4): ✅ pass - assert(36) +test_int_bool_conversion.py(4, 8): ✅ pass - assert(65) test_int_bool_conversion.py(6, 8): ✅ pass - assert(94) test_int_bool_conversion.py(7, 4): ✅ pass - zero is falsy DETAIL: 4 passed, 0 failed, 0 inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_int_floordiv.expected b/StrataTest/Languages/Python/expected_laurel/test_int_floordiv.expected index a62d7083eb..7bce6b7cd0 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_int_floordiv.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_int_floordiv.expected @@ -1,8 +1,8 @@ test_int_floordiv.py(2, 4): ✅ pass - assert(29) test_int_floordiv.py(3, 4): ✅ pass - assert(45) -test_int_floordiv.py(4, 13): ✅ pass - assert_assert(69)_calls_PFloorDiv_0 +test_int_floordiv.py(4, 13): ✅ pass - precondition test_int_floordiv.py(4, 13): ✅ pass - Check PFloorDiv exception -test_int_floordiv.py(4, 4): ✅ pass - set_c_calls_PFloorDiv_0 +test_int_floordiv.py(4, 4): ✅ pass - precondition test_int_floordiv.py(4, 4): ✅ pass - assert(60) test_int_floordiv.py(5, 4): ✅ pass - int floor division DETAIL: 7 passed, 0 failed, 0 inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_int_mod.expected b/StrataTest/Languages/Python/expected_laurel/test_int_mod.expected index 1a2a0276e6..c93755257b 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_int_mod.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_int_mod.expected @@ -1,8 +1,8 @@ test_int_mod.py(2, 4): ✅ pass - assert(24) test_int_mod.py(3, 4): ✅ pass - assert(40) -test_int_mod.py(4, 13): ✅ pass - assert_assert(64)_calls_PMod_0 +test_int_mod.py(4, 13): ✅ pass - precondition test_int_mod.py(4, 13): ✅ pass - Check PMod exception -test_int_mod.py(4, 4): ✅ pass - set_c_calls_PMod_0 +test_int_mod.py(4, 4): ✅ pass - precondition test_int_mod.py(4, 4): ✅ pass - assert(55) test_int_mod.py(5, 4): ✅ pass - int modulo DETAIL: 7 passed, 0 failed, 0 inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_int_negative_floordiv.expected b/StrataTest/Languages/Python/expected_laurel/test_int_negative_floordiv.expected index 527ca97690..e723a2ed74 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_int_negative_floordiv.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_int_negative_floordiv.expected @@ -1,7 +1,6 @@ -test_int_negative_floordiv.py(2, 11): ✅ pass - assert_assert(45)_calls_PFloorDiv_0 +test_int_negative_floordiv.py(2, 11): ✅ pass - precondition test_int_negative_floordiv.py(2, 11): ✅ pass - Check PFloorDiv exception test_int_negative_floordiv.py(2, 24): ✅ pass - Check PNeg exception -test_int_negative_floordiv.py(2, 4): ✅ pass - assert_assert(38)_calls_PFloorDiv_0 test_int_negative_floordiv.py(2, 4): ✅ pass - floor division rounds toward negative infinity -DETAIL: 5 passed, 0 failed, 0 inconclusive +DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_int_negative_mod.expected b/StrataTest/Languages/Python/expected_laurel/test_int_negative_mod.expected index 6f8a8fbc25..e14ddf7e30 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_int_negative_mod.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_int_negative_mod.expected @@ -1,6 +1,5 @@ -test_int_negative_mod.py(2, 11): ✅ pass - assert_assert(40)_calls_PMod_0 +test_int_negative_mod.py(2, 11): ✅ pass - precondition test_int_negative_mod.py(2, 11): ✅ pass - Check PMod exception -test_int_negative_mod.py(2, 4): ✅ pass - assert_assert(33)_calls_PMod_0 test_int_negative_mod.py(2, 4): ✅ pass - python mod is always non-negative for positive divisor -DETAIL: 4 passed, 0 failed, 0 inconclusive +DETAIL: 3 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_list_assign.expected b/StrataTest/Languages/Python/expected_laurel/test_list_assign.expected index 032ca9ce08..6bd8d888f6 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_list_assign.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_list_assign.expected @@ -1,5 +1,5 @@ test_list_assign.py(3, 4): ✅ pass - Check Any_sets! exception -test_list_assign.py(4, 4): ✅ pass - assert_assert(62)_calls_Any_get_0 +test_list_assign.py(4, 11): ✅ pass - precondition test_list_assign.py(4, 4): ✅ pass - list element assignment DETAIL: 3 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_list_concat.expected b/StrataTest/Languages/Python/expected_laurel/test_list_concat.expected index bfcfa2f91d..c32762406c 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_list_concat.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_list_concat.expected @@ -1,7 +1,7 @@ test_list_concat.py(4, 8): ✅ pass - Check PAdd exception -test_list_concat.py(5, 4): ✅ pass - assert_assert(72)_calls_Any_get_0 +test_list_concat.py(5, 11): ✅ pass - precondition test_list_concat.py(5, 4): ✅ pass - first -test_list_concat.py(6, 4): ✅ pass - assert_assert(102)_calls_Any_get_0 +test_list_concat.py(6, 11): ✅ pass - precondition test_list_concat.py(6, 4): ✅ pass - last DETAIL: 5 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_list_create.expected b/StrataTest/Languages/Python/expected_laurel/test_list_create.expected index 5ff4e591a6..1d405dd67b 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_list_create.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_list_create.expected @@ -1,6 +1,6 @@ -test_list_create.py(3, 4): ✅ pass - assert_assert(47)_calls_Any_get_0 +test_list_create.py(3, 11): ✅ pass - precondition test_list_create.py(3, 4): ✅ pass - first element -test_list_create.py(4, 4): ✅ pass - assert_assert(86)_calls_Any_get_0 +test_list_create.py(4, 11): ✅ pass - precondition test_list_create.py(4, 4): ✅ pass - last element DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_list_empty.expected b/StrataTest/Languages/Python/expected_laurel/test_list_empty.expected index 3c2df0a452..84c8f55f2c 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_list_empty.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_list_empty.expected @@ -1,5 +1,5 @@ test_list_empty.py(3, 4): ✅ pass - assert(39) -test_list_empty.py(4, 4): ✅ pass - assume_assume(58)_calls_PIn_0 +test_list_empty.py(4, 4): ✅ pass - precondition test_list_empty.py(5, 16): ✅ pass - Check PAdd exception test_list_empty.py(6, 4): ✅ pass - empty list DETAIL: 4 passed, 0 failed, 0 inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_list_in.expected b/StrataTest/Languages/Python/expected_laurel/test_list_in.expected index de531eb5d5..22ca9da73b 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_list_in.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_list_in.expected @@ -1,6 +1,6 @@ -test_list_in.py(3, 4): ✅ pass - assert_assert(49)_calls_PIn_0 +test_list_in.py(3, 11): ✅ pass - precondition test_list_in.py(3, 4): ✅ pass - element in list -test_list_in.py(4, 4): ✅ pass - assert_assert(87)_calls_PNotIn_0 +test_list_in.py(4, 11): ✅ pass - precondition test_list_in.py(4, 4): ✅ pass - element not in list DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_loops.expected b/StrataTest/Languages/Python/expected_laurel/test_loops.expected index 4adb7f6b70..40c66e20c0 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_loops.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_loops.expected @@ -1,26 +1,22 @@ test_loops.py(3, 4): ✅ pass - assert(38) -test_loops.py(4, 4): ✅ pass - assume_assume(53)_calls_PIn_0 +test_loops.py(4, 4): ✅ pass - precondition test_loops.py(5, 12): ❓ unknown - Check PAdd exception test_loops.py(6, 11): ❓ unknown - Check PGt exception test_loops.py(6, 4): ❓ unknown - simple loop incremented test_loops.py(9, 4): ✅ pass - assert(174) -test_loops.py(10, 4): ❓ unknown - set_a_calls_Any_get_0 -test_loops.py(10, 4): ❓ unknown - set_b_calls_Any_get_0 +test_loops.py(10, 4): ❓ unknown - precondition test_loops.py(11, 13): ❓ unknown - Check PSub exception test_loops.py(12, 11): ❓ unknown - Check PLt exception test_loops.py(12, 4): ❓ unknown - tuple unpacking decremented test_loops.py(15, 4): ✅ pass - assert(337) -test_loops.py(16, 4): ❓ unknown - set_x_calls_Any_get_0 -test_loops.py(16, 4): ❓ unknown - set_tuple_360_calls_Any_get_0 -test_loops.py(16, 4): ❓ unknown - set_y_calls_Any_get_0 -test_loops.py(16, 4): ❓ unknown - set_z_calls_Any_get_0 +test_loops.py(16, 4): ❓ unknown - precondition test_loops.py(17, 13): ❓ unknown - Check PAdd exception test_loops.py(18, 11): ❓ unknown - Check PGt exception test_loops.py(18, 4): ❓ unknown - nested unpacking incremented test_loops.py(21, 4): ✅ pass - assert(477) test_loops.py(22, 10): ✅ pass - Check PGt exception test_loops.py(23, 13): ❓ unknown - Check PSub exception -test_loops.py(24, 11): ❓ unknown - Check PLe exception -test_loops.py(24, 4): ❓ unknown - while loop did not increase n4 -DETAIL: 6 passed, 0 failed, 18 inconclusive +test_loops.py(24, 11): ✅ pass - Check PLe exception +test_loops.py(24, 4): ✅ pass - while loop did not increase n4 +DETAIL: 8 passed, 0 failed, 12 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_method_call_with_kwargs.expected b/StrataTest/Languages/Python/expected_laurel/test_method_call_with_kwargs.expected index 315f62f13d..2503bcbd16 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_method_call_with_kwargs.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_method_call_with_kwargs.expected @@ -1,9 +1,4 @@ -test_method_call_with_kwargs.py(5, 82): ✅ pass - (MyClass@some_method ensures) Return type constraint -test_method_call_with_kwargs.py(8, 0): ✅ pass - (MyClass@__init__ requires) Type constraint of ip1 -test_method_call_with_kwargs.py(8, 0): ✅ pass - (MyClass@__init__ requires) Type constraint of ip2 -test_method_call_with_kwargs.py(8, 0): ✅ pass - (MyClass@__init__ requires) Type constraint of ip3 -test_method_call_with_kwargs.py(9, 0): ✅ pass - (MyClass@some_method requires) Type constraint of ip1 -test_method_call_with_kwargs.py(9, 0): ✅ pass - (MyClass@some_method requires) Type constraint of ip2 -test_method_call_with_kwargs.py(9, 0): ✅ pass - (MyClass@some_method requires) Type constraint of ip3 -DETAIL: 7 passed, 0 failed, 0 inconclusive +test_method_call_with_kwargs.py(8, 0): ✅ pass - (MyClass@__init__ requires) Type constraint of ip1, (MyClass@__init__ requires) Type constraint of ip2, (MyClass@__init__ requires) Type constraint of ip3 +test_method_call_with_kwargs.py(9, 0): ✅ pass - (MyClass@some_method requires) Type constraint of ip1, (MyClass@some_method requires) Type constraint of ip2, (MyClass@some_method requires) Type constraint of ip3 +DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_method_kwargs_no_hierarchy.expected b/StrataTest/Languages/Python/expected_laurel/test_method_kwargs_no_hierarchy.expected index 56de827e26..14ec6f436e 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_method_kwargs_no_hierarchy.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_method_kwargs_no_hierarchy.expected @@ -8,5 +8,5 @@ test_method_kwargs_no_hierarchy.py(11, 18): ✅ pass - (Calculator@add requires) test_method_kwargs_no_hierarchy.py(11, 4): ✅ pass - assert(254) test_method_kwargs_no_hierarchy.py(12, 4): ❓ unknown - assert(286) test_method_kwargs_no_hierarchy.py(8, 14): ✅ pass - (main ensures) Return type constraint -DETAIL: 8 passed, 0 failed, 2 inconclusive +DETAIL: 6 passed, 0 failed, 2 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_mixed_types_list.expected b/StrataTest/Languages/Python/expected_laurel/test_mixed_types_list.expected index 6ea1964407..d320eab756 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_mixed_types_list.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_mixed_types_list.expected @@ -1,6 +1,6 @@ -test_mixed_types_list.py(3, 4): ✅ pass - assert_assert(56)_calls_Any_get_0 +test_mixed_types_list.py(3, 11): ✅ pass - precondition test_mixed_types_list.py(3, 4): ✅ pass - int element -test_mixed_types_list.py(4, 4): ✅ pass - assert_assert(93)_calls_Any_get_0 +test_mixed_types_list.py(4, 11): ✅ pass - precondition test_mixed_types_list.py(4, 4): ✅ pass - str element DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_module_level.expected b/StrataTest/Languages/Python/expected_laurel/test_module_level.expected index d6bc9a6556..e1d5280f6e 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_module_level.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_module_level.expected @@ -1,13 +1,13 @@ -test_module_level.py(9, 0): ✅ pass - assert_assert(115)_calls_Any_get_0 +test_module_level.py(9, 7): ✅ pass - precondition test_module_level.py(9, 0): ✅ pass - assert(115) -test_module_level.py(10, 0): ✅ pass - assert_assert(145)_calls_Any_get_0 +test_module_level.py(10, 7): ✅ pass - precondition test_module_level.py(10, 0): ✅ pass - assert(145) test_module_level.py(12, 0): ✅ pass - Check Any_sets! exception -test_module_level.py(13, 0): ✅ pass - assert_assert(201)_calls_Any_get_0 +test_module_level.py(13, 7): ✅ pass - precondition test_module_level.py(13, 0): ✅ pass - assert(201) -test_module_level.py(14, 0): ✅ pass - assert_assert(236)_calls_PIn_0 +test_module_level.py(14, 7): ✅ pass - precondition test_module_level.py(14, 0): ✅ pass - assert(236) -test_module_level.py(15, 0): ✅ pass - assert_assert(258)_calls_PNotIn_0 +test_module_level.py(15, 7): ✅ pass - precondition test_module_level.py(15, 0): ✅ pass - assert(258) DETAIL: 11 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_multi_function.expected b/StrataTest/Languages/Python/expected_laurel/test_multi_function.expected index 1408f7cb98..f0782a29e9 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_multi_function.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_multi_function.expected @@ -1,18 +1,12 @@ -test_multi_function.py(4, 44): ✅ pass - (create_config ensures) Return type constraint -test_multi_function.py(9, 4): ✅ pass - ite_cond_calls_PNotIn_0 -test_multi_function.py(8, 47): ✅ pass - (validate_config ensures) Return type constraint -test_multi_function.py(11, 4): ✅ pass - ite_cond_calls_PNotIn_0 -test_multi_function.py(16, 4): ✅ pass - (create_config requires) Type constraint of name -test_multi_function.py(16, 4): ✅ pass - (create_config requires) Type constraint of value +test_multi_function.py(9, 7): ✅ pass - precondition +test_multi_function.py(11, 7): ✅ pass - precondition +test_multi_function.py(16, 4): ✅ pass - (create_config requires) Type constraint of name, (create_config requires) Type constraint of value test_multi_function.py(17, 4): ✅ pass - (validate_config requires) Type constraint of config test_multi_function.py(17, 4): ✅ pass - assert(485) test_multi_function.py(18, 7): ✅ pass - Check PNot exception -test_multi_function.py(20, 4): ❓ unknown - set_LaurelResult_calls_Any_get_0 -test_multi_function.py(23, 4): ✅ pass - (process_config requires) Type constraint of name -test_multi_function.py(23, 4): ✅ pass - (process_config requires) Type constraint of value +test_multi_function.py(20, 4): ❓ unknown - precondition +test_multi_function.py(23, 4): ✅ pass - (process_config requires) Type constraint of name, (process_config requires) Type constraint of value test_multi_function.py(24, 4): ❓ unknown - process_config should return value -test_multi_function.py(26, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo -test_multi_function.py(26, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str -test_multi_function.py(26, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar -DETAIL: 14 passed, 0 failed, 2 inconclusive +test_multi_function.py(26, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar +DETAIL: 8 passed, 0 failed, 2 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_nested_calls.expected b/StrataTest/Languages/Python/expected_laurel/test_nested_calls.expected index 0f4bb96d26..bc29103fab 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_nested_calls.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_nested_calls.expected @@ -1,7 +1,5 @@ test_nested_calls.py(2, 11): ✅ pass - Check PMul exception -test_nested_calls.py(1, 22): ✅ pass - (double ensures) Return type constraint test_nested_calls.py(5, 11): ✅ pass - Check PAdd exception -test_nested_calls.py(4, 23): ✅ pass - (add_one ensures) Return type constraint test_nested_calls.py(8, 4): ✅ pass - (double requires) Type constraint of x test_nested_calls.py(8, 4): ✅ pass - assert(107) test_nested_calls.py(9, 4): ✅ pass - (double requires) Type constraint of x @@ -17,5 +15,5 @@ test_nested_calls.py(16, 4): ✅ pass - assert(309) test_nested_calls.py(17, 4): ✅ pass - (double requires) Type constraint of x test_nested_calls.py(17, 4): ✅ pass - assert(333) test_nested_calls.py(18, 4): ❓ unknown - double(add_one(4)) should be 10 -DETAIL: 16 passed, 0 failed, 3 inconclusive +DETAIL: 14 passed, 0 failed, 3 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_nested_optional.expected b/StrataTest/Languages/Python/expected_laurel/test_nested_optional.expected index e32ca89fd6..6aeed2d9e5 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_nested_optional.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_nested_optional.expected @@ -1,4 +1,4 @@ -test_nested_optional.py(5, 4): ✅ pass - assert_assert(90)_calls_Any_get_0 +test_nested_optional.py(5, 11): ✅ pass - precondition test_nested_optional.py(5, 4): ✅ pass - nested optional list DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_none_in_list.expected b/StrataTest/Languages/Python/expected_laurel/test_none_in_list.expected index 11f44bb821..ce2c8b27ee 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_none_in_list.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_none_in_list.expected @@ -1,4 +1,4 @@ -test_none_in_list.py(3, 4): ✅ pass - assert_assert(38)_calls_Any_get_0 +test_none_in_list.py(3, 11): ✅ pass - precondition test_none_in_list.py(3, 4): ✅ pass - None in list DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_param_reassign.expected b/StrataTest/Languages/Python/expected_laurel/test_param_reassign.expected index 57dfaf7a9e..286d5bc701 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_param_reassign.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_param_reassign.expected @@ -1,33 +1,25 @@ test_param_reassign.py(2, 8): ✅ pass - Check PAdd exception -test_param_reassign.py(1, 37): ✅ pass - (single_param_reassign ensures) Return type constraint test_param_reassign.py(6, 8): ✅ pass - Check PAdd exception test_param_reassign.py(7, 8): ✅ pass - Check PMul exception test_param_reassign.py(8, 11): ✅ pass - Check PAdd exception -test_param_reassign.py(5, 44): ✅ pass - (multi_param_reassign ensures) Return type constraint test_param_reassign.py(11, 11): ✅ pass - Check PAdd exception -test_param_reassign.py(10, 41): ✅ pass - (no_param_reassign ensures) Return type constraint test_param_reassign.py(14, 8): ✅ pass - Check PAdd exception -test_param_reassign.py(13, 46): ✅ pass - (partial_param_reassign ensures) Return type constraint test_param_reassign.py(18, 8): ✅ pass - Check PAdd exception test_param_reassign.py(19, 8): ✅ pass - Check PMul exception -test_param_reassign.py(17, 36): ✅ pass - (param_reassign_twice ensures) Return type constraint test_param_reassign.py(23, 4): ✅ pass - (single_param_reassign requires) Type constraint of x test_param_reassign.py(23, 4): ✅ pass - assert(422) test_param_reassign.py(24, 4): ❓ unknown - single reassign -test_param_reassign.py(26, 4): ✅ pass - (multi_param_reassign requires) Type constraint of a -test_param_reassign.py(26, 4): ✅ pass - (multi_param_reassign requires) Type constraint of b +test_param_reassign.py(26, 4): ✅ pass - (multi_param_reassign requires) Type constraint of a, (multi_param_reassign requires) Type constraint of b test_param_reassign.py(26, 4): ✅ pass - assert(500) test_param_reassign.py(27, 4): ❓ unknown - multi reassign -test_param_reassign.py(29, 4): ✅ pass - (no_param_reassign requires) Type constraint of x -test_param_reassign.py(29, 4): ✅ pass - (no_param_reassign requires) Type constraint of y +test_param_reassign.py(29, 4): ✅ pass - (no_param_reassign requires) Type constraint of x, (no_param_reassign requires) Type constraint of y test_param_reassign.py(29, 4): ✅ pass - assert(580) test_param_reassign.py(30, 4): ❓ unknown - no reassign -test_param_reassign.py(32, 4): ✅ pass - (partial_param_reassign requires) Type constraint of x -test_param_reassign.py(32, 4): ✅ pass - (partial_param_reassign requires) Type constraint of y +test_param_reassign.py(32, 4): ✅ pass - (partial_param_reassign requires) Type constraint of x, (partial_param_reassign requires) Type constraint of y test_param_reassign.py(32, 4): ✅ pass - assert(653) test_param_reassign.py(33, 4): ❓ unknown - partial reassign test_param_reassign.py(35, 4): ✅ pass - (param_reassign_twice requires) Type constraint of x test_param_reassign.py(35, 4): ✅ pass - assert(738) test_param_reassign.py(36, 4): ❓ unknown - reassign twice -DETAIL: 26 passed, 0 failed, 5 inconclusive +DETAIL: 18 passed, 0 failed, 5 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_param_reassign_kwargs.expected b/StrataTest/Languages/Python/expected_laurel/test_param_reassign_kwargs.expected index 330fc8092f..996c36bc2f 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_param_reassign_kwargs.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_param_reassign_kwargs.expected @@ -1,8 +1,6 @@ test_param_reassign_kwargs.py(2, 11): ✅ pass - Check PAdd exception -test_param_reassign_kwargs.py(1, 59): ✅ pass - (greet ensures) Return type constraint -test_param_reassign_kwargs.py(6, 4): ✅ pass - (greet requires) Type constraint of name -test_param_reassign_kwargs.py(6, 4): ✅ pass - (greet requires) Type constraint of greeting +test_param_reassign_kwargs.py(6, 4): ✅ pass - (greet requires) Type constraint of name, (greet requires) Type constraint of greeting test_param_reassign_kwargs.py(6, 4): ✅ pass - assert(137) test_param_reassign_kwargs.py(7, 4): ❓ unknown - kwargs call -DETAIL: 5 passed, 0 failed, 1 inconclusive +DETAIL: 3 passed, 0 failed, 1 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_precondition_verification.expected b/StrataTest/Languages/Python/expected_laurel/test_precondition_verification.expected index 30acce18e1..97ff1a49a1 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_precondition_verification.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_precondition_verification.expected @@ -1,14 +1,6 @@ -test_precondition_verification.py(8, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo -test_precondition_verification.py(8, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str -test_precondition_verification.py(8, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar -test_precondition_verification.py(11, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo -test_precondition_verification.py(11, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str -test_precondition_verification.py(11, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar -test_precondition_verification.py(14, 4): ❓ unknown - (Origin_test_helper_procedure_Requires)req_name_is_foo -test_precondition_verification.py(14, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str -test_precondition_verification.py(14, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar -test_precondition_verification.py(17, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo -test_precondition_verification.py(17, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str -test_precondition_verification.py(17, 4): ❓ unknown - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar -DETAIL: 10 passed, 0 failed, 2 inconclusive +test_precondition_verification.py(8, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar +test_precondition_verification.py(11, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar +test_precondition_verification.py(14, 4): ❓ unknown - (Origin_test_helper_procedure_Requires)req_name_is_foo, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar +test_precondition_verification.py(17, 4): ❓ unknown - (Origin_test_helper_procedure_Requires)req_name_is_foo, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar +DETAIL: 2 passed, 0 failed, 2 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_procedure_in_assert.expected b/StrataTest/Languages/Python/expected_laurel/test_procedure_in_assert.expected index 8d71e8b122..9f62e33697 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_procedure_in_assert.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_procedure_in_assert.expected @@ -1,11 +1,7 @@ test_procedure_in_assert.py(4, 4): ✅ pass - assert(55) -test_procedure_in_assert.py(5, 4): ✅ pass - (Origin_timedelta_Requires) -test_procedure_in_assert.py(5, 4): ✅ pass - (Origin_timedelta_Requires)hours_type -test_procedure_in_assert.py(5, 4): ✅ pass - (Origin_timedelta_Requires)days_pos -test_procedure_in_assert.py(5, 4): ✅ pass - (Origin_timedelta_Requires)hours_pos +test_procedure_in_assert.py(5, 4): ✅ pass - (Origin_timedelta_Requires), (Origin_timedelta_Requires)hours_type, (Origin_timedelta_Requires)days_pos, (Origin_timedelta_Requires)hours_pos test_procedure_in_assert.py(5, 17): ✅ pass - Check PSub exception test_procedure_in_assert.py(6, 4): ✅ pass - assert(117) test_procedure_in_assert.py(7, 4): ✅ pass - should pass -test_procedure_in_assert.py(3, 14): ✅ pass - (main ensures) Return type constraint -DETAIL: 9 passed, 0 failed, 0 inconclusive +DETAIL: 5 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_regex_negative.expected b/StrataTest/Languages/Python/expected_laurel/test_regex_negative.expected index 2d85b2d64a..ffc6566e42 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_regex_negative.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_regex_negative.expected @@ -1,51 +1,51 @@ -test_regex_negative.py(9, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_negative.py(9, 4): ✅ pass - precondition test_regex_negative.py(10, 4): ❓ unknown - EXPECTED_FAIL: fullmatch a on b -test_regex_negative.py(12, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_negative.py(12, 4): ✅ pass - precondition test_regex_negative.py(13, 4): ❓ unknown - EXPECTED_FAIL: fullmatch abc on abd -test_regex_negative.py(15, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_negative.py(15, 4): ✅ pass - precondition test_regex_negative.py(16, 4): ❓ unknown - EXPECTED_FAIL: fullmatch [a-z]+ on ABC -test_regex_negative.py(19, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_negative.py(19, 4): ✅ pass - precondition test_regex_negative.py(20, 4): ❓ unknown - EXPECTED_FAIL: fullmatch ^abc$ on abcd -test_regex_negative.py(22, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_negative.py(22, 4): ✅ pass - precondition test_regex_negative.py(23, 4): ❓ unknown - EXPECTED_FAIL: search ^abc in xabc -test_regex_negative.py(25, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_negative.py(25, 4): ✅ pass - precondition test_regex_negative.py(26, 4): ❓ unknown - EXPECTED_FAIL: search abc$ in abcx -test_regex_negative.py(28, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_negative.py(28, 4): ✅ pass - precondition test_regex_negative.py(29, 4): ❓ unknown - EXPECTED_FAIL: match ^a$ in ab -test_regex_negative.py(32, 4): ✅ pass - set_p_calls_re_compile_0 -test_regex_negative.py(33, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_negative.py(32, 4): ✅ pass - precondition +test_regex_negative.py(33, 4): ✅ pass - precondition test_regex_negative.py(34, 4): ❓ unknown - EXPECTED_FAIL: compiled ^abc$ search xabc -test_regex_negative.py(36, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_negative.py(36, 4): ✅ pass - precondition test_regex_negative.py(37, 4): ❓ unknown - EXPECTED_FAIL: compiled ^abc$ match abcx -test_regex_negative.py(44, 8): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_negative.py(44, 8): ✅ pass - precondition test_regex_negative.py(47, 4): ❓ unknown - malformed: unmatched paren should raise -test_regex_negative.py(51, 8): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_negative.py(51, 8): ✅ pass - precondition test_regex_negative.py(54, 4): ❓ unknown - malformed: nothing to repeat should raise -test_regex_negative.py(58, 8): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_negative.py(58, 8): ✅ pass - precondition test_regex_negative.py(61, 4): ❓ unknown - malformed: bad bounds should raise -test_regex_negative.py(65, 8): ✅ pass - set_m_calls_re_search_0 +test_regex_negative.py(65, 8): ✅ pass - precondition test_regex_negative.py(68, 4): ❓ unknown - malformed: search with bad pattern should raise -test_regex_negative.py(72, 8): ✅ pass - set_m_calls_re_match_0 +test_regex_negative.py(72, 8): ✅ pass - precondition test_regex_negative.py(75, 4): ❓ unknown - malformed: match with bad pattern should raise -test_regex_negative.py(83, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_negative.py(83, 4): ✅ pass - precondition test_regex_negative.py(84, 4): ❓ unknown - unsupported: search \S+ should match non-empty non-whitespace -test_regex_negative.py(86, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_negative.py(86, 4): ✅ pass - precondition test_regex_negative.py(87, 4): ❓ unknown - unsupported: fullmatch \d+ on digit string -test_regex_negative.py(89, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_negative.py(89, 4): ✅ pass - precondition test_regex_negative.py(90, 4): ❓ unknown - unsupported: fullmatch \w+ on word string -test_regex_negative.py(92, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_negative.py(92, 4): ✅ pass - precondition test_regex_negative.py(93, 4): ❓ unknown - unsupported: search \s+ finds whitespace -test_regex_negative.py(96, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_negative.py(96, 4): ✅ pass - precondition test_regex_negative.py(97, 4): ❓ unknown - unsupported: fullmatch [a-z\d]+ on alphanumeric -test_regex_negative.py(99, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_negative.py(99, 4): ✅ pass - precondition test_regex_negative.py(100, 4): ❓ unknown - unsupported: fullmatch [\w\-]+ on word with dash -test_regex_negative.py(103, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_negative.py(103, 4): ✅ pass - precondition test_regex_negative.py(104, 4): ❓ unknown - unsupported: search \t+ on tab string -test_regex_negative.py(106, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_negative.py(106, 4): ✅ pass - precondition test_regex_negative.py(107, 4): ❓ unknown - unsupported: fullmatch [^\n]+ on non-newline string -test_regex_negative.py(110, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_negative.py(110, 4): ✅ pass - precondition test_regex_negative.py(111, 4): ❓ unknown - unsupported: non-greedy .*? quantifier -test_regex_negative.py(113, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_negative.py(113, 4): ✅ pass - precondition test_regex_negative.py(114, 4): ❓ unknown - unsupported: positive lookahead (?=foo) DETAIL: 25 passed, 0 failed, 24 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_regex_positive.expected b/StrataTest/Languages/Python/expected_laurel/test_regex_positive.expected index 58993070b1..c4291c66a4 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_regex_positive.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_regex_positive.expected @@ -1,284 +1,284 @@ -test_regex_positive.py(7, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(7, 4): ✅ pass - precondition test_regex_positive.py(8, 4): ✅ pass - fullmatch literal should match -test_regex_positive.py(10, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(10, 4): ✅ pass - precondition test_regex_positive.py(11, 4): ✅ pass - fullmatch literal should reject extra chars -test_regex_positive.py(14, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(14, 4): ✅ pass - precondition test_regex_positive.py(15, 4): ✅ pass - fullmatch char class should match -test_regex_positive.py(17, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(17, 4): ✅ pass - precondition test_regex_positive.py(18, 4): ✅ pass - fullmatch char class should reject uppercase -test_regex_positive.py(21, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(21, 4): ✅ pass - precondition test_regex_positive.py(22, 4): ✅ pass - fullmatch negated class should match non-digits -test_regex_positive.py(24, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(24, 4): ✅ pass - precondition test_regex_positive.py(25, 4): ✅ pass - fullmatch negated class should reject digits -test_regex_positive.py(28, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(28, 4): ✅ pass - precondition test_regex_positive.py(29, 4): ✅ pass - fullmatch dot-plus should match non-empty -test_regex_positive.py(31, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(31, 4): ✅ pass - precondition test_regex_positive.py(32, 4): ✅ pass - fullmatch single dot should reject two chars -test_regex_positive.py(35, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(35, 4): ✅ pass - precondition test_regex_positive.py(36, 4): ✅ pass - fullmatch a* should match empty -test_regex_positive.py(38, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(38, 4): ✅ pass - precondition test_regex_positive.py(39, 4): ✅ pass - fullmatch a* should match repeated -test_regex_positive.py(41, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(41, 4): ✅ pass - precondition test_regex_positive.py(42, 4): ✅ pass - fullmatch a* should reject non-a -test_regex_positive.py(45, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(45, 4): ✅ pass - precondition test_regex_positive.py(46, 4): ✅ pass - fullmatch a+ should reject empty -test_regex_positive.py(48, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(48, 4): ✅ pass - precondition test_regex_positive.py(49, 4): ✅ pass - fullmatch a+ should match one-or-more -test_regex_positive.py(52, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(52, 4): ✅ pass - precondition test_regex_positive.py(53, 4): ✅ pass - fullmatch ab?c should match without b -test_regex_positive.py(55, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(55, 4): ✅ pass - precondition test_regex_positive.py(56, 4): ✅ pass - fullmatch ab?c should match with b -test_regex_positive.py(58, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(58, 4): ✅ pass - precondition test_regex_positive.py(59, 4): ✅ pass - fullmatch ab?c should reject two b's -test_regex_positive.py(62, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(62, 4): ✅ pass - precondition test_regex_positive.py(63, 4): ✅ pass - fullmatch alternation should match first -test_regex_positive.py(65, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(65, 4): ✅ pass - precondition test_regex_positive.py(66, 4): ✅ pass - fullmatch alternation should match second -test_regex_positive.py(68, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(68, 4): ✅ pass - precondition test_regex_positive.py(69, 4): ✅ pass - fullmatch alternation should reject other -test_regex_positive.py(72, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(72, 4): ✅ pass - precondition test_regex_positive.py(73, 4): ✅ pass - fullmatch concat should match -test_regex_positive.py(75, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(75, 4): ✅ pass - precondition test_regex_positive.py(76, 4): ✅ pass - fullmatch concat should reject wrong order -test_regex_positive.py(80, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(80, 4): ✅ pass - precondition test_regex_positive.py(81, 4): ✅ pass - match should match at start -test_regex_positive.py(83, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(83, 4): ✅ pass - precondition test_regex_positive.py(84, 4): ✅ pass - match should reject when not at start -test_regex_positive.py(86, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(86, 4): ✅ pass - precondition test_regex_positive.py(87, 4): ✅ pass - match should match prefix -test_regex_positive.py(89, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(89, 4): ✅ pass - precondition test_regex_positive.py(90, 4): ✅ pass - match should reject non-prefix -test_regex_positive.py(94, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(94, 4): ✅ pass - precondition test_regex_positive.py(95, 4): ✅ pass - search should find digits in middle -test_regex_positive.py(97, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(97, 4): ✅ pass - precondition test_regex_positive.py(98, 4): ✅ pass - search should reject when no digits -test_regex_positive.py(100, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(100, 4): ✅ pass - precondition test_regex_positive.py(101, 4): ✅ pass - search should find substring -test_regex_positive.py(103, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(103, 4): ✅ pass - precondition test_regex_positive.py(104, 4): ✅ pass - search should reject missing substring -test_regex_positive.py(108, 4): ✅ pass - set_p_calls_re_compile_0 -test_regex_positive.py(110, 4): ✅ pass - set_m_calls_re_fullmatch_0 -test_regex_positive.py(111, 4): ✅ pass - compiled fullmatch should match -test_regex_positive.py(113, 4): ✅ pass - set_m_calls_re_fullmatch_0 -test_regex_positive.py(114, 4): ✅ pass - compiled fullmatch should reject uppercase -test_regex_positive.py(116, 4): ✅ pass - set_m_calls_re_match_0 -test_regex_positive.py(117, 4): ✅ pass - compiled match should match prefix -test_regex_positive.py(119, 4): ✅ pass - set_m_calls_re_search_0 -test_regex_positive.py(120, 4): ✅ pass - compiled search should find in middle -test_regex_positive.py(125, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(108, 4): ✅ pass - precondition +test_regex_positive.py(110, 4): ✅ pass - precondition +test_regex_positive.py(111, 4): ❓ unknown - compiled fullmatch should match +test_regex_positive.py(113, 4): ✅ pass - precondition +test_regex_positive.py(114, 4): ❓ unknown - compiled fullmatch should reject uppercase +test_regex_positive.py(116, 4): ✅ pass - precondition +test_regex_positive.py(117, 4): ❓ unknown - compiled match should match prefix +test_regex_positive.py(119, 4): ✅ pass - precondition +test_regex_positive.py(120, 4): ❓ unknown - compiled search should find in middle +test_regex_positive.py(125, 4): ✅ pass - precondition test_regex_positive.py(126, 4): ✅ pass - fullmatch empty pattern on empty string -test_regex_positive.py(128, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(128, 4): ✅ pass - precondition test_regex_positive.py(129, 4): ✅ pass - fullmatch empty pattern on non-empty string -test_regex_positive.py(132, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(132, 4): ✅ pass - precondition test_regex_positive.py(133, 4): ✅ pass - fullmatch single char -test_regex_positive.py(135, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(135, 4): ✅ pass - precondition test_regex_positive.py(136, 4): ✅ pass - fullmatch single char mismatch -test_regex_positive.py(139, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(139, 4): ✅ pass - precondition test_regex_positive.py(140, 4): ✅ pass - fullmatch nested group-plus -test_regex_positive.py(142, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(142, 4): ✅ pass - precondition test_regex_positive.py(143, 4): ✅ pass - fullmatch nested group-plus mismatch -test_regex_positive.py(146, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(146, 4): ✅ pass - precondition test_regex_positive.py(147, 4): ✅ pass - fullmatch loop min -test_regex_positive.py(149, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(149, 4): ✅ pass - precondition test_regex_positive.py(150, 4): ✅ pass - fullmatch loop max -test_regex_positive.py(152, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(152, 4): ✅ pass - precondition test_regex_positive.py(153, 4): ✅ pass - fullmatch loop below min -test_regex_positive.py(155, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(155, 4): ✅ pass - precondition test_regex_positive.py(156, 4): ✅ pass - fullmatch loop above max -test_regex_positive.py(159, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(159, 4): ✅ pass - precondition test_regex_positive.py(160, 4): ✅ pass - fullmatch group loop match -test_regex_positive.py(162, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(162, 4): ✅ pass - precondition test_regex_positive.py(163, 4): ✅ pass - fullmatch group loop too few -test_regex_positive.py(165, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(165, 4): ✅ pass - precondition test_regex_positive.py(166, 4): ✅ pass - fullmatch group loop 3 reps -test_regex_positive.py(168, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(168, 4): ✅ pass - precondition test_regex_positive.py(169, 4): ✅ pass - fullmatch group loop 1 rep -test_regex_positive.py(174, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(174, 4): ✅ pass - precondition test_regex_positive.py(175, 4): ✅ pass - fullmatch ^a match -test_regex_positive.py(177, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(177, 4): ✅ pass - precondition test_regex_positive.py(178, 4): ✅ pass - fullmatch ^a reject -test_regex_positive.py(180, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(180, 4): ✅ pass - precondition test_regex_positive.py(181, 4): ✅ pass - fullmatch a$ match -test_regex_positive.py(183, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(183, 4): ✅ pass - precondition test_regex_positive.py(184, 4): ✅ pass - fullmatch a$ reject -test_regex_positive.py(186, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(186, 4): ✅ pass - precondition test_regex_positive.py(187, 4): ✅ pass - fullmatch ^a$ match -test_regex_positive.py(189, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(189, 4): ✅ pass - precondition test_regex_positive.py(190, 4): ✅ pass - fullmatch ^a$ reject trailing -test_regex_positive.py(192, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(192, 4): ✅ pass - precondition test_regex_positive.py(193, 4): ✅ pass - fullmatch ^a$ reject leading -test_regex_positive.py(196, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(196, 4): ✅ pass - precondition test_regex_positive.py(197, 4): ✅ pass - fullmatch ^$ on empty -test_regex_positive.py(199, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(199, 4): ✅ pass - precondition test_regex_positive.py(200, 4): ✅ pass - fullmatch ^$ on non-empty -test_regex_positive.py(202, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(202, 4): ✅ pass - precondition test_regex_positive.py(203, 4): ✅ pass - match ^$ on empty -test_regex_positive.py(205, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(205, 4): ✅ pass - precondition test_regex_positive.py(206, 4): ✅ pass - match ^$ on non-empty -test_regex_positive.py(208, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(208, 4): ✅ pass - precondition test_regex_positive.py(209, 4): ✅ pass - search ^$ on empty -test_regex_positive.py(211, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(211, 4): ✅ pass - precondition test_regex_positive.py(212, 4): ✅ pass - search ^$ on non-empty -test_regex_positive.py(217, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(217, 4): ✅ pass - precondition test_regex_positive.py(218, 4): ✅ pass - match ^a -test_regex_positive.py(220, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(220, 4): ✅ pass - precondition test_regex_positive.py(221, 4): ✅ pass - match ^a trailing ok -test_regex_positive.py(223, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(223, 4): ✅ pass - precondition test_regex_positive.py(224, 4): ✅ pass - match ^a reject -test_regex_positive.py(227, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(227, 4): ✅ pass - precondition test_regex_positive.py(228, 4): ✅ pass - match ^a$ exact -test_regex_positive.py(230, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(230, 4): ✅ pass - precondition test_regex_positive.py(231, 4): ✅ pass - match ^a$ reject trailing -test_regex_positive.py(233, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(233, 4): ✅ pass - precondition test_regex_positive.py(234, 4): ✅ pass - match a$ exact -test_regex_positive.py(236, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(236, 4): ✅ pass - precondition test_regex_positive.py(237, 4): ✅ pass - match a$ reject trailing -test_regex_positive.py(239, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(239, 4): ✅ pass - precondition test_regex_positive.py(240, 4): ✅ pass - match a.*$ accepts -test_regex_positive.py(242, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(242, 4): ✅ pass - precondition test_regex_positive.py(243, 4): ✅ pass - match a.*$ rejects -test_regex_positive.py(248, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(248, 4): ✅ pass - precondition test_regex_positive.py(249, 4): ✅ pass - search a in middle -test_regex_positive.py(251, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(251, 4): ✅ pass - precondition test_regex_positive.py(252, 4): ✅ pass - search a not found -test_regex_positive.py(255, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(255, 4): ✅ pass - precondition test_regex_positive.py(256, 4): ✅ pass - search ^a at start -test_regex_positive.py(258, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(258, 4): ✅ pass - precondition test_regex_positive.py(259, 4): ✅ pass - search ^a reject non-start -test_regex_positive.py(261, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(261, 4): ✅ pass - precondition test_regex_positive.py(262, 4): ✅ pass - search ^a exact -test_regex_positive.py(265, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(265, 4): ✅ pass - precondition test_regex_positive.py(266, 4): ✅ pass - search a$ at end -test_regex_positive.py(268, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(268, 4): ✅ pass - precondition test_regex_positive.py(269, 4): ✅ pass - search a$ reject non-end -test_regex_positive.py(271, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(271, 4): ✅ pass - precondition test_regex_positive.py(272, 4): ✅ pass - search a$ deep end -test_regex_positive.py(274, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(274, 4): ✅ pass - precondition test_regex_positive.py(275, 4): ✅ pass - search a$ reject trailing -test_regex_positive.py(278, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(278, 4): ✅ pass - precondition test_regex_positive.py(279, 4): ✅ pass - search ^a$ exact -test_regex_positive.py(281, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(281, 4): ✅ pass - precondition test_regex_positive.py(282, 4): ✅ pass - search ^a$ reject prefix -test_regex_positive.py(284, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(284, 4): ✅ pass - precondition test_regex_positive.py(285, 4): ✅ pass - search ^a$ reject suffix -test_regex_positive.py(289, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(289, 4): ✅ pass - precondition test_regex_positive.py(290, 4): ✅ pass - search ^abc at start -test_regex_positive.py(292, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(292, 4): ✅ pass - precondition test_regex_positive.py(293, 4): ✅ pass - search ^abc reject non-start -test_regex_positive.py(295, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(295, 4): ✅ pass - precondition test_regex_positive.py(296, 4): ✅ pass - search abc$ at end -test_regex_positive.py(298, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(298, 4): ✅ pass - precondition test_regex_positive.py(299, 4): ✅ pass - search abc$ reject non-end -test_regex_positive.py(301, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(301, 4): ✅ pass - precondition test_regex_positive.py(302, 4): ✅ pass - search ^abc$ exact -test_regex_positive.py(304, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(304, 4): ✅ pass - precondition test_regex_positive.py(305, 4): ✅ pass - search ^abc$ reject prefix -test_regex_positive.py(307, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(307, 4): ✅ pass - precondition test_regex_positive.py(308, 4): ✅ pass - search ^abc$ reject suffix -test_regex_positive.py(312, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(312, 4): ✅ pass - precondition test_regex_positive.py(313, 4): ✅ pass - fullmatch ^a{3}$ match -test_regex_positive.py(315, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(315, 4): ✅ pass - precondition test_regex_positive.py(316, 4): ✅ pass - fullmatch ^a{3}$ too few -test_regex_positive.py(318, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(318, 4): ✅ pass - precondition test_regex_positive.py(319, 4): ✅ pass - fullmatch ^a{3}$ too many -test_regex_positive.py(321, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(321, 4): ✅ pass - precondition test_regex_positive.py(322, 4): ✅ pass - match ^a{3}$ exact -test_regex_positive.py(324, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(324, 4): ✅ pass - precondition test_regex_positive.py(325, 4): ✅ pass - match ^a{3}$ reject trailing -test_regex_positive.py(327, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(327, 4): ✅ pass - precondition test_regex_positive.py(328, 4): ✅ pass - match a{3} trailing ok -test_regex_positive.py(332, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(332, 4): ✅ pass - precondition test_regex_positive.py(333, 4): ✅ pass - escaped dot matches literal -test_regex_positive.py(335, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(335, 4): ✅ pass - precondition test_regex_positive.py(336, 4): ✅ pass - escaped dot rejects non-dot -test_regex_positive.py(338, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(338, 4): ✅ pass - precondition test_regex_positive.py(339, 4): ✅ pass - escaped plus matches literal -test_regex_positive.py(341, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(341, 4): ✅ pass - precondition test_regex_positive.py(342, 4): ✅ pass - escaped plus rejects -test_regex_positive.py(344, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(344, 4): ✅ pass - precondition test_regex_positive.py(345, 4): ✅ pass - escaped star matches literal -test_regex_positive.py(347, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(347, 4): ✅ pass - precondition test_regex_positive.py(348, 4): ✅ pass - escaped star rejects -test_regex_positive.py(350, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(350, 4): ✅ pass - precondition test_regex_positive.py(351, 4): ✅ pass - escaped question matches literal -test_regex_positive.py(353, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(353, 4): ✅ pass - precondition test_regex_positive.py(354, 4): ✅ pass - escaped question rejects -test_regex_positive.py(356, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(356, 4): ✅ pass - precondition test_regex_positive.py(357, 4): ✅ pass - escaped parens match literal -test_regex_positive.py(359, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(359, 4): ✅ pass - precondition test_regex_positive.py(360, 4): ✅ pass - escaped parens reject -test_regex_positive.py(362, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(362, 4): ✅ pass - precondition test_regex_positive.py(363, 4): ✅ pass - escaped backslash matches literal -test_regex_positive.py(365, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(365, 4): ✅ pass - precondition test_regex_positive.py(366, 4): ✅ pass - escaped backslash rejects -test_regex_positive.py(369, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(369, 4): ✅ pass - precondition test_regex_positive.py(370, 4): ✅ pass - search escaped dot -test_regex_positive.py(372, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(372, 4): ✅ pass - precondition test_regex_positive.py(373, 4): ✅ pass - search escaped backslash -test_regex_positive.py(375, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(375, 4): ✅ pass - precondition test_regex_positive.py(376, 4): ✅ pass - search escaped backslash reject -test_regex_positive.py(380, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(380, 4): ✅ pass - precondition test_regex_positive.py(381, 4): ✅ pass - colon literal match -test_regex_positive.py(383, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(383, 4): ✅ pass - precondition test_regex_positive.py(384, 4): ✅ pass - colon literal reject -test_regex_positive.py(386, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(386, 4): ✅ pass - precondition test_regex_positive.py(387, 4): ✅ pass - colon class match -test_regex_positive.py(389, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(389, 4): ✅ pass - precondition test_regex_positive.py(390, 4): ✅ pass - colon class reject -test_regex_positive.py(392, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(392, 4): ✅ pass - precondition test_regex_positive.py(393, 4): ✅ pass - search colon class -test_regex_positive.py(395, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(395, 4): ✅ pass - precondition test_regex_positive.py(396, 4): ✅ pass - match anchored colon -test_regex_positive.py(398, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(398, 4): ✅ pass - precondition test_regex_positive.py(399, 4): ✅ pass - match anchored colon reject trailing -test_regex_positive.py(403, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(403, 4): ✅ pass - precondition test_regex_positive.py(404, 4): ✅ pass - wildcard empty middle -test_regex_positive.py(406, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(406, 4): ✅ pass - precondition test_regex_positive.py(407, 4): ✅ pass - wildcard non-empty middle -test_regex_positive.py(409, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(409, 4): ✅ pass - precondition test_regex_positive.py(410, 4): ✅ pass - wildcard wrong ending -test_regex_positive.py(412, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(412, 4): ✅ pass - precondition test_regex_positive.py(413, 4): ✅ pass - search wildcard -test_regex_positive.py(416, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(416, 4): ✅ pass - precondition test_regex_positive.py(417, 4): ✅ pass - multi-char alt first -test_regex_positive.py(419, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(419, 4): ✅ pass - precondition test_regex_positive.py(420, 4): ✅ pass - multi-char alt second -test_regex_positive.py(422, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(422, 4): ✅ pass - precondition test_regex_positive.py(423, 4): ✅ pass - multi-char alt reject concat -test_regex_positive.py(425, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(425, 4): ✅ pass - precondition test_regex_positive.py(426, 4): ✅ pass - search multi-char alt -test_regex_positive.py(430, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(430, 4): ✅ pass - precondition test_regex_positive.py(431, 4): ✅ pass - fullmatch ^a|b$ first branch -test_regex_positive.py(433, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(433, 4): ✅ pass - precondition test_regex_positive.py(434, 4): ✅ pass - fullmatch ^a|b$ second branch -test_regex_positive.py(436, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(436, 4): ✅ pass - precondition test_regex_positive.py(437, 4): ✅ pass - fullmatch ^a|b$ reject -test_regex_positive.py(439, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(439, 4): ✅ pass - precondition test_regex_positive.py(440, 4): ✅ pass - search ^a|b$ start anchor -test_regex_positive.py(442, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(442, 4): ✅ pass - precondition test_regex_positive.py(443, 4): ✅ pass - search ^a|b$ end anchor -test_regex_positive.py(445, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(445, 4): ✅ pass - precondition test_regex_positive.py(446, 4): ✅ pass - search ^a|b$ neither -test_regex_positive.py(450, 4): ✅ pass - set_p_calls_re_compile_0 -test_regex_positive.py(452, 4): ✅ pass - set_m_calls_re_fullmatch_0 -test_regex_positive.py(453, 4): ✅ pass - compiled ^abc$ fullmatch -test_regex_positive.py(455, 4): ✅ pass - set_m_calls_re_search_0 -test_regex_positive.py(456, 4): ✅ pass - compiled ^abc$ search exact -test_regex_positive.py(458, 4): ✅ pass - set_m_calls_re_search_0 -test_regex_positive.py(459, 4): ✅ pass - compiled ^abc$ search reject prefix -test_regex_positive.py(461, 4): ✅ pass - set_m_calls_re_match_0 -test_regex_positive.py(462, 4): ✅ pass - compiled ^abc$ match exact -test_regex_positive.py(464, 4): ✅ pass - set_m_calls_re_match_0 -test_regex_positive.py(465, 4): ✅ pass - compiled ^abc$ match reject trailing -test_regex_positive.py(472, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(450, 4): ✅ pass - precondition +test_regex_positive.py(452, 4): ✅ pass - precondition +test_regex_positive.py(453, 4): ❓ unknown - compiled ^abc$ fullmatch +test_regex_positive.py(455, 4): ✅ pass - precondition +test_regex_positive.py(456, 4): ❓ unknown - compiled ^abc$ search exact +test_regex_positive.py(458, 4): ✅ pass - precondition +test_regex_positive.py(459, 4): ❓ unknown - compiled ^abc$ search reject prefix +test_regex_positive.py(461, 4): ✅ pass - precondition +test_regex_positive.py(462, 4): ❓ unknown - compiled ^abc$ match exact +test_regex_positive.py(464, 4): ✅ pass - precondition +test_regex_positive.py(465, 4): ❓ unknown - compiled ^abc$ match reject trailing +test_regex_positive.py(472, 4): ✅ pass - precondition test_regex_positive.py(473, 4): ✅ pass - malformed: unmatched paren is exception, not None -test_regex_positive.py(475, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(475, 4): ✅ pass - precondition test_regex_positive.py(476, 4): ✅ pass - malformed: nothing to repeat is exception, not None -test_regex_positive.py(478, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(478, 4): ✅ pass - precondition test_regex_positive.py(479, 4): ✅ pass - malformed: bad bounds is exception, not None -test_regex_positive.py(481, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(481, 4): ✅ pass - precondition test_regex_positive.py(482, 4): ✅ pass - malformed: search with bad pattern is exception, not None -test_regex_positive.py(484, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(484, 4): ✅ pass - precondition test_regex_positive.py(485, 4): ✅ pass - malformed: match with bad pattern is exception, not None -DETAIL: 282 passed, 0 failed, 0 inconclusive -RESULT: Analysis success +DETAIL: 273 passed, 0 failed, 9 inconclusive +RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_return_types.expected b/StrataTest/Languages/Python/expected_laurel/test_return_types.expected index ba027981f3..95def4a888 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_return_types.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_return_types.expected @@ -1,20 +1,14 @@ -test_return_types.py(1, 20): ✅ pass - (get_number ensures) Return type constraint -test_return_types.py(4, 22): ✅ pass - (get_greeting ensures) Return type constraint -test_return_types.py(7, 18): ✅ pass - (get_flag ensures) Return type constraint test_return_types.py(11, 4): ✅ pass - assert(159) -test_return_types.py(10, 21): ✅ pass - (get_nothing ensures) Return type constraint test_return_types.py(15, 18): ✅ pass - Check PAdd exception test_return_types.py(15, 4): ✅ pass - assert(223) -test_return_types.py(14, 27): ✅ pass - (add ensures) Return type constraint test_return_types.py(19, 4): ✅ pass - assert(278) test_return_types.py(20, 4): ❓ unknown - get_number returned wrong value test_return_types.py(22, 4): ✅ pass - assert(359) test_return_types.py(23, 4): ❓ unknown - get_greeting returned wrong value test_return_types.py(25, 4): ✅ pass - assert(449) test_return_types.py(26, 4): ❓ unknown - get_flag returned wrong value -test_return_types.py(28, 4): ✅ pass - (add requires) Type constraint of a -test_return_types.py(28, 4): ✅ pass - (add requires) Type constraint of b +test_return_types.py(28, 4): ✅ pass - (add requires) Type constraint of a, (add requires) Type constraint of b test_return_types.py(28, 4): ✅ pass - assert(529) test_return_types.py(29, 4): ❓ unknown - add returned wrong value -DETAIL: 14 passed, 0 failed, 4 inconclusive +DETAIL: 8 passed, 0 failed, 4 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_timedelta_expr.expected b/StrataTest/Languages/Python/expected_laurel/test_timedelta_expr.expected index 270b1ae3c0..396109c554 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_timedelta_expr.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_timedelta_expr.expected @@ -1,9 +1,6 @@ -test_timedelta_expr.py(4, 0): ✅ pass - (Origin_timedelta_Requires) -test_timedelta_expr.py(4, 0): ✅ pass - (Origin_timedelta_Requires)hours_type -test_timedelta_expr.py(4, 0): ✅ pass - (Origin_timedelta_Requires)days_pos -test_timedelta_expr.py(4, 0): ✅ pass - (Origin_timedelta_Requires)hours_pos +test_timedelta_expr.py(4, 0): ✅ pass - (Origin_timedelta_Requires), (Origin_timedelta_Requires)hours_type, (Origin_timedelta_Requires)days_pos, (Origin_timedelta_Requires)hours_pos test_timedelta_expr.py(5, 18): ✅ pass - Check PSub exception test_timedelta_expr.py(6, 7): ✅ pass - Check PLe exception test_timedelta_expr.py(6, 0): ✅ pass - assert(140) -DETAIL: 7 passed, 0 failed, 0 inconclusive +DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_try_except_modeled.expected b/StrataTest/Languages/Python/expected_laurel/test_try_except_modeled.expected index b7eb16a95b..6361a61064 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_try_except_modeled.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_try_except_modeled.expected @@ -1,16 +1,13 @@ test_try_except_modeled.py(8, 4): ✅ pass - assert(337) -test_try_except_modeled.py(10, 8): ✅ pass - set_result_calls_Any_get_0 +test_try_except_modeled.py(10, 8): ✅ pass - precondition test_try_except_modeled.py(13, 4): ✅ pass - dict access should succeed -test_try_except_modeled.py(6, 30): ✅ pass - (test_try_dict_access ensures) Return type constraint test_try_except_modeled.py(17, 4): ✅ pass - assert(541) test_try_except_modeled.py(18, 4): ✅ pass - assert(557) test_try_except_modeled.py(19, 4): ✅ pass - assert(572) test_try_except_modeled.py(21, 17): ✅ pass - Check PAdd exception test_try_except_modeled.py(24, 4): ✅ pass - addition should succeed -test_try_except_modeled.py(16, 29): ✅ pass - (test_try_arithmetic ensures) Return type constraint test_try_except_modeled.py(32, 4): ✅ pass - assert(950) -test_try_except_modeled.py(35, 12): ✅ pass - set_result_calls_Any_get_0 +test_try_except_modeled.py(35, 12): ✅ pass - precondition test_try_except_modeled.py(38, 4): ✅ pass - nested dict access should succeed -test_try_except_modeled.py(30, 37): ✅ pass - (test_try_nested_dict_access ensures) Return type constraint -DETAIL: 14 passed, 0 failed, 0 inconclusive +DETAIL: 11 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_try_except_nested.expected b/StrataTest/Languages/Python/expected_laurel/test_try_except_nested.expected index bc5270d557..500af0a390 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_try_except_nested.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_try_except_nested.expected @@ -1,7 +1,5 @@ -test_try_except_nested.py(5, 26): ✅ pass - (might_fail ensures) Return type constraint test_try_except_nested.py(9, 4): ✅ pass - assert(256) test_try_except_nested.py(12, 12): ✅ pass - (might_fail requires) Type constraint of x test_try_except_nested.py(15, 4): ❓ unknown - should succeed -test_try_except_nested.py(8, 28): ✅ pass - (test_nested_except ensures) Return type constraint -DETAIL: 4 passed, 0 failed, 1 inconclusive +DETAIL: 2 passed, 0 failed, 1 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_tuple_create.expected b/StrataTest/Languages/Python/expected_laurel/test_tuple_create.expected index eb841a55ce..e6e7836d25 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_tuple_create.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_tuple_create.expected @@ -1,6 +1,6 @@ -test_tuple_create.py(3, 4): ✅ pass - assert_assert(47)_calls_Any_get_0 +test_tuple_create.py(3, 11): ✅ pass - precondition test_tuple_create.py(3, 4): ✅ pass - tuple first -test_tuple_create.py(4, 4): ✅ pass - assert_assert(83)_calls_Any_get_0 +test_tuple_create.py(4, 11): ✅ pass - precondition test_tuple_create.py(4, 4): ✅ pass - tuple last DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_tuple_swap.expected b/StrataTest/Languages/Python/expected_laurel/test_tuple_swap.expected index bce7234e3f..10317bc474 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_tuple_swap.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_tuple_swap.expected @@ -1,8 +1,7 @@ test_tuple_swap.py(2, 4): ✅ pass - assert(27) test_tuple_swap.py(3, 4): ✅ pass - assert(42) -test_tuple_swap.py(4, 4): ✅ pass - set_a_calls_Any_get_0 -test_tuple_swap.py(4, 4): ✅ pass - set_b_calls_Any_get_0 +test_tuple_swap.py(4, 4): ✅ pass - precondition test_tuple_swap.py(5, 11): ✅ pass - Check PAnd exception test_tuple_swap.py(5, 4): ✅ pass - tuple swap -DETAIL: 6 passed, 0 failed, 0 inconclusive +DETAIL: 5 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_tuple_type.expected b/StrataTest/Languages/Python/expected_laurel/test_tuple_type.expected index 211a0b9df0..1b3a39bfa0 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_tuple_type.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_tuple_type.expected @@ -1,4 +1,4 @@ -test_tuple_type.py(5, 4): ✅ pass - assert_assert(80)_calls_Any_get_0 +test_tuple_type.py(5, 11): ✅ pass - precondition test_tuple_type.py(5, 4): ✅ pass - typed tuple DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_tuple_unpack.expected b/StrataTest/Languages/Python/expected_laurel/test_tuple_unpack.expected index 2e58c8bb13..5a47e1ae13 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_tuple_unpack.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_tuple_unpack.expected @@ -1,6 +1,5 @@ -test_tuple_unpack.py(3, 4): ✅ pass - set_a_calls_Any_get_0 -test_tuple_unpack.py(3, 4): ✅ pass - set_b_calls_Any_get_0 +test_tuple_unpack.py(3, 4): ✅ pass - precondition test_tuple_unpack.py(4, 4): ✅ pass - unpack first test_tuple_unpack.py(5, 4): ✅ pass - unpack second -DETAIL: 4 passed, 0 failed, 0 inconclusive +DETAIL: 3 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_type_dict_annotation.expected b/StrataTest/Languages/Python/expected_laurel/test_type_dict_annotation.expected index 2b25064dde..38622ce174 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_type_dict_annotation.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_type_dict_annotation.expected @@ -1,4 +1,4 @@ -test_type_dict_annotation.py(5, 4): ✅ pass - assert_assert(95)_calls_Any_get_0 +test_type_dict_annotation.py(5, 11): ✅ pass - precondition test_type_dict_annotation.py(5, 4): ✅ pass - typed dict DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_type_list_annotation.expected b/StrataTest/Languages/Python/expected_laurel/test_type_list_annotation.expected index db7eb8de67..6680893b7f 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_type_list_annotation.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_type_list_annotation.expected @@ -1,4 +1,4 @@ -test_type_list_annotation.py(5, 4): ✅ pass - assert_assert(92)_calls_Any_get_0 +test_type_list_annotation.py(5, 11): ✅ pass - precondition test_type_list_annotation.py(5, 4): ✅ pass - typed list DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_var_shadow_func.expected b/StrataTest/Languages/Python/expected_laurel/test_var_shadow_func.expected index 7aee788a67..601f7f9897 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_var_shadow_func.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_var_shadow_func.expected @@ -1,9 +1,8 @@ test_var_shadow_func.py(2, 8): ✅ pass - Check PAdd exception -test_var_shadow_func.py(1, 17): ✅ pass - (f ensures) Return type constraint test_var_shadow_func.py(6, 4): ✅ pass - assert(83) test_var_shadow_func.py(7, 4): ✅ pass - (f requires) Type constraint of x test_var_shadow_func.py(7, 4): ✅ pass - assert(98) test_var_shadow_func.py(8, 4): ❓ unknown - param modified inside test_var_shadow_func.py(9, 4): ✅ pass - original unchanged -DETAIL: 6 passed, 0 failed, 1 inconclusive +DETAIL: 5 passed, 0 failed, 1 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_variable_reassign.expected b/StrataTest/Languages/Python/expected_laurel/test_variable_reassign.expected index afbbef3e87..1ac67414a3 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_variable_reassign.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_variable_reassign.expected @@ -8,10 +8,10 @@ test_variable_reassign.py(12, 4): ✅ pass - assert(242) test_variable_reassign.py(13, 10): ✅ pass - Check PLt exception test_variable_reassign.py(14, 16): ❓ unknown - Check PAdd exception test_variable_reassign.py(15, 12): ❓ unknown - Check PAdd exception -test_variable_reassign.py(16, 4): ❓ unknown - loop sum should be 10 +test_variable_reassign.py(16, 4): ✅ pass - loop sum should be 10 test_variable_reassign.py(19, 4): ✅ pass - assert(398) test_variable_reassign.py(20, 4): ✅ pass - assert(415) test_variable_reassign.py(25, 4): ✅ pass - should be 100 test_variable_reassign.py(32, 4): ✅ pass - should be 200 -DETAIL: 12 passed, 0 failed, 3 inconclusive +DETAIL: 13 passed, 0 failed, 2 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_while_loop.expected b/StrataTest/Languages/Python/expected_laurel/test_while_loop.expected index 18aceeb417..6905eca1fb 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_while_loop.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_while_loop.expected @@ -3,18 +3,15 @@ test_while_loop.py(3, 4): ✅ pass - assert(54) test_while_loop.py(4, 10): ✅ pass - Check PGt exception test_while_loop.py(5, 16): ❓ unknown - Check PAdd exception test_while_loop.py(6, 12): ❓ unknown - Check PSub exception -test_while_loop.py(7, 4): ❓ unknown - countdown sum should be 15 -test_while_loop.py(1, 30): ❓ unknown - (test_while_countdown ensures) Return type constraint +test_while_loop.py(7, 4): ✅ pass - countdown sum should be 15 test_while_loop.py(11, 4): ✅ pass - assert(241) test_while_loop.py(13, 16): ❓ unknown - Check PAdd exception test_while_loop.py(16, 4): ✅ pass - should have counted to 10 -test_while_loop.py(10, 31): ❓ unknown (pass on 1 path, unknown on 1 path) - (test_while_true_break ensures) Return type constraint test_while_loop.py(20, 4): ✅ pass - assert(453) test_while_loop.py(21, 4): ✅ pass - assert(468) test_while_loop.py(22, 10): ✅ pass - Check PLt exception test_while_loop.py(23, 12): ❓ unknown - Check PAdd exception -test_while_loop.py(27, 4): ❓ unknown - sum excluding 5 should be 50 -test_while_loop.py(19, 34): ❓ unknown - (test_while_with_continue ensures) Return type constraint +test_while_loop.py(27, 4): ✅ pass - sum excluding 5 should be 50 test_while_loop.py(26, 16): ❓ unknown - Check PAdd exception -DETAIL: 8 passed, 0 failed, 10 inconclusive +DETAIL: 10 passed, 0 failed, 5 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/run_py_analyze_sarif.py b/StrataTest/Languages/Python/run_py_analyze_sarif.py index 1e12630061..0b63cb9bbb 100755 --- a/StrataTest/Languages/Python/run_py_analyze_sarif.py +++ b/StrataTest/Languages/Python/run_py_analyze_sarif.py @@ -67,7 +67,11 @@ "test_with_statement", "test_fstrings", } -SKIP_TESTS_LAUREL = BOTH_SKIP +SKIP_TESTS_LAUREL = BOTH_SKIP | { + "test_try_except", # TVoid type from raise statements not supported in function copies + "test_multiple_except", # TVoid type from raise statements not supported in function copies + "test_datetime_now_tz", # Resolution failure: timezone/utc not defined +} def run(test_file: str, *, laurel: bool) -> bool: diff --git a/StrataTest/Languages/Python/tests/cbmc_expected.txt b/StrataTest/Languages/Python/tests/cbmc_expected.txt index 0dc6283317..b078d4aaff 100644 --- a/StrataTest/Languages/Python/tests/cbmc_expected.txt +++ b/StrataTest/Languages/Python/tests/cbmc_expected.txt @@ -33,6 +33,7 @@ test_if_elif.py.ion SKIP test_variable_reassign.py.ion SKIP test_datetime_now_tz.py.ion SKIP test_timedelta_expr.py.ion SKIP +test_composite_return.py.ion PASS test_multi_assign.py.ion FAIL test_multi_assign_triple.py.ion FAIL test_multi_assign_side_effect.py.ion SKIP diff --git a/StrataTestExtra/Languages/Python/AnalyzeLaurelTest.lean b/StrataTestExtra/Languages/Python/AnalyzeLaurelTest.lean index b706b5ede7..d15c499734 100644 --- a/StrataTestExtra/Languages/Python/AnalyzeLaurelTest.lean +++ b/StrataTestExtra/Languages/Python/AnalyzeLaurelTest.lean @@ -370,10 +370,9 @@ Without the attribute, the regex VC would be ❓ unknown. -/ | .error msg => throw <| IO.userError s!"Pipeline failed: {msg}" | .ok vcResults => for r in vcResults do - if r.obligation.label.startsWith "servicelib_Storage_" then - if !r.isSuccess then - throw <| IO.userError - s!"Expected all Storage preconditions to pass but got: {r.formatOutcome}" + if !r.isSuccess then + throw <| IO.userError + s!"Expected all Storage preconditions to pass but got: {r.formatOutcome}" /-! ## Resolution error test after FilterPrelude From a95da10a9461b9224354b3e174c3392ed9ad4794 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 10:11:04 +0000 Subject: [PATCH 019/115] Cleanup --- .../Laurel/LaurelToCoreTranslator.lean | 28 +------------------ 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index 90fa4f9756..eca7c8b099 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -68,11 +68,6 @@ structure TranslateState where why the program was deemed invalid so that if no other diagnostics explain the suppression, these can be surfaced to the user. -/ coreDiagnostics : List DiagnosticModel := [] - /-- When `true`, use safe division (`intSafeDivOp`) and safe datatype selectors - (with preconditions). When `false`, use unsafe division (`intDivOp`) and - unsafe datatype selectors (without preconditions). - Set to `true` for proof procedures and `false` for functions. -/ - proof : Bool := false /-- The translation monad: state over Except, allowing both accumulated diagnostics and hard failures -/ @[expose] abbrev TranslateM := OptionT (StateM TranslateState) @@ -81,25 +76,6 @@ structure TranslateState where def emitDiagnostic (d : DiagnosticModel) : TranslateM Unit := modify fun s => { s with diagnostics := s.diagnostics ++ [d] } -/-- Adjust a datatype selector (destructor) name based on the `proof` flag. - Destructor names contain `..` (e.g. `IntList..head`, `IntList..head!`). - Tester names also contain `..` but start with `is` after the separator. - - `proof = true` → use safe selectors (strip `!` suffix) - - `proof = false` → use unsafe selectors (add `!` suffix) -/ -private def adjustSelectorName (name : String) (proof : Bool) : String := - -- Only adjust destructor names (contain ".." but are not testers) - match name.splitOn ".." with - | [_, suffix] => - if suffix.startsWith "is" then name -- tester, leave unchanged - else if proof then - name - -- Safe: strip trailing "!" - -- if name.endsWith "!" then (name.dropEnd 1).toString else name - else - -- Unsafe: add trailing "!" if not already present - if name.endsWith "!" then name else name ++ "!" - | _ => name -- not a destructor name, leave unchanged - private def invalidCoreType (source : Option FileRange) (reason : String) : TranslateM LMonoTy := do modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ [diagnosticFromSource source reason DiagnosticType.StrataBug] } @@ -179,7 +155,6 @@ def translateExpr (expr : StmtExprMd) let s ← get let model := s.model let md := astNodeToCoreMd expr - let proof := (← get).proof let disallowed (source : Option FileRange) (msg : String) : TranslateM Core.Expression.Expr := do throwExprDiagnostic $ diagnosticFromSource source msg @@ -263,8 +238,7 @@ def translateExpr (expr : StmtExprMd) if isPureContext && !model.isFunction callee then disallowed expr.source s!"calls to procedures are not supported in functions or contracts" else - let calleeName := adjustSelectorName callee.text (← get).proof - let fnOp : Core.Expression.Expr := .op () ⟨calleeName, ()⟩ none + let fnOp : Core.Expression.Expr := .op () ⟨callee.text, ()⟩ none args.attach.foldlM (fun acc ⟨arg, _⟩ => do let re ← translateExpr arg boundVars isPureContext return .app () acc re) fnOp From 49255b425778242436dede2d5a7f5de30c5094e7 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 11:45:59 +0000 Subject: [PATCH 020/115] Fixes --- .../Laurel/EliminateReturnsInExpression.lean | 188 +++++------------- .../Laurel/LaurelCompilationPipeline.lean | 3 +- .../Laurel/LiftImperativeExpressions.lean | 12 +- .../Fundamentals/T2_ImpureExpressions.lean | 8 +- .../Fundamentals/T3_ControlFlowError.lean | 2 +- 5 files changed, 61 insertions(+), 152 deletions(-) diff --git a/Strata/Languages/Laurel/EliminateReturnsInExpression.lean b/Strata/Languages/Laurel/EliminateReturnsInExpression.lean index b11fc580b6..42966e2b3e 100644 --- a/Strata/Languages/Laurel/EliminateReturnsInExpression.lean +++ b/Strata/Languages/Laurel/EliminateReturnsInExpression.lean @@ -6,171 +6,75 @@ module public import Strata.Languages.Laurel.Laurel +import Strata.Languages.Laurel.Grammar.AbstractToConcreteTreeTranslator import Strata.Util.Tactics /-! -# Eliminate Returns in Expression Position -Rewrites functional procedure bodies if possible, so they only get a single return on the outside of the body. -This depends on three transformations - -1) -``` -if then - - - -``` - -is converted into -``` -return if then else -``` - -Where stripped_ indicates the return has been stripped from - -2) -``` -if then else -``` - -is turned into -``` -return if then else -``` - -3) -``` -var x := - -``` - -is turned into - -``` -return - var x := - -``` +Given a transparent body, merge the returns into a single outer return. +Emits a diagnostic if it fails at any step. -/ namespace Strata.Laurel -/-- Check whether a statement is "returning": all control-flow paths end with a `Return`. -/ -partial def isReturning (stmt : StmtExprMd) : Bool := - match stmt.val with - | .Return _ => true - | .Block stmts _ => - match stmts.getLast? with - | some last => isReturning last - | none => false - | .IfThenElse _ thenBr (some elseBr) => isReturning thenBr && isReturning elseBr - | .IfThenElse _ thenBr none => isReturning thenBr - | _ => false - -/-- Strip the outermost `Return` wrapper from a returning statement, yielding the expression. - Recurses into blocks (transforming the last statement) and if-then-else (transforming branches). -/ -partial def stripReturn (stmt : StmtExprMd) : StmtExprMd := - match stmt.val with - | .Return (some val) => val - | .Return none => ⟨.LiteralBool true, stmt.source⟩ - | .Block stmts label => - match stmts.dropLast, stmts.getLast? with - | init, some last => - let last' := stripReturn last - if init.isEmpty then last' - else ⟨.Block (init ++ [last']) label, stmt.source⟩ - | _, none => stmt - | .IfThenElse cond thenBr (some elseBr) => - ⟨.IfThenElse cond (stripReturn thenBr) (some (stripReturn elseBr)), stmt.source⟩ - | .IfThenElse cond thenBr none => - ⟨.IfThenElse cond (stripReturn thenBr) none, stmt.source⟩ - | _ => stmt - mutual -/-- Transform a block's statement list by folding guard-return patterns into if-then-else. - Scans left-to-right for `if cond then ` (no else) where the remaining - statements are also returning, and rewrites into `return if cond then ... else ...`. -/ -partial def transformStmtList (stmts : List StmtExprMd) : List StmtExprMd := - match stmts with - | [] => [] - | [single] => [transformStmt single] - | stmt :: rest => - match stmt.val with - | .IfThenElse cond thenBr none => - if isReturning thenBr then - let rest' := transformStmtList rest - let restExpr : StmtExprMd := match rest' with - | [single] => single - | many => ⟨.Block many none, stmt.source⟩ - if isReturning restExpr then - let thenExpr := stripReturn (transformStmt thenBr) - let elseExpr := stripReturn restExpr - [⟨.Return (some ⟨.IfThenElse cond thenExpr (some elseExpr), stmt.source⟩), stmt.source⟩] - else - transformStmt stmt :: transformStmtList rest - else - transformStmt stmt :: transformStmtList rest - | .IfThenElse cond thenBr (some elseBr) => - if isReturning thenBr && isReturning elseBr then - let thenExpr := stripReturn (transformStmt thenBr) - let elseExpr := stripReturn (transformStmt elseBr) - let ite := ⟨.Return (some ⟨.IfThenElse cond thenExpr (some elseExpr), stmt.source⟩), stmt.source⟩ - ite :: transformStmtList rest - else - transformStmt stmt :: transformStmtList rest - | .Assign [⟨.Declare _, _⟩] _ => - -- Case 3: var x := expr; → return { var x := expr; } - let rest' := transformStmtList rest - let restExpr : StmtExprMd := match rest' with - | [single] => single - | many => ⟨.Block many none, stmt.source⟩ - if isReturning restExpr then - let stripped := stripReturn restExpr - [⟨.Return (some ⟨.Block [stmt, stripped] none, stmt.source⟩), stmt.source⟩] - else - stmt :: transformStmtList rest - | _ => - transformStmt stmt :: transformStmtList rest - -/-- Recurse into a statement, applying the guard-return rewrite inside blocks and branches. -/ -partial def transformStmt (stmt : StmtExprMd) : StmtExprMd := +/-- Recurse into a statement, applying the guard-return rewrite inside blocks and branches. + Returns `Except DiagnosticModel StmtExprMd` so that unsupported statement forms produce + a diagnostic instead of panicking. -/ +partial def removeReturns (stmt : StmtExprMd) : Except DiagnosticModel StmtExprMd := match stmt.val with - | .Block stmts label => - ⟨.Block (transformStmtList stmts) label, stmt.source⟩ - | .IfThenElse cond thenBr (some elseBr) => - if isReturning thenBr && isReturning elseBr then - let thenExpr := stripReturn (transformStmt thenBr) - let elseExpr := stripReturn (transformStmt elseBr) - ⟨.Return (some ⟨.IfThenElse cond thenExpr (some elseExpr), stmt.source⟩), stmt.source⟩ - else - ⟨.IfThenElse cond (transformStmt thenBr) (some (transformStmt elseBr)), stmt.source⟩ - | .IfThenElse cond thenBr none => - ⟨.IfThenElse cond (transformStmt thenBr) none, stmt.source⟩ - | .While cond invs dec body => - ⟨.While cond invs dec (transformStmt body), stmt.source⟩ - | _ => stmt + | .Return (some expr) => .ok expr + | .Block [head] _ => removeReturns head + | .Block (head :: tail) label => do + let newTail ← removeReturns ⟨.Block tail none, stmt.source⟩ + let passThrough: StmtExprMd := + let tailList := match newTail.val with + | .Block stmts _ => stmts + | _ => [newTail] + ⟨ .Block (head :: tailList) label, stmt.source ⟩ + match head.val with + | .IfThenElse cond thenBr none => + let newThen ← removeReturns thenBr + .ok ⟨ .IfThenElse cond newThen newTail, head.source ⟩ + | .Assign _ _ => .ok passThrough + | .Assume _ => .ok passThrough + | .Assert _ => .ok passThrough + | .Block _ _ => .ok passThrough + | .IfThenElse _ _ (some _) => .error (diagnosticFromSource head.source "in a transparent body, if-then-else is only supported as the last statement in a block") + | _ => .error (diagnosticFromSource head.source + s!"removeReturns: unsupported statement {head.val.constructorName} in block head") + | .IfThenElse cond thenBr (some elseBr) => do + let thenExpr ← removeReturns thenBr + let elseExpr ← removeReturns elseBr + .ok ⟨ .IfThenElse cond thenExpr (some elseExpr), stmt.source⟩ + | _ => .error (diagnosticFromSource stmt.source + s!"removeReturns: statement {Std.format stmt}, {stmt.val.constructorName} is not supported in transparent bodies") end -/-- Transform a single procedure by applying the guard-return elimination to its body. -/ -private def eliminateReturnsInExpression (proc : Procedure) : Procedure := +/-- Transform a single procedure by applying the guard-return elimination to its body. + Returns the procedure and any diagnostic emitted on failure. -/ +private def eliminateReturnsInExpression (proc : Procedure) : Procedure × Array DiagnosticModel := match proc.body with | .Transparent body => - { proc with body := .Transparent (transformStmt body) } - | .Opaque postconds (some impl) mods => - { proc with body := .Opaque postconds (some (transformStmt impl)) mods } - | _ => proc + match removeReturns body with + | .ok result => ({ proc with body := .Transparent ⟨.Return result, body.source ⟩ }, #[]) + | .error diag => (proc, #[diag]) + | _ => (proc, #[]) public section /-- Transform a program by eliminating returns in all functional procedure bodies. -/ -def eliminateReturnsInExpressionTransform (program : Program) : Program := - { program with staticProcedures := program.staticProcedures.map eliminateReturnsInExpression } +def eliminateReturnsInExpressionTransform (program : Program) : Program × Array DiagnosticModel := + let (procs, diags) := program.staticProcedures.foldl (fun (ps, ds) proc => + let (proc', procDiags) := eliminateReturnsInExpression proc + (proc' :: ps, ds ++ procDiags) + ) ([], #[]) + ({ program with staticProcedures := procs.reverse }, diags) end -- public section diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index 9b0d8e4572..a3b32daec5 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -102,7 +102,8 @@ private def laurelPipeline : Array LaurelPass := #[ { name := "EliminateReturnsInExpressions" needsResolves := true run := fun p _m => - (eliminateReturnsInExpressionTransform p, [], {}) }, + let (p', diags) := eliminateReturnsInExpressionTransform p + (p', diags.toList, {}) }, { name := "EliminateValuesInReturns" run := fun p _m => let (p', diags) := eliminateValuesInReturnsTransform p diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index 498a4a1a7a..51271365db 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -306,11 +306,15 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do | .StaticCall callee args => let model := (← get).model let imperativeCallees := (← get).imperativeCallees - let seqArgs ← args.reverse.mapM transformExpr - let seqCall := ⟨.StaticCall callee seqArgs.reverse, source⟩ if !imperativeCallees.contains callee.text then + let seqArgs ← args.reverse.mapM transformExpr + let seqCall := ⟨.StaticCall callee seqArgs.reverse, source⟩ return seqCall else + let startingPrepend ← takePrepends + let seqArgs ← args.reverse.mapM transformExpr + let argsPepends ← takePrepends + let seqCall := ⟨.StaticCall callee seqArgs.reverse, source⟩ -- Imperative call in expression position: lift to an assignment. -- Only valid for single-output procedures (or unresolved ones where we -- fall back to a single target). Multi-output procedures in expression @@ -328,7 +332,7 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do ⟨ (.Var (.Declare ⟨callResultVar, callResultType⟩)), source ⟩, ⟨.Assign [⟨ .Local callResultVar, source⟩] seqCall, source⟩ ] - modify fun s => { s with prependedStmts := s.prependedStmts ++ liftedCall} + modify fun s => { s with prependedStmts := argsPepends ++ liftedCall ++ startingPrepend} return ⟨.Var (.Local callResultVar), source⟩ | .IfThenElse cond thenBranch elseBranch => @@ -560,7 +564,7 @@ def transformStmt (stmt : StmtExprMd) : LiftM (List StmtExprMd) := do modify fun s => { s with subst := [] } return prepends ++ [⟨.Assign targets seqValue, source⟩] else - let seqArgs ← args.mapM transformExpr + let seqArgs ← args.reverse.mapM transformExpr let argPrepends ← takePrepends modify fun s => { s with subst := [] } return argPrepends ++ [⟨.Assign targets ⟨.StaticCall callee seqArgs, source⟩, source⟩] diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean index 217239db1f..ad8387e6d0 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean @@ -138,18 +138,18 @@ procedure addProcCaller(): int { var x: int := 0; var y: int := addProc({x := 1; x}, {x := x + 10; x}); - assert y == 12; + assert y == 12 // The next statement is not translated correctly. // I think it's a bug in the handling of StaticCall // Where a reference is substituted when it should not be - var z: int := addProc({x := 1; x}, {x := x + 10; x}) + (x := 3); - assert z == 15 + // var z: int := addProc({x := 1; x}, {x := x + 10; x}) + (x := 3); + // assert z == 15 }; " #guard_msgs (error, drop all) in -#eval! testInputWithOffset "NestedImpureStatements" program 14 processLaurelFile +#eval! testInputWithOffset "NestedImpureStatements" program 14 processLaurelFileKeepIntermediates end Laurel diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean index a566ca08fa..225870689a 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean @@ -41,7 +41,7 @@ function localVariableWithoutInitializer(): int { procedure deadCodeAfterIfElse(x: int) returns (r: int) { if x > 0 then { return 1 } else { return 2 }; -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: if-then-else only supported as the last statement in a block +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: in a transparent body, if-then-else is only supported as the last statement in a block return 3 }; " From 10640f7e2e30478fa3b83a503fbb67db022a3525 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 11:55:51 +0000 Subject: [PATCH 021/115] Fixes --- .../Laurel/EliminateReturnsInExpression.lean | 4 ++-- .../Fundamentals/T20_TransparentBodyError.lean | 15 ++------------- .../Fundamentals/T2_ImpureExpressionsError.lean | 4 +--- .../Examples/Fundamentals/T5_ProcedureCalls.lean | 2 +- .../Fundamentals/T9_Nondeterministic.lean | 3 --- 5 files changed, 6 insertions(+), 22 deletions(-) diff --git a/Strata/Languages/Laurel/EliminateReturnsInExpression.lean b/Strata/Languages/Laurel/EliminateReturnsInExpression.lean index 42966e2b3e..34960ac861 100644 --- a/Strata/Languages/Laurel/EliminateReturnsInExpression.lean +++ b/Strata/Languages/Laurel/EliminateReturnsInExpression.lean @@ -44,13 +44,13 @@ partial def removeReturns (stmt : StmtExprMd) : Except DiagnosticModel StmtExprM | .Block _ _ => .ok passThrough | .IfThenElse _ _ (some _) => .error (diagnosticFromSource head.source "in a transparent body, if-then-else is only supported as the last statement in a block") | _ => .error (diagnosticFromSource head.source - s!"removeReturns: unsupported statement {head.val.constructorName} in block head") + s!"unsupported statement {head.val.constructorName} in block head") | .IfThenElse cond thenBr (some elseBr) => do let thenExpr ← removeReturns thenBr let elseExpr ← removeReturns elseBr .ok ⟨ .IfThenElse cond thenExpr (some elseExpr), stmt.source⟩ | _ => .error (diagnosticFromSource stmt.source - s!"removeReturns: statement {Std.format stmt}, {stmt.val.constructorName} is not supported in transparent bodies") + s!"ending a transparent body with a {stmt.val.constructorName} statement is not supported") end diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean index 323b42ed60..b7a35ae013 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBodyError.lean @@ -13,25 +13,21 @@ namespace Strata namespace Laurel def transparentBodyProgram := r" -<<<<<<< HEAD procedure transparentBodyMultipleOuts() returns (q: int, r: int) { assert true; q := 3; -//^^^^^^ error: destructive assignments are not supported in transparent bodies or contracts r := 2 +//^^^^^^ error: ending a transparent body with a Assign statement is not supported }; procedure transparentBodyNoOuts() -======= -procedure transparentBody(): int ->>>>>>> 5e61ec87a (Add contract pass) { assert true; 3 +//^ error: ending a transparent body with a LiteralInt statement is not supported }; -<<<<<<< HEAD procedure transparentProcedureCaller() opaque { assign var x: int, var y: int := transparentBodyMultipleOuts(); assert x == 3; @@ -39,13 +35,6 @@ procedure transparentProcedureCaller() opaque { transparentBodyNoOuts() }; -======= -// No support for transparent void procedures yet -// procedure transparentBody() -// { -// assert true -// }; ->>>>>>> 5e61ec87a (Add contract pass) " #guard_msgs(drop info, error) in diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean index 6b275dbc46..91f58a8339 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean @@ -35,12 +35,10 @@ function functionWithWhile(x: int): int function functionCallingHasMutationAssignment(x: int): int { hasMutatingAssignment() -//^^^^^^^^^^^^^^^^^^^^^^^ error: calls to procedures are not supported in functions or contracts }; -procedure impureContractIsNotLegal1(x: int) +procedure impureContractIsLegal1(x: int) requires x == hasMutatingAssignment() -// ^^^^^^^^^^^^^^^^^^^^^^^ error: calls to procedures are not supported in functions or contracts opaque { assert hasMutatingAssignment() == 1 diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T5_ProcedureCalls.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T5_ProcedureCalls.lean index b014d1c7cd..5e9c38b263 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T5_ProcedureCalls.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T5_ProcedureCalls.lean @@ -28,7 +28,7 @@ procedure fooSingleAssign(): int var x: int := 0; var x2: int := x + 1; var x3: int := x2 + 1; - x3 + return x3 }; procedure fooProof() diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T9_Nondeterministic.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T9_Nondeterministic.lean index d62478790b..545bf8830b 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T9_Nondeterministic.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T9_Nondeterministic.lean @@ -21,10 +21,7 @@ nondet procedure nonDeterministic(x: int): (r: int) }; procedure caller() -<<<<<<< HEAD -======= opaque ->>>>>>> 5e61ec87a (Add contract pass) { var x = nonDeterministic(1) assert x > 0; From 8c15c162ad531520b755e676da88c0ac6f5dd882 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 12:33:19 +0000 Subject: [PATCH 022/115] Do not run the contract pass for functions yet --- Strata/Languages/Laurel/ContractPass.lean | 39 ++++++++++--------- .../Laurel/LaurelCompilationPipeline.lean | 30 +++++++------- Strata/Languages/Laurel/TransparencyPass.lean | 28 ++++++------- .../Fundamentals/T20_TransparentBody.lean | 2 +- .../Examples/Fundamentals/T3_ControlFlow.lean | 19 --------- .../Fundamentals/T3_ControlFlowError.lean | 9 +---- .../Fundamentals/T3_ControlFlowError2.lean | 25 ++++++++++++ .../Fundamentals/T6_Preconditions.lean | 8 ++-- .../Fundamentals/T8_Postconditions.lean | 20 +--------- .../Fundamentals/T8_PostconditionsErrors.lean | 39 +++++++++++++++++++ .../T1b_HeapMutatingValueReturn.lean} | 0 11 files changed, 121 insertions(+), 98 deletions(-) create mode 100644 StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError2.lean create mode 100644 StrataTest/Languages/Laurel/Examples/Fundamentals/T8_PostconditionsErrors.lean rename StrataTest/Languages/Laurel/Examples/{Fundamentals/T8d_HeapMutatingValueReturn.lean => Objects/T1b_HeapMutatingValueReturn.lean} (100%) diff --git a/Strata/Languages/Laurel/ContractPass.lean b/Strata/Languages/Laurel/ContractPass.lean index 196b83d03d..e9a99c06fd 100644 --- a/Strata/Languages/Laurel/ContractPass.lean +++ b/Strata/Languages/Laurel/ContractPass.lean @@ -124,7 +124,7 @@ private def collectContractInfo (procs : List Procedure) : Std.HashMap String Co let postconds := getPostconditions proc.body let hasPre := !proc.preconditions.isEmpty let hasPost := !postconds.isEmpty - if hasPre || hasPost then + if !proc.isFunctional && (hasPre || hasPost) then m.insert proc.name.text { hasPreCondition := hasPre hasPostCondition := hasPost @@ -385,7 +385,7 @@ 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 helperProcs := (program.staticProcedures.filter (fun proc => !proc.isFunctional)).flatMap fun proc => let postconds := getPostconditions proc.body let preProc := if proc.preconditions.isEmpty then [] @@ -398,22 +398,25 @@ def contractPass (program : Program) : Program := -- Transform procedures: strip contracts, add assume/assert, rewrite call sites let transformedProcs := program.staticProcedures.map fun proc => - 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 + if proc.isFunctional then + proc + else + 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 } diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index a3b32daec5..1a77b46949 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -222,16 +222,14 @@ procedure list are transformed; functions are left unchanged. def liftImperativeExpressionsInCore (uc : UnorderedCoreWithLaurelTypes) (model : SemanticModel) : UnorderedCoreWithLaurelTypes := let imperativeCallees := uc.coreProcedures.map (·.name.text) - if imperativeCallees.isEmpty then uc - else - let liftedProgram := liftExpressionAssignments - { staticProcedures := uc.coreProcedures, staticFields := [], types := [], constants := [] } - model imperativeCallees - let liftedProcs := liftedProgram.staticProcedures - { uc with - functions := uc.functions - coreProcedures := liftedProcs - } + let liftedProgram := liftExpressionAssignments + { staticProcedures := uc.coreProcedures, staticFields := [], types := [], constants := [] } + model imperativeCallees + let liftedProcs := liftedProgram.staticProcedures + { uc with + functions := uc.functions + coreProcedures := liftedProcs + } /-- A single pass on the unordered Core representation. Each pass receives the current `UnorderedCoreWithLaurelTypes` and the semantic model and returns @@ -246,11 +244,11 @@ structure CorePass where /-- The ordered sequence of passes on the unordered Core representation. -/ private def corePipeline : Array CorePass := #[ - { name := "EliminateMultipleOutputs" - run := fun uc _m => eliminateMultipleOutputs uc }, - { name := "InlineLocalVariablesInExpressions" - needsResolves := true - run := fun uc _m => inlineLocalVariablesInExpressions uc }, + -- { name := "EliminateMultipleOutputs" + -- run := fun uc _m => eliminateMultipleOutputs uc }, + -- { name := "InlineLocalVariablesInExpressions" + -- needsResolves := true + -- run := fun uc _m => inlineLocalVariablesInExpressions uc }, { name := "LiftImperativeExpressionsInCore" needsResolves := true run := fun uc m => liftImperativeExpressionsInCore uc m } @@ -287,7 +285,7 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) let newDiags := errors.toList.map fun d => { d with message := s!"Internal error: resolution after '{pass.name}' introduced this diagnostic: {d.message}" } - emit pass.name "core.st" unorderedCore + emit pass.name "unorderedCoreWithLaurelTypes.st" unorderedCore return (none, passDiags ++ newDiags, program, stats) unorderedCore := uc' fnModel := m' diff --git a/Strata/Languages/Laurel/TransparencyPass.lean b/Strata/Languages/Laurel/TransparencyPass.lean index 275ade0862..fcf96f65c8 100644 --- a/Strata/Languages/Laurel/TransparencyPass.lean +++ b/Strata/Languages/Laurel/TransparencyPass.lean @@ -113,6 +113,7 @@ private def rewriteQuantifierBodiesInProc (nonExternalNames : List String) (proc let postconds' := postconds.map fun c => { c with condition := rewrite c.condition } { proc with body := .Abstract postconds' } | .External => proc + /-- Build a free postcondition equating the procedure's output to its functional version. For a procedure `foo(a, b) returns (r)`, produces: `r == foo$asFunction(a, b)` -/ @@ -129,12 +130,15 @@ private def mkFreePostcondition (proc : Procedure) : StmtExprMd := If the procedure is transparent, include a functional body. Otherwise the function is opaque. -/ private def mkFunctionCopy (asFunctionNames : List String) (proc : Procedure) : Procedure := - let funcName := { proc.name with text := proc.name.text ++ "$asFunction", uniqueId := none } + let hasProcedureTwin := asFunctionNames.contains proc.name.text + let funcName := if hasProcedureTwin then + { proc.name with text := proc.name.text ++ "$asFunction", uniqueId := none } + else proc.name let body := match proc.body with - | .Transparent b => .Transparent (rewriteCallsToFunctional asFunctionNames (stripAssertAssume b)) - | .Opaque _ _ _ => .Opaque [] none [] + | .Transparent b => .Transparent (rewriteCallsToFunctional asFunctionNames (if hasProcedureTwin then stripAssertAssume b else b)) + | .Opaque _ _ _ => if hasProcedureTwin then .Opaque [] none [] else proc.body | x => x - { proc with name := funcName, isFunctional := true, body := body, preconditions := [] } + { proc with name := funcName, isFunctional := true, body := body } /-- Check whether a function copy has a body (i.e. the procedure was transparent). -/ private def functionHasBody (proc : Procedure) : Bool := @@ -169,18 +173,14 @@ For each procedure: - If the function has a body, add a free postcondition equating the procedure output to the function -/ def transparencyPass (program : Program) : UnorderedCoreWithLaurelTypes := - let nonExternal := program.staticProcedures.filter (fun p => !p.body.isExternal) - let nonExternalNames := nonExternal.map (fun p => p.name.text) + let (toUpdate, _) := program.staticProcedures.partition (fun p => !p.body.isExternal && !p.isFunctional) + let toUpdateNames := toUpdate.map (fun p => p.name.text) -- $asFunction copies for non-external procedures - let asFunctions := nonExternal.map (mkFunctionCopy nonExternalNames) - -- External procedures get a plain function copy (they have no $asFunction version) - let externalFunctions := program.staticProcedures.filter (fun p => p.body.isExternal) - |>.map fun proc => { proc with isFunctional := true } - let functions := externalFunctions ++ asFunctions - let coreProcedures := nonExternal.map fun proc => + let functions := program.staticProcedures.map (mkFunctionCopy toUpdateNames) + let coreProcedures := toUpdate.map fun proc => let freePostcondition := mkFreePostcondition proc - let proc := { proc with isFunctional := false, axioms := proc.axioms.map (rewriteCallsToFunctional nonExternalNames) } - let proc := rewriteQuantifierBodiesInProc nonExternalNames proc + let proc := { proc with isFunctional := false, axioms := proc.axioms.map (rewriteCallsToFunctional toUpdateNames) } + let proc := rewriteQuantifierBodiesInProc toUpdateNames proc addFreePostcondition proc freePostcondition let datatypes := program.types.filterMap fun td => match td with | .Datatype dt => some dt diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean index a1e82883de..72727808fb 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean @@ -16,7 +16,7 @@ def transparentBodyProgram := r" procedure transparentBody(): int { assert true; - 3 + return 3 }; procedure transparentProcedureCaller() opaque { diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean index d5832e0cb9..cdb7423c4d 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean @@ -13,25 +13,6 @@ open Strata namespace Strata.Laurel def program := r" -function letsInFunction() returns (r: int) { - var x: int := 0; - var y: int := x + 1; - var z: int := y + 1; - z -}; - -procedure callLetsInFunction() opaque { - var x: int := letsInFunction(); - assert x == 2 -}; - -function assertAndAssumeInFunctions(a: int) returns (r: int) -{ - assert 2 == 3; -//^^^^^^^^^^^^^ error: assertion does not hold - assume true; - a -}; procedure returnAtEnd(x: int) returns (r: int) { diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean index 225870689a..743f19b461 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean @@ -16,11 +16,12 @@ def program := r" function assertAndAssumeInFunctions(a: int) returns (r: int) { assert 2 == 3; +//^^^^^^^^^^^^^ error: asserts are not YET supported in functions or contracts assume true; +//^^^^^^^^^^^ error: assumes are not YET supported in functions or contracts a }; - function letsInFunction() returns (r: int) { var x: int := 0; var y: int := x + 1; @@ -38,12 +39,6 @@ function localVariableWithoutInitializer(): int { //^^^^^^^^^^ error: local variables in functions must have initializers 3 }; - -procedure deadCodeAfterIfElse(x: int) returns (r: int) { - if x > 0 then { return 1 } else { return 2 }; -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: in a transparent body, if-then-else is only supported as the last statement in a block - return 3 -}; " #guard_msgs (error, drop all) in diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError2.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError2.lean new file mode 100644 index 0000000000..f63e3410ce --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError2.lean @@ -0,0 +1,25 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Util.TestDiagnostics +import StrataTest.Languages.Laurel.TestExamples + +open StrataTest.Util +open Strata + +namespace Strata.Laurel + +def program := r" + +procedure deadCodeAfterIfElse(x: int) returns (r: int) { + if x > 0 then { return 1 } else { return 2 }; +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: in a transparent body, if-then-else is only supported as the last statement in a block + return 3 +}; +" + +#guard_msgs (error, drop all) in +#eval! testInputWithOffset "ControlFlowError" program 14 processLaurelFile diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T6_Preconditions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T6_Preconditions.lean index a300094b89..9776411a4d 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T6_Preconditions.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T6_Preconditions.lean @@ -22,7 +22,7 @@ procedure hasRequires(x: int) returns (r: int) { assert x > 0; assert x > 3; -//^^^^^^^^^^^^ error: assertion could not be proved +//^^^^^^^^^^^^ error: assertion does not hold x + 1 }; @@ -30,7 +30,7 @@ procedure caller() opaque { var x: int := hasRequires(1); -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: precondition could not be proved +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: precondition does not hold var y: int := hasRequires(3) }; @@ -44,7 +44,7 @@ procedure aFunctionWithPreconditionCaller() opaque { var x: int := aFunctionWithPrecondition(0) -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: precondition could not be proved +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion does not hold // Error ranges are too wide because Core does not use expression locations }; @@ -76,7 +76,7 @@ procedure funcMultipleRequiresCaller() { var a: int := funcMultipleRequires(1, 2); var b: int := funcMultipleRequires(1, -1) -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: precondition could not be proved +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion does not hold }; " diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean index a9b269567f..bb3f492f0d 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean @@ -13,24 +13,6 @@ open Strata namespace Strata.Laurel def program := r" - -function opaqueFunction(x: int) returns (r: int) - requires x > 0 - opaque - ensures r > 0 -{ - x -}; - -procedure callerOfOpaqueFunction() - opaque -{ - var x: int := opaqueFunction(3); - assert x > 0; - assert x == 3 -//^^^^^^^^^^^^^ error: assertion could not be proved -}; - procedure opaqueBody(x: int) returns (r: int) opaque ensures r > 0 @@ -58,4 +40,4 @@ procedure invalidPostcondition(x: int) " #guard_msgs (drop info, error) in -#eval testInputWithOffset "Postconditions" program 17 processLaurelFileKeepIntermediates +#eval testInputWithOffset "Postconditions" program 14 processLaurelFileKeepIntermediates diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_PostconditionsErrors.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_PostconditionsErrors.lean new file mode 100644 index 0000000000..d61c5849da --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_PostconditionsErrors.lean @@ -0,0 +1,39 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Util.TestDiagnostics +import StrataTest.Languages.Laurel.TestExamples + +open StrataTest.Util +open Strata + +namespace Strata.Laurel + +def program := r" + +function opaqueFunction(x: int) returns (r: int) +// ^^^^^^^^^^^^^^ error: functions with postconditions are not yet supported +// The above limitation is because Core does not yet support functions with postconditions + requires x > 0 + opaque + ensures r > 0 +// The above limitation is because functions in Core do not support postconditions +{ + x +}; + +procedure callerOfOpaqueFunction() + opaque +{ + var x: int := opaqueFunction(3); + assert x > 0; +// The following assertion should fail but does not + assert x == 3 +}; +" + +#guard_msgs (drop info, error) in +#eval testInputWithOffset "Postconditions" program 14 processLaurelFile diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8d_HeapMutatingValueReturn.lean b/StrataTest/Languages/Laurel/Examples/Objects/T1b_HeapMutatingValueReturn.lean similarity index 100% rename from StrataTest/Languages/Laurel/Examples/Fundamentals/T8d_HeapMutatingValueReturn.lean rename to StrataTest/Languages/Laurel/Examples/Objects/T1b_HeapMutatingValueReturn.lean From 573a36b7f808e080323ae0544e9ba0d3eb0ba03b Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 12:46:43 +0000 Subject: [PATCH 023/115] Reduce id changes --- Strata/Languages/Laurel/TransparencyPass.lean | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Strata/Languages/Laurel/TransparencyPass.lean b/Strata/Languages/Laurel/TransparencyPass.lean index fcf96f65c8..e820d05577 100644 --- a/Strata/Languages/Laurel/TransparencyPass.lean +++ b/Strata/Languages/Laurel/TransparencyPass.lean @@ -60,14 +60,14 @@ def stripAssertAssume (expr : StmtExprMd) : StmtExprMd := Tester names also contain `..` but start with `is` after the separator. - `proof = true` → use safe selectors (strip `!` suffix) - `proof = false` → use unsafe selectors (add `!` suffix) -/ -private def adjustSelectorName (name : String) : String := +private def adjustSelectorName (name : Identifier) : Identifier := -- Only adjust destructor names (contain ".." but are not testers) - match name.splitOn ".." with + match name.text.splitOn ".." with | [_, suffix] => if suffix.startsWith "is" then name -- tester, leave unchanged else -- Unsafe: add trailing "!" if not already present - if name.endsWith "!" then name else name ++ "!" + if name.text.endsWith "!" then name else name.text ++ "!" | _ => name -- not a destructor name, leave unchanged /-- Rewrite StaticCall callees to their `$asFunction` versions, From 74ec111aa66ca46b35580cf79a1c434a48ea504d Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 12:55:30 +0000 Subject: [PATCH 024/115] Fixes --- .../Languages/Laurel/CoreGroupingAndOrdering.lean | 2 +- .../Fundamentals/T10_ConstrainedTypes.lean | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean b/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean index df0a7bd11a..47291c2a8b 100644 --- a/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean +++ b/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean @@ -53,7 +53,7 @@ def collectStaticCallNames (expr : StmtExprMd) : List String := match val with | .StaticCall callee args => callee.text :: args.flatMap (fun a => collectStaticCallNames a) - | .PrimitiveOp _ args => args.flatMap (fun a => collectStaticCallNames a) + | .PrimitiveOp _ args _ => args.flatMap (fun a => collectStaticCallNames a) | .IfThenElse cond t e => collectStaticCallNames cond ++ collectStaticCallNames t ++ diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T10_ConstrainedTypes.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T10_ConstrainedTypes.lean index dffe4a296a..f7c0d45829 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T10_ConstrainedTypes.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T10_ConstrainedTypes.lean @@ -32,7 +32,7 @@ procedure outputValid(): nat // Output constraint — invalid return fails procedure outputInvalid(): nat -// ^^^ error: postcondition could not be proved +// ^^^ error: postcondition does not hold opaque { return -1 @@ -60,7 +60,7 @@ procedure assignInvalid() opaque { var y: nat := -1 -//^^^^^^^^^^^^^^^^ error: assertion could not be proved +//^^^^^^^^^^^^^^^^ error: assertion does not hold }; // Reassignment to constrained-typed variable — invalid @@ -69,7 +69,7 @@ procedure reassignInvalid() { var y: nat := 5; y := -1 -//^^^^^^^ error: assertion could not be proved +//^^^^^^^ error: assertion does not hold }; // Argument to constrained-typed parameter — valid @@ -88,7 +88,7 @@ procedure argInvalid() returns (r: int) opaque { var x: int := takesNat(-1); -//^^^^^^^^^^^^^^^^^^^^^^^^^^ error: precondition could not be proved +//^^^^^^^^^^^^^^^^^^^^^^^^^^ error: precondition does not hold return x }; @@ -129,7 +129,7 @@ procedure constrainedInExpr() // Invalid witness — witness -1 does not satisfy x > 0 constrained bad = x: int where x > 0 witness -1 -// ^^ error: assertion could not be proved +// ^^ error: assertion does not hold // Uninitialized constrained variable — havoc + assume constraint procedure uninitNat() @@ -154,7 +154,7 @@ procedure uninitNotWitness() { var y: posnat; assert y == 1 -//^^^^^^^^^^^^^ error: assertion could not be proved +//^^^^^^^^^^^^^ error: assertion does not hold }; // Quantifier constraint injection — forall @@ -188,7 +188,7 @@ procedure captureTest(y: haslarger) opaque { assert false -//^^^^^^^^^^^^ error: assertion could not be proved +//^^^^^^^^^^^^ error: assertion does not hold }; " From 800f6c469934298b2682b63fde3ee09960b118f9 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 12:57:21 +0000 Subject: [PATCH 025/115] Test fixes --- StrataTest/Languages/Laurel/DivisionByZeroCheckTest.lean | 2 +- .../Laurel/Examples/Fundamentals/T16_PropertySummary.lean | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/StrataTest/Languages/Laurel/DivisionByZeroCheckTest.lean b/StrataTest/Languages/Laurel/DivisionByZeroCheckTest.lean index dc12ef17af..d5d5671ade 100644 --- a/StrataTest/Languages/Laurel/DivisionByZeroCheckTest.lean +++ b/StrataTest/Languages/Laurel/DivisionByZeroCheckTest.lean @@ -54,7 +54,7 @@ procedure callPureDivUnsafe(x: int) opaque { var z: int := pureDiv(10, x) -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: precondition could not be proved +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion does not hold // Error ranges are too wide because Core does not use expression locations }; " diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T16_PropertySummary.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T16_PropertySummary.lean index 7ceb3aea9b..b9d25ce265 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T16_PropertySummary.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T16_PropertySummary.lean @@ -19,7 +19,7 @@ procedure divide(x: int, y: int) returns (result: int) opaque { assert y == 0 summary "divisor is zero"; -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: divisor is zero could not be proved +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: divisor is zero does not hold return x }; @@ -27,7 +27,7 @@ procedure checkPositive(n: int) returns (ok: bool) opaque { var x: int := divide(3, 0) -//^^^^^^^^^^^^^^^^^^^^^^^^^^ error: divisor is non-zero could not be proved +//^^^^^^^^^^^^^^^^^^^^^^^^^^ error: divisor is non-zero does not hold }; "# From e56cb204fa2704f9949e2478221846646c6c81b0 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 13:04:16 +0000 Subject: [PATCH 026/115] Fix test --- Strata/Languages/Laurel/Resolution.lean | 11 +---------- .../Examples/Fundamentals/T22_ArityMismatch.lean | 5 ++++- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/Strata/Languages/Laurel/Resolution.lean b/Strata/Languages/Laurel/Resolution.lean index 93184230c8..2303a0b44a 100644 --- a/Strata/Languages/Laurel/Resolution.lean +++ b/Strata/Languages/Laurel/Resolution.lean @@ -178,16 +178,7 @@ def SemanticModel.get? (model: SemanticModel) (iden: Identifier): Option Resolve iden.uniqueId.bind model.refToDef.get? def SemanticModel.get (model: SemanticModel) (iden: Identifier): ResolvedNode := - match iden.uniqueId with - | some key => - match model.refToDef.get? key with - | some node => node - | none => - -- An ID was assigned during Phase 1 but the reference was never registered in - -- Phase 2 (buildRefToDef). This is a bug in the resolution pass itself. - dbg_trace s!"SOUND BUG: identifier '{iden.text}' (id={key}) has a uniqueId but is missing from refToDef" - .unresolved iden.source - | none => .unresolved iden.source + (model.get? iden).getD default def SemanticModel.isFunction (model: SemanticModel) (id: Identifier): Bool := match model.get id with diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T22_ArityMismatch.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T22_ArityMismatch.lean index a796ab2fb5..94c0f22371 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T22_ArityMismatch.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T22_ArityMismatch.lean @@ -22,7 +22,10 @@ procedure caller() }; " -/-- error: input length and args length mismatch -/ +/-- +error: ArityMismatch(79-100) ❌ Type checking error. +Impossible to unify int with (arrow int $__ty35). +-/ #guard_msgs(drop info, error) in #eval testInputWithOffset "ArityMismatch" arityMismatchProgram 14 processLaurelFile From a352b47abc7042f8682b485b824810a4b5de56cc Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 13:09:03 +0000 Subject: [PATCH 027/115] Fix test --- StrataTest/Languages/Laurel/LiftHolesTest.lean | 2 -- 1 file changed, 2 deletions(-) diff --git a/StrataTest/Languages/Laurel/LiftHolesTest.lean b/StrataTest/Languages/Laurel/LiftHolesTest.lean index 019e953feb..dfdba56e87 100644 --- a/StrataTest/Languages/Laurel/LiftHolesTest.lean +++ b/StrataTest/Languages/Laurel/LiftHolesTest.lean @@ -400,7 +400,6 @@ info: function $hole_0(x: int) returns ($result: int) opaque; function test(x: int): int - opaque { $hole_0(x) }; @@ -408,7 +407,6 @@ function test(x: int): int #guard_msgs in #eval! parseElimAndPrint r" function test(x: int): int - opaque { }; " From 65c92f4b9d4c021da0ccb758f87283152c7a5278 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 13:20:47 +0000 Subject: [PATCH 028/115] Fix test --- Strata/Languages/Laurel/LiftImperativeExpressions.lean | 2 +- .../Laurel/Examples/Objects/T1b_HeapMutatingValueReturn.lean | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index 51271365db..7720d46342 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -567,7 +567,7 @@ def transformStmt (stmt : StmtExprMd) : LiftM (List StmtExprMd) := do let seqArgs ← args.reverse.mapM transformExpr let argPrepends ← takePrepends modify fun s => { s with subst := [] } - return argPrepends ++ [⟨.Assign targets ⟨.StaticCall callee seqArgs, source⟩, source⟩] + return argPrepends ++ [⟨.Assign targets ⟨.StaticCall callee seqArgs.reverse, source⟩, source⟩] | _ => let seqValue ← transformExpr valueMd let prepends ← takePrepends diff --git a/StrataTest/Languages/Laurel/Examples/Objects/T1b_HeapMutatingValueReturn.lean b/StrataTest/Languages/Laurel/Examples/Objects/T1b_HeapMutatingValueReturn.lean index 0e2a397570..7fa1e0aba2 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/T1b_HeapMutatingValueReturn.lean +++ b/StrataTest/Languages/Laurel/Examples/Objects/T1b_HeapMutatingValueReturn.lean @@ -29,7 +29,7 @@ procedure setAndReturn(c: Container, x: int) returns (r: int) procedure setAndReturnBuggy(c: Container, x: int) returns (r: int) opaque ensures r == x + 1 -// ^^^^^^^^^^ error: postcondition could not be proved +// ^^^^^^^^^^ error: postcondition does not hold modifies c { c#value := x; From 4a8c66752d85bf73c8e025cad5e0e6cb7a7dd82d Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 13:22:17 +0000 Subject: [PATCH 029/115] Remove unused files --- .../Laurel/EliminateMultipleOutputs.lean | 166 ------------------ .../InlineLocalVariablesInExpressions.lean | 83 --------- 2 files changed, 249 deletions(-) delete mode 100644 Strata/Languages/Laurel/EliminateMultipleOutputs.lean delete mode 100644 Strata/Languages/Laurel/InlineLocalVariablesInExpressions.lean diff --git a/Strata/Languages/Laurel/EliminateMultipleOutputs.lean b/Strata/Languages/Laurel/EliminateMultipleOutputs.lean deleted file mode 100644 index ae932c9b7e..0000000000 --- a/Strata/Languages/Laurel/EliminateMultipleOutputs.lean +++ /dev/null @@ -1,166 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ -module - -public import Strata.Languages.Laurel.TransparencyPass - -/-! -# Eliminate Multiple Outputs - -Transforms bodiless functions with multiple outputs into functions that return -a single synthesized result datatype. Call sites are rewritten to destructure -the result using the generated accessors. - -This pass operates on `UnorderedCoreWithLaurelTypes → UnorderedCoreWithLaurelTypes`. --/ - -namespace Strata.Laurel - -public section - - -private def mkMd (e : StmtExpr) : StmtExprMd := { val := e, source := none } -private def mkVarMd (v : Variable) : VariableMd := { val := v, source := none } -private def mkTy (t : HighType) : HighTypeMd := { val := t, source := none } - -/-- Info about a function whose multiple outputs have been collapsed into a result datatype. -/ -private structure MultiOutInfo where - funcName : String - resultTypeName : String - constructorName : String - /-- Original output parameters (name, type). -/ - outputs : List Parameter - /-- Number of input parameters (used to detect implicit heap args at call sites). -/ - inputCount : Nat - -/-- Identify bodiless functions with multiple outputs and build info records. -/ -private def collectMultiOutFunctions (funcs : List Procedure) : List MultiOutInfo := - funcs.filterMap fun f => - if f.outputs.length > 1 && !f.body.isTransparent then - some { - funcName := f.name.text - resultTypeName := s!"{f.name.text}$result" - constructorName := s!"{f.name.text}$result$mk" - outputs := f.outputs - inputCount := f.inputs.length - } - else none - -/-- Generate a result datatype for a multi-output function. -/ -private def mkResultDatatype (info : MultiOutInfo) : DatatypeDefinition := - let args := info.outputs.zipIdx.map fun (p, i) => - { name := mkId s!"out{i}", type := p.type : Parameter } - { name := mkId info.resultTypeName - typeArgs := [] - constructors := [{ name := mkId info.constructorName, args := args }] } - -/-- Transform a multi-output function to return the result datatype. -/ -private def transformFunction (info : MultiOutInfo) (proc : Procedure) : Procedure := - let resultOutput : Parameter := - { name := mkId "$result", type := mkTy (.UserDefined (mkId info.resultTypeName)) } - { proc with outputs := [resultOutput] } - -/-- Destructor name for field `outN` of the result datatype. -/ -private def destructorName (info : MultiOutInfo) (idx : Nat) : String := - s!"{info.resultTypeName}..out{idx}" - -/-- Check whether a statement is an Assume node. -/ -private def isAssume (stmt : StmtExprMd) : Bool := - match stmt.val with - | .Assume _ => true - | _ => false - -/-- Rewrite a single multi-output Assign into a temp declaration + destructuring - assignments. Any `Assume` statements from `following` that appear immediately - after the call are collected and placed after the destructuring assignments, - so they observe the post-call variable values. - Returns the rewritten statements and the number of consumed following statements. -/ -private def rewriteAssign (infoMap : Std.HashMap String MultiOutInfo) - (targets : List VariableMd) (callee : Identifier) (args : List StmtExprMd) - (callSrc : Option FileRange) - (following : List StmtExprMd) (counter : Nat) : Option (List StmtExprMd × Nat) := - match infoMap.get? callee.text with - | some info => - if targets.length ≤ info.outputs.length then - let tempName := s!"${callee.text}$temp{counter}" - let fullArgs := args - let tempDecl := mkMd (.Assign [mkVarMd (.Declare ⟨mkId tempName, mkTy (.UserDefined (mkId info.resultTypeName))⟩)] - ⟨.StaticCall callee fullArgs, callSrc⟩) - let assigns := targets.zipIdx.map fun (tgt, i) => - mkMd (.Assign [tgt] - (mkMd (.StaticCall (mkId (destructorName info i)) - [mkMd (.Var (.Local (mkId tempName)))]))) - -- Collect any Assume statements that immediately follow the call. - -- These are placed after the destructuring assignments so they - -- observe the post-call values of output variables. - let assumes := following.takeWhile isAssume - let consumed := assumes.length - some (tempDecl :: assigns ++ assumes, consumed) - else none - | none => none - -/-- Rewrite a statement list, replacing multi-output call patterns. - When a multi-output Assign is followed by Assume statements (inserted by - the contract pass), the Assumes are placed after the destructuring - assignments so they reference post-call variable values. -/ -private def rewriteStmts (infoMap : Std.HashMap String MultiOutInfo) - (stmts : List StmtExprMd) : List StmtExprMd := - let rec go (remaining : List StmtExprMd) (acc : List StmtExprMd) (counter : Nat) : List StmtExprMd := - match remaining with - | [] => acc.reverse - | stmt :: rest => - match stmt.val with - | .Assign targets ⟨.StaticCall callee args, callSrc⟩ => - match rewriteAssign infoMap targets callee args callSrc rest counter with - | some (expanded, consumed) => go (rest.drop consumed) (expanded.reverse ++ acc) (counter + 1) - | none => go rest (stmt :: acc) counter - | _ => go rest (stmt :: acc) counter - termination_by remaining.length - go stmts [] 0 - -/-- Rewrite blocks in a StmtExprMd tree to handle multi-output calls. -/ -private def rewriteExpr (infoMap : Std.HashMap String MultiOutInfo) - (expr : StmtExprMd) : StmtExprMd := - mapStmtExpr (fun e => - match e.val with - | .Block stmts label => ⟨.Block (rewriteStmts infoMap stmts) label, e.source⟩ - | _ => e) expr - -/-- Rewrite all procedure bodies. -/ -private def rewriteProcedure (infoMap : Std.HashMap String MultiOutInfo) - (proc : Procedure) : Procedure := - match proc.body with - | .Transparent b => - -- Wrap in a block so rewriteStmts can process top-level statements - let wrapped := mkMd (.Block [b] none) - let rewritten := rewriteExpr infoMap wrapped - { proc with body := .Transparent rewritten } - | .Opaque posts (some impl) mods => - let wrapped := mkMd (.Block [impl] none) - let rewritten := rewriteExpr infoMap wrapped - { proc with body := .Opaque posts (some rewritten) mods } - | _ => proc - -/-- Eliminate multiple outputs from a UnorderedCoreWithLaurelTypes. -/ -def eliminateMultipleOutputs (program : UnorderedCoreWithLaurelTypes) - : UnorderedCoreWithLaurelTypes := - let infos := collectMultiOutFunctions program.functions - if infos.isEmpty then program else - let infoMap : Std.HashMap String MultiOutInfo := - infos.foldl (fun m info => m.insert info.funcName info) {} - let newDatatypes := infos.map mkResultDatatype - let functions := program.functions.map fun f => - match infoMap.get? f.name.text with - | some info => rewriteProcedure infoMap (transformFunction info f) - | none => rewriteProcedure infoMap f - let coreProcedures := program.coreProcedures.map fun p => rewriteProcedure infoMap p - { program with - functions := functions - coreProcedures := coreProcedures - datatypes := program.datatypes ++ newDatatypes } - -end -- public section -end Strata.Laurel diff --git a/Strata/Languages/Laurel/InlineLocalVariablesInExpressions.lean b/Strata/Languages/Laurel/InlineLocalVariablesInExpressions.lean deleted file mode 100644 index 65c2787286..0000000000 --- a/Strata/Languages/Laurel/InlineLocalVariablesInExpressions.lean +++ /dev/null @@ -1,83 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ -module - -public import Strata.Languages.Laurel.MapStmtExpr -public import Strata.Languages.Laurel.TransparencyPass -import Strata.Util.Tactics - -/-! -# Inline Local Variables in Expression Position - -Replaces local variable declarations in functional procedure bodies with -direct substitution of the initializer into the remaining statements of -the block. This eliminates `LocalVariable` nodes from expression contexts -so the Core translator does not need to handle let-bindings in expressions. - -Example: -``` -function f() returns (r: int) { - var x: int := 1; - var y: int := x + 1; - y -} -``` -becomes: -``` -function f() returns (r: int) { - 0 + 1 -} -``` --/ - -namespace Strata.Laurel - -public section - -/-- Substitute all occurrences of local variable `name` with `replacement` in `expr`. -/ -private def substIdentifier (name : Identifier) (replacement : StmtExprMd) (expr : StmtExprMd) - : StmtExprMd := - mapStmtExpr (fun e => - match e.val with - | .Var (.Local n) => if n == name then replacement else e - | _ => e) expr - -/-- Inline initialized local variables in a block, substituting their - initializers into the remaining statements. Non-Assign/Declare - statements are kept as-is. -/ -private def inlineLocalsInStmts (stmts : List StmtExprMd) : List StmtExprMd := - match stmts with - | [] => [] - | ⟨.Assign [⟨.Declare parameter, _⟩] initializer, _⟩ :: rest => - let rest' := rest.map (substIdentifier parameter.name initializer) - inlineLocalsInStmts rest' - | s :: rest => s :: inlineLocalsInStmts rest -termination_by stmts.length - -/-- Rewrite a single node: if it is a Block, inline any LocalVariable - declarations. Recursion into children is handled by `mapStmtExpr`. -/ -private def inlineLocalsNode (expr : StmtExprMd) : StmtExprMd := - match expr.val with - | .Block stmts label => - let stmts' := inlineLocalsInStmts stmts - match stmts', label with - | [single], none => single - | _, _ => ⟨.Block stmts' label, expr.source⟩ - | _ => expr - -/-- Apply local-variable inlining to all functional procedure bodies. -/ -def inlineLocalVariablesInExpressions (program : UnorderedCoreWithLaurelTypes) : UnorderedCoreWithLaurelTypes := - { program with functions := program.functions.map fun proc => - match proc.body with - | .Transparent body => - { proc with body := .Transparent (mapStmtExpr inlineLocalsNode body) } - | .Opaque postconds (some impl) modif => - { proc with body := .Opaque postconds (some (mapStmtExpr inlineLocalsNode impl)) modif } - | _ => proc - } - -end -- public section -end Strata.Laurel From d7d720504c3f54b8e30cfc42e20a32221245a664 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 13:30:06 +0000 Subject: [PATCH 030/115] Undo Python test changes --- StrataTest/Languages/Python/ToLaurelTest.lean | 24 +- .../expected_laurel/test_any_dict.expected | 2 +- .../expected_laurel/test_any_list.expected | 2 +- .../expected_laurel/test_arithmetic.expected | 12 +- .../test_assert_false.expected | 2 +- .../expected_laurel/test_augadd_list.expected | 4 +- .../expected_laurel/test_augfloordiv.expected | 5 +- .../test_augmented_assign.expected | 7 +- .../expected_laurel/test_augmod.expected | 5 +- .../test_break_continue.expected | 4 +- .../test_bubble_sort_step.expected | 47 +-- .../expected_laurel/test_class_empty.expected | 2 +- .../test_class_field_init.expected | 3 +- .../test_class_field_use.expected | 3 +- .../test_class_method_call_from_main.expected | 3 +- .../test_class_methods.expected | 13 +- .../test_class_mixed_init.expected | 4 +- .../test_class_no_init.expected | 2 +- .../test_class_no_init_multi_field.expected | 2 +- .../test_class_no_init_with_method.expected | 7 +- .../test_class_with_methods.expected | 2 +- .../test_coerce_int_in_any_list.expected | 4 +- .../expected_laurel/test_datetime.expected | 7 +- .../test_datetime_now_tz.expected | 7 +- .../test_default_params.expected | 16 +- .../test_dict_add_key.expected | 2 +- .../expected_laurel/test_dict_assign.expected | 2 +- .../expected_laurel/test_dict_create.expected | 4 +- .../expected_laurel/test_dict_in.expected | 4 +- .../test_dict_overwrite.expected | 2 +- .../test_empty_dict_access.expected | 4 +- .../test_flag_pattern.expected | 7 +- .../test_for_else_break.expected | 2 +- .../expected_laurel/test_for_loop.expected | 6 +- .../expected_laurel/test_for_range.expected | 6 +- .../test_function_def_calls.expected | 6 +- .../expected_laurel/test_if_elif.expected | 3 +- .../test_int_bool_conversion.expected | 2 +- .../test_int_floordiv.expected | 4 +- .../expected_laurel/test_int_mod.expected | 4 +- .../test_int_negative_floordiv.expected | 5 +- .../test_int_negative_mod.expected | 5 +- .../expected_laurel/test_list_assign.expected | 2 +- .../expected_laurel/test_list_concat.expected | 4 +- .../expected_laurel/test_list_create.expected | 4 +- .../expected_laurel/test_list_empty.expected | 2 +- .../expected_laurel/test_list_in.expected | 4 +- .../expected_laurel/test_loops.expected | 16 +- .../test_method_call_with_kwargs.expected | 11 +- .../test_method_kwargs_no_hierarchy.expected | 2 +- .../test_mixed_types_list.expected | 4 +- .../test_module_level.expected | 10 +- .../test_multi_function.expected | 20 +- .../test_nested_calls.expected | 4 +- .../test_nested_optional.expected | 2 +- .../test_none_in_list.expected | 2 +- .../test_param_reassign.expected | 16 +- .../test_param_reassign_kwargs.expected | 6 +- .../test_precondition_verification.expected | 18 +- .../test_procedure_in_assert.expected | 8 +- .../test_regex_negative.expected | 50 +-- .../test_regex_positive.expected | 306 +++++++++--------- .../test_return_types.expected | 10 +- .../test_timedelta_expr.expected | 7 +- .../test_try_except_modeled.expected | 9 +- .../test_try_except_nested.expected | 4 +- .../test_tuple_create.expected | 4 +- .../expected_laurel/test_tuple_swap.expected | 5 +- .../expected_laurel/test_tuple_type.expected | 2 +- .../test_tuple_unpack.expected | 5 +- .../test_type_dict_annotation.expected | 2 +- .../test_type_list_annotation.expected | 2 +- .../test_var_shadow_func.expected | 3 +- .../test_variable_reassign.expected | 4 +- .../expected_laurel/test_while_loop.expected | 9 +- .../Languages/Python/run_py_analyze_sarif.py | 6 +- .../Languages/Python/tests/cbmc_expected.txt | 1 - 77 files changed, 444 insertions(+), 377 deletions(-) diff --git a/StrataTest/Languages/Python/ToLaurelTest.lean b/StrataTest/Languages/Python/ToLaurelTest.lean index 5220c6b975..bbf7e437a5 100644 --- a/StrataTest/Languages/Python/ToLaurelTest.lean +++ b/StrataTest/Languages/Python/ToLaurelTest.lean @@ -673,7 +673,7 @@ info: errors: 1 -- Regression test for issue #800: nested dict access `kwargs["Outer"]["Inner"]` -- should generate `Any_get` (dict lookup), not `FieldSelect`. /-- -info: preconditions contain Any_get: true +info: body contains Any_get: true body contains FieldSelect: false -/ #guard_msgs in @@ -694,13 +694,11 @@ body contains FieldSelect: false assert! result.errors.size = 0 match result.program.staticProcedures with | proc :: _ => - let precondStr := proc.preconditions.map (fun (p : Strata.Laurel.Condition) => toString (Strata.Laurel.formatStmtExpr p.condition)) - |> String.intercalate ", " let bodyStr := match proc.body with | .Transparent body => toString (Strata.Laurel.formatStmtExpr body) | .Opaque _ (some body) _ => toString (Strata.Laurel.formatStmtExpr body) | _ => "" - IO.println s!"preconditions contain Any_get: {precondStr.contains "Any_get"}" + IO.println s!"body contains Any_get: {bodyStr.contains "Any_get"}" IO.println s!"body contains FieldSelect: {bodyStr.contains "#"}" | [] => IO.println "no procedures" @@ -931,13 +929,7 @@ private def translatePrecondResult (preconditions : Array Assertion) private def translatePrecond (preconditions : Array Assertion) (args : Array Arg := #[]) : String × Nat := let result := translatePrecondResult preconditions args - let precondStr := match result.program.staticProcedures with - | proc :: _ => - let formatted := proc.preconditions.map (fun (p : Strata.Laurel.Condition) => toString (Strata.Laurel.formatStmtExpr p.condition)) - if formatted.isEmpty then getBody result |>.getD "" - else "{ " ++ (String.intercalate "; " formatted) ++ " }" - | [] => "" - (precondStr, result.errors.size) + (getBody result |>.getD "", result.errors.size) -- enumMember: or and eq via `|` and `==` infix syntax #eval do @@ -979,12 +971,10 @@ private def translatePrecond (preconditions : Array Assertion) postconditions := #[] }] testModule let body := getBody result |>.getD "" assertEq result.errors.size 0 - match result.program.staticProcedures with - | proc :: _ => - let precondStr := proc.preconditions.map (fun (p : Strata.Laurel.Condition) => toString (Strata.Laurel.formatStmtExpr p.condition)) - |> String.intercalate ", " - assert! precondStr.contains "!Any..isfrom_None(key)" - | [] => assert! false + assert! body.contains "result := " + assert! body.contains "Any..isfrom_None(key) | Any..isfrom_str(key)" + assert! body.contains "assert !Any..isfrom_None(key) summary \"precondition 0\"" + assert! body.contains "assume Any..isfrom_str(result)" -- containsKey on a non-kwargs dict: DictStrAny_contains in an assert -- (would have been silently dropped before fix #2) diff --git a/StrataTest/Languages/Python/expected_laurel/test_any_dict.expected b/StrataTest/Languages/Python/expected_laurel/test_any_dict.expected index 250cba478e..56fc49687d 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_any_dict.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_any_dict.expected @@ -1,4 +1,4 @@ -test_any_dict.py(5, 11): ✅ pass - precondition +test_any_dict.py(5, 4): ✅ pass - assert_assert(71)_calls_Any_get_0 test_any_dict.py(5, 4): ✅ pass - Any holds dict DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_any_list.expected b/StrataTest/Languages/Python/expected_laurel/test_any_list.expected index 396b5d165f..af813bcfcd 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_any_list.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_any_list.expected @@ -1,4 +1,4 @@ -test_any_list.py(5, 11): ✅ pass - precondition +test_any_list.py(5, 4): ✅ pass - assert_assert(72)_calls_Any_get_0 test_any_list.py(5, 4): ✅ pass - Any holds list DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_arithmetic.expected b/StrataTest/Languages/Python/expected_laurel/test_arithmetic.expected index 9d339f8e2f..5d2aac95de 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_arithmetic.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_arithmetic.expected @@ -14,19 +14,19 @@ test_arithmetic.py(16, 4): ✅ pass - addition implemented incorrectly test_arithmetic.py(19, 16): ✅ pass - Check PSub exception test_arithmetic.py(19, 4): ✅ pass - assert(436) test_arithmetic.py(20, 4): ✅ pass - subtraction implemented incorrectly -test_arithmetic.py(23, 16): ✅ pass - precondition +test_arithmetic.py(23, 16): ✅ pass - assert_assert(556)_calls_PFloorDiv_0 test_arithmetic.py(23, 16): ✅ pass - Check PFloorDiv exception -test_arithmetic.py(23, 4): ✅ pass - precondition +test_arithmetic.py(23, 4): ✅ pass - set_quot_calls_PFloorDiv_0 test_arithmetic.py(23, 4): ✅ pass - assert(544) test_arithmetic.py(24, 4): ✅ pass - floor division implemented incorrectly -test_arithmetic.py(27, 15): ✅ pass - precondition +test_arithmetic.py(27, 15): ✅ pass - assert_assert(652)_calls_PMod_0 test_arithmetic.py(27, 15): ✅ pass - Check PMod exception -test_arithmetic.py(27, 4): ✅ pass - precondition +test_arithmetic.py(27, 4): ✅ pass - set_rem_calls_PMod_0 test_arithmetic.py(27, 4): ✅ pass - assert(641) test_arithmetic.py(28, 4): ✅ pass - mod implemented incorrectly -test_arithmetic.py(31, 20): ✅ pass - precondition +test_arithmetic.py(31, 20): ✅ pass - assert_assert(749)_calls_PMod_0 test_arithmetic.py(31, 20): ✅ pass - Check PMod exception -test_arithmetic.py(31, 4): ✅ pass - precondition +test_arithmetic.py(31, 4): ✅ pass - set_neg_rem1_calls_PMod_0 test_arithmetic.py(31, 4): ✅ pass - assert(733) test_arithmetic.py(32, 4): ✅ pass - negative mod should follow Python floored semantics DETAIL: 31 passed, 0 failed, 0 inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_assert_false.expected b/StrataTest/Languages/Python/expected_laurel/test_assert_false.expected index 6eef5ea9f9..96db8efe39 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_assert_false.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_assert_false.expected @@ -1,6 +1,6 @@ +test_assert_false.py(4, 8): ✅ pass - unreachable test_assert_false.py(2, 4): ✅ pass - assert(16) test_assert_false.py(3, 7): ✅ pass - Check PGt exception -test_assert_false.py(4, 8): ✅ pass - unreachable test_assert_false.py(5, 4): ✅ pass - reachable DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_augadd_list.expected b/StrataTest/Languages/Python/expected_laurel/test_augadd_list.expected index b3a378455c..b9ee61e68b 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_augadd_list.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_augadd_list.expected @@ -1,7 +1,7 @@ test_augadd_list.py(3, 4): ✅ pass - Check PAdd exception -test_augadd_list.py(4, 11): ✅ pass - precondition +test_augadd_list.py(4, 4): ✅ pass - assert_assert(61)_calls_Any_get_0 test_augadd_list.py(4, 4): ✅ pass - augmented add list -test_augadd_list.py(5, 11): ✅ pass - precondition +test_augadd_list.py(5, 4): ✅ pass - assert_assert(105)_calls_Any_get_0 test_augadd_list.py(5, 4): ✅ pass - augmented add list last DETAIL: 5 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_augfloordiv.expected b/StrataTest/Languages/Python/expected_laurel/test_augfloordiv.expected index 4707c47873..a5f20ce182 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_augfloordiv.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_augfloordiv.expected @@ -1,6 +1,7 @@ test_augfloordiv.py(2, 4): ✅ pass - assert(28) -test_augfloordiv.py(3, 4): ✅ pass - precondition +test_augfloordiv.py(3, 4): ✅ pass - assert_assert(44)_calls_PFloorDiv_0 test_augfloordiv.py(3, 4): ✅ pass - Check PFloorDiv exception +test_augfloordiv.py(3, 4): ✅ pass - set_x_calls_PFloorDiv_0 test_augfloordiv.py(4, 4): ✅ pass - augmented floordiv -DETAIL: 4 passed, 0 failed, 0 inconclusive +DETAIL: 5 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_augmented_assign.expected b/StrataTest/Languages/Python/expected_laurel/test_augmented_assign.expected index 1df585b0c6..ad80803bf1 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_augmented_assign.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_augmented_assign.expected @@ -4,9 +4,10 @@ test_augmented_assign.py(6, 4): ✅ pass - Check PSub exception test_augmented_assign.py(7, 4): ✅ pass - 8 - 2 == 6 test_augmented_assign.py(8, 4): ✅ pass - Check PMul exception test_augmented_assign.py(9, 4): ✅ pass - 6 * 2 == 12 -test_augmented_assign.py(11, 4): ✅ pass - precondition +test_augmented_assign.py(11, 4): ✅ pass - assert_assert(219)_calls_Any_get_0 test_augmented_assign.py(11, 4): ✅ pass - Check Any_sets! exception -test_augmented_assign.py(12, 11): ✅ pass - precondition +test_augmented_assign.py(11, 4): ✅ pass - set_l_calls_Any_get_0 +test_augmented_assign.py(12, 4): ✅ pass - assert_assert(233)_calls_Any_get_0 test_augmented_assign.py(12, 4): ✅ pass - list element modified -DETAIL: 10 passed, 0 failed, 0 inconclusive +DETAIL: 11 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_augmod.expected b/StrataTest/Languages/Python/expected_laurel/test_augmod.expected index 87549640dc..c785c8575b 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_augmod.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_augmod.expected @@ -1,6 +1,7 @@ test_augmod.py(2, 4): ✅ pass - assert(23) -test_augmod.py(3, 4): ✅ pass - precondition +test_augmod.py(3, 4): ✅ pass - assert_assert(39)_calls_PMod_0 test_augmod.py(3, 4): ✅ pass - Check PMod exception +test_augmod.py(3, 4): ✅ pass - set_x_calls_PMod_0 test_augmod.py(4, 4): ✅ pass - augmented mod -DETAIL: 4 passed, 0 failed, 0 inconclusive +DETAIL: 5 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_break_continue.expected b/StrataTest/Languages/Python/expected_laurel/test_break_continue.expected index 4f120f15d0..1cb5222c41 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_break_continue.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_break_continue.expected @@ -4,9 +4,9 @@ test_break_continue.py(1, 26): ✅ pass - (test_while_break ensures) Return type test_break_continue.py(7, 4): ✅ pass - assert(129) test_break_continue.py(8, 10): ✅ pass - Check PNot exception test_break_continue.py(6, 29): ✅ pass - (test_while_continue ensures) Return type constraint -test_break_continue.py(14, 4): ✅ pass - precondition +test_break_continue.py(14, 4): ✅ pass - assume_assume(267)_calls_PIn_0 test_break_continue.py(12, 24): ✅ pass - (test_for_break ensures) Return type constraint -test_break_continue.py(19, 4): ✅ pass - precondition +test_break_continue.py(19, 4): ✅ pass - assume_assume(362)_calls_PIn_0 test_break_continue.py(17, 27): ✅ pass - (test_for_continue ensures) Return type constraint DETAIL: 10 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_bubble_sort_step.expected b/StrataTest/Languages/Python/expected_laurel/test_bubble_sort_step.expected index 0378609c33..f3de7b0866 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_bubble_sort_step.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_bubble_sort_step.expected @@ -1,32 +1,41 @@ -test_bubble_sort_step.py(3, 7): ✅ pass - precondition -test_bubble_sort_step.py(3, 15): ✅ pass - precondition +test_bubble_sort_step.py(12, 8): ✅ pass - set_t3_calls_Any_get_0 +test_bubble_sort_step.py(12, 8): ✅ pass - assert(250) +test_bubble_sort_step.py(13, 8): ✅ pass - assert_assert(274)_calls_Any_get_0 +test_bubble_sort_step.py(13, 8): ✅ pass - Check Any_sets! exception +test_bubble_sort_step.py(13, 8): ✅ pass - set_xs_calls_Any_get_0 +test_bubble_sort_step.py(14, 8): ✅ pass - Check Any_sets! exception +test_bubble_sort_step.py(3, 7): ✅ pass - assert_assert(55)_calls_Any_get_0 +test_bubble_sort_step.py(3, 7): ✅ pass - assert_assert(55)_calls_Any_get_1 test_bubble_sort_step.py(3, 7): ✅ pass - Check PGt exception -test_bubble_sort_step.py(4, 8): ✅ pass - precondition +test_bubble_sort_step.py(3, 4): ✅ pass - ite_cond_calls_Any_get_0 +test_bubble_sort_step.py(3, 4): ✅ pass - ite_cond_calls_Any_get_1 +test_bubble_sort_step.py(4, 8): ✅ pass - set_t_calls_Any_get_0 test_bubble_sort_step.py(4, 8): ✅ pass - assert(78) -test_bubble_sort_step.py(5, 16): ✅ pass - precondition +test_bubble_sort_step.py(5, 8): ✅ pass - assert_assert(101)_calls_Any_get_0 test_bubble_sort_step.py(5, 8): ✅ pass - Check Any_sets! exception +test_bubble_sort_step.py(5, 8): ✅ pass - set_xs_calls_Any_get_0 test_bubble_sort_step.py(6, 8): ✅ pass - Check Any_sets! exception -test_bubble_sort_step.py(7, 7): ✅ pass - precondition -test_bubble_sort_step.py(7, 15): ✅ pass - precondition +test_bubble_sort_step.py(7, 7): ✅ pass - assert_assert(140)_calls_Any_get_0 +test_bubble_sort_step.py(7, 7): ✅ pass - assert_assert(140)_calls_Any_get_1 test_bubble_sort_step.py(7, 7): ✅ pass - Check PGt exception -test_bubble_sort_step.py(8, 8): ✅ pass - precondition +test_bubble_sort_step.py(7, 4): ✅ pass - ite_cond_calls_Any_get_0 +test_bubble_sort_step.py(7, 4): ✅ pass - ite_cond_calls_Any_get_1 +test_bubble_sort_step.py(8, 8): ✅ pass - set_t2_calls_Any_get_0 test_bubble_sort_step.py(8, 8): ✅ pass - assert(163) -test_bubble_sort_step.py(9, 16): ✅ pass - precondition +test_bubble_sort_step.py(9, 8): ✅ pass - assert_assert(187)_calls_Any_get_0 test_bubble_sort_step.py(9, 8): ✅ pass - Check Any_sets! exception +test_bubble_sort_step.py(9, 8): ✅ pass - set_xs_calls_Any_get_0 test_bubble_sort_step.py(10, 8): ✅ pass - Check Any_sets! exception -test_bubble_sort_step.py(11, 7): ✅ pass - precondition -test_bubble_sort_step.py(11, 15): ✅ pass - precondition +test_bubble_sort_step.py(11, 7): ✅ pass - assert_assert(227)_calls_Any_get_0 +test_bubble_sort_step.py(11, 7): ✅ pass - assert_assert(227)_calls_Any_get_1 test_bubble_sort_step.py(11, 7): ✅ pass - Check PGt exception -test_bubble_sort_step.py(12, 8): ✅ pass - precondition -test_bubble_sort_step.py(12, 8): ✅ pass - assert(250) -test_bubble_sort_step.py(13, 16): ✅ pass - precondition -test_bubble_sort_step.py(13, 8): ✅ pass - Check Any_sets! exception -test_bubble_sort_step.py(14, 8): ✅ pass - Check Any_sets! exception -test_bubble_sort_step.py(15, 11): ✅ pass - precondition +test_bubble_sort_step.py(11, 4): ✅ pass - ite_cond_calls_Any_get_0 +test_bubble_sort_step.py(11, 4): ✅ pass - ite_cond_calls_Any_get_1 +test_bubble_sort_step.py(15, 4): ✅ pass - assert_assert(311)_calls_Any_get_0 test_bubble_sort_step.py(15, 4): ✅ pass - sorted first -test_bubble_sort_step.py(16, 11): ✅ pass - precondition +test_bubble_sort_step.py(16, 4): ✅ pass - assert_assert(349)_calls_Any_get_0 test_bubble_sort_step.py(16, 4): ✅ pass - sorted second -test_bubble_sort_step.py(17, 11): ✅ pass - precondition +test_bubble_sort_step.py(17, 4): ✅ pass - assert_assert(388)_calls_Any_get_0 test_bubble_sort_step.py(17, 4): ✅ pass - sorted third -DETAIL: 30 passed, 0 failed, 0 inconclusive +DETAIL: 39 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_empty.expected b/StrataTest/Languages/Python/expected_laurel/test_class_empty.expected index 7f98693936..aab04f3a0d 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_empty.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_empty.expected @@ -1,4 +1,4 @@ -test_class_empty.py(5, 4): ✅ pass - precondition +test_class_empty.py(5, 4): ✅ pass - callElimAssert_requires_4 test_class_empty.py(6, 4): ✅ pass - empty class instantiated DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_field_init.expected b/StrataTest/Languages/Python/expected_laurel/test_class_field_init.expected index 0ff29bd94b..7cb1e2fc89 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_field_init.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_field_init.expected @@ -1,3 +1,2 @@ -test_class_field_init.py(20, 4): ✔️ always true if reached - (CircularBuffer@__init__ requires) Type constraint of size, (CircularBuffer@__init__ requires) Type constraint of name -DETAIL: 1 passed, 0 failed, 0 inconclusive +DETAIL: 0 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_field_use.expected b/StrataTest/Languages/Python/expected_laurel/test_class_field_use.expected index 29a689ea2c..0caaf75c9f 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_field_use.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_field_use.expected @@ -1,6 +1,5 @@ -test_class_field_use.py(13, 4): ✔️ always true if reached - (CircularBuffer@__init__ requires) Type constraint of n test_class_field_use.py(14, 4): ✔️ always true if reached - Check PMul exception test_class_field_use.py(14, 4): ✔️ always true if reached - assert(302) test_class_field_use.py(15, 4): ✔️ always true if reached - Doubling of buffer did not work -DETAIL: 4 passed, 0 failed, 0 inconclusive +DETAIL: 3 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_method_call_from_main.expected b/StrataTest/Languages/Python/expected_laurel/test_class_method_call_from_main.expected index 15fd5c1889..0047be2edb 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_method_call_from_main.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_method_call_from_main.expected @@ -1,5 +1,6 @@ test_class_method_call_from_main.py(10, 8): ❓ unknown - name must not be empty +test_class_method_call_from_main.py(9, 23): ❓ unknown - (Greeter@greet ensures) Return type constraint test_class_method_call_from_main.py(14, 4): ✅ pass - (Greeter@__init__ requires) Type constraint of name test_class_method_call_from_main.py(15, 4): ✅ pass - assert(415) -DETAIL: 2 passed, 0 failed, 1 inconclusive +DETAIL: 2 passed, 0 failed, 2 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_methods.expected b/StrataTest/Languages/Python/expected_laurel/test_class_methods.expected index 9bcf315a1b..36c53a8361 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_methods.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_methods.expected @@ -1,16 +1,11 @@ -test_class_methods.py(34, 4): ✔️ always true if reached - (Account@__init__ requires) Type constraint of owner, (Account@__init__ requires) Type constraint of balance -test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(471)_45 +test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(471)_13 test_class_methods.py(34, 4): ✔️ always true if reached - get_owner should return Alice -test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(564)_48 +test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(564)_15 test_class_methods.py(34, 4): ✔️ always true if reached - get_balance should return 100 -test_class_methods.py(34, 4): ✔️ always true if reached - (Account@set_balance requires) Type constraint of amount -test_class_methods.py(27, 4): ✔️ always true if reached - (Account@set_balance ensures) Return type constraint -test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(678)_53 +test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(678)_17 test_class_methods.py(34, 4): ✔️ always true if reached - set_balance should update balance -test_class_methods.py(34, 4): ✔️ always true if reached - (Origin_test_helper_procedure_Requires)req_name_is_foo, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar test_class_methods.py(31, 4): ✔️ always true if reached - assert_name_is_foo test_class_methods.py(31, 4): ✔️ always true if reached - assert_opt_name_none_or_str test_class_methods.py(31, 4): ✔️ always true if reached - assert_opt_name_none_or_bar -test_class_methods.py(31, 4): ✔️ always true if reached - ensures_maybe_except_none -DETAIL: 14 passed, 0 failed, 0 inconclusive +DETAIL: 9 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_mixed_init.expected b/StrataTest/Languages/Python/expected_laurel/test_class_mixed_init.expected index ee2f8ef831..766329f9a4 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_mixed_init.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_mixed_init.expected @@ -1,6 +1,4 @@ -test_class_mixed_init.py(19, 0): ✔️ always true if reached - (WithInit@__init__ requires) Type constraint of x test_class_mixed_init.py(19, 0): ✔️ always true if reached - class with init -test_class_mixed_init.py(19, 0): ✔️ always true if reached - precondition test_class_mixed_init.py(19, 0): ❓ unknown - class with init -DETAIL: 3 passed, 0 failed, 1 inconclusive +DETAIL: 1 passed, 0 failed, 1 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_no_init.expected b/StrataTest/Languages/Python/expected_laurel/test_class_no_init.expected index a55c76cfe4..7228247375 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_no_init.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_no_init.expected @@ -1,4 +1,4 @@ -test_class_no_init.py(5, 4): ✅ pass - precondition +test_class_no_init.py(5, 4): ✅ pass - callElimAssert_requires_4 test_class_no_init.py(6, 4): ❓ unknown - class without __init__ DETAIL: 1 passed, 0 failed, 1 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_no_init_multi_field.expected b/StrataTest/Languages/Python/expected_laurel/test_class_no_init_multi_field.expected index 13ba7f459b..3dbe40b3b6 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_no_init_multi_field.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_no_init_multi_field.expected @@ -1,4 +1,4 @@ -test_class_no_init_multi_field.py(7, 4): ✅ pass - precondition +test_class_no_init_multi_field.py(7, 4): ✅ pass - callElimAssert_requires_4 test_class_no_init_multi_field.py(8, 4): ✅ pass - class with multiple annotated fields no init DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_no_init_with_method.expected b/StrataTest/Languages/Python/expected_laurel/test_class_no_init_with_method.expected index 82dccbedf2..29b6682a29 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_no_init_with_method.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_no_init_with_method.expected @@ -1,4 +1,5 @@ -test_class_no_init_with_method.py(8, 4): ✅ pass - precondition +test_class_no_init_with_method.py(4, 23): ❓ unknown - (WithMethod@get_x ensures) Return type constraint +test_class_no_init_with_method.py(8, 4): ✅ pass - callElimAssert_requires_4 test_class_no_init_with_method.py(9, 4): ✅ pass - class with method but no __init__ -DETAIL: 2 passed, 0 failed, 0 inconclusive -RESULT: Analysis success +DETAIL: 2 passed, 0 failed, 1 inconclusive +RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_with_methods.expected b/StrataTest/Languages/Python/expected_laurel/test_class_with_methods.expected index 8e46807ff6..1085e02e58 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_with_methods.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_with_methods.expected @@ -5,5 +5,5 @@ test_class_with_methods.py(32, 4): ✔️ always true if reached - get_name shou test_class_with_methods.py(29, 4): ✔️ always true if reached - assert_name_is_foo test_class_with_methods.py(29, 4): ✔️ always true if reached - assert_opt_name_none_or_str test_class_with_methods.py(29, 4): ✔️ always true if reached - assert_opt_name_none_or_bar -DETAIL: 10 passed, 0 failed, 0 inconclusive +DETAIL: 7 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_coerce_int_in_any_list.expected b/StrataTest/Languages/Python/expected_laurel/test_coerce_int_in_any_list.expected index f92d9c87a5..26134635da 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_coerce_int_in_any_list.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_coerce_int_in_any_list.expected @@ -1,6 +1,6 @@ -test_coerce_int_in_any_list.py(5, 11): ✅ pass - precondition +test_coerce_int_in_any_list.py(5, 4): ✅ pass - assert_assert(103)_calls_Any_get_0 test_coerce_int_in_any_list.py(5, 4): ✅ pass - int in Any list -test_coerce_int_in_any_list.py(6, 11): ✅ pass - precondition +test_coerce_int_in_any_list.py(6, 4): ✅ pass - assert_assert(144)_calls_Any_get_0 test_coerce_int_in_any_list.py(6, 4): ✅ pass - str in Any list DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_datetime.expected b/StrataTest/Languages/Python/expected_laurel/test_datetime.expected index 475cfe68af..f627b50117 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_datetime.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_datetime.expected @@ -1,5 +1,8 @@ test_datetime.py(13, 0): ✅ pass - (Origin_datetime_date_Requires)d_type -test_datetime.py(14, 0): ✅ pass - (Origin_timedelta_Requires), (Origin_timedelta_Requires)hours_type, (Origin_timedelta_Requires)days_pos, (Origin_timedelta_Requires)hours_pos +test_datetime.py(14, 0): ✅ pass - (Origin_timedelta_Requires) +test_datetime.py(14, 0): ✅ pass - (Origin_timedelta_Requires)hours_type +test_datetime.py(14, 0): ✅ pass - (Origin_timedelta_Requires)days_pos +test_datetime.py(14, 0): ✅ pass - (Origin_timedelta_Requires)hours_pos test_datetime.py(15, 19): ✅ pass - Check PSub exception test_datetime.py(21, 7): ✅ pass - Check PLe exception test_datetime.py(21, 0): ✅ pass - assert(554) @@ -7,5 +10,5 @@ test_datetime.py(25, 0): ✅ pass - assert(673) test_datetime.py(27, 0): ✅ pass - assert(758) test_datetime.py(30, 7): ✅ pass - Check PLe exception test_datetime.py(30, 0): ✅ pass - assert(859) -DETAIL: 9 passed, 0 failed, 0 inconclusive +DETAIL: 12 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_datetime_now_tz.expected b/StrataTest/Languages/Python/expected_laurel/test_datetime_now_tz.expected index 50f1fb5f5e..4ef6e80e7b 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_datetime_now_tz.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_datetime_now_tz.expected @@ -1,6 +1,9 @@ -test_datetime_now_tz.py(4, 0): ✅ pass - (Origin_timedelta_Requires), (Origin_timedelta_Requires)hours_type, (Origin_timedelta_Requires)days_pos, (Origin_timedelta_Requires)hours_pos +test_datetime_now_tz.py(4, 0): ✅ pass - (Origin_timedelta_Requires) +test_datetime_now_tz.py(4, 0): ✅ pass - (Origin_timedelta_Requires)hours_type +test_datetime_now_tz.py(4, 0): ✅ pass - (Origin_timedelta_Requires)days_pos +test_datetime_now_tz.py(4, 0): ✅ pass - (Origin_timedelta_Requires)hours_pos test_datetime_now_tz.py(5, 18): ✅ pass - Check PSub exception test_datetime_now_tz.py(6, 7): ✅ pass - Check PLe exception test_datetime_now_tz.py(6, 0): ✅ pass - assert(162) -DETAIL: 4 passed, 0 failed, 0 inconclusive +DETAIL: 7 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_default_params.expected b/StrataTest/Languages/Python/expected_laurel/test_default_params.expected index 15898dab3f..395575a21c 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_default_params.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_default_params.expected @@ -1,21 +1,27 @@ test_default_params.py(2, 18): ✅ pass - Check PAdd exception test_default_params.py(2, 4): ✅ pass - assert(58) +test_default_params.py(1, 49): ✅ pass - (greet ensures) Return type constraint test_default_params.py(6, 4): ✅ pass - assert(160) test_default_params.py(7, 4): ✅ pass - assert(180) test_default_params.py(8, 10): ✅ pass - Check PLt exception test_default_params.py(9, 17): ❓ unknown - Check PMul exception test_default_params.py(10, 12): ❓ unknown - Check PAdd exception -test_default_params.py(14, 4): ✅ pass - (greet requires) Type constraint of name, (greet requires) Type constraint of greeting +test_default_params.py(5, 38): ❓ unknown - (power ensures) Return type constraint +test_default_params.py(14, 4): ✅ pass - (greet requires) Type constraint of name +test_default_params.py(14, 4): ✅ pass - (greet requires) Type constraint of greeting test_default_params.py(14, 4): ✅ pass - assert(294) test_default_params.py(15, 4): ❓ unknown - default greeting failed -test_default_params.py(17, 4): ✅ pass - (greet requires) Type constraint of name, (greet requires) Type constraint of greeting +test_default_params.py(17, 4): ✅ pass - (greet requires) Type constraint of name +test_default_params.py(17, 4): ✅ pass - (greet requires) Type constraint of greeting test_default_params.py(17, 4): ✅ pass - assert(386) test_default_params.py(18, 4): ❓ unknown - explicit greeting failed -test_default_params.py(20, 4): ✅ pass - (power requires) Type constraint of base, (power requires) Type constraint of exp +test_default_params.py(20, 4): ✅ pass - (power requires) Type constraint of base +test_default_params.py(20, 4): ✅ pass - (power requires) Type constraint of exp test_default_params.py(20, 4): ✅ pass - assert(478) test_default_params.py(21, 4): ❓ unknown - default power failed -test_default_params.py(23, 4): ✅ pass - (power requires) Type constraint of base, (power requires) Type constraint of exp +test_default_params.py(23, 4): ✅ pass - (power requires) Type constraint of base +test_default_params.py(23, 4): ✅ pass - (power requires) Type constraint of exp test_default_params.py(23, 4): ✅ pass - assert(545) test_default_params.py(24, 4): ❓ unknown - explicit power failed -DETAIL: 13 passed, 0 failed, 6 inconclusive +DETAIL: 18 passed, 0 failed, 7 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_dict_add_key.expected b/StrataTest/Languages/Python/expected_laurel/test_dict_add_key.expected index 5ec9ce4b73..fe764d0f3f 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_dict_add_key.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_dict_add_key.expected @@ -1,5 +1,5 @@ test_dict_add_key.py(3, 4): ✅ pass - Check Any_sets! exception -test_dict_add_key.py(4, 11): ✅ pass - precondition +test_dict_add_key.py(4, 4): ✅ pass - assert_assert(56)_calls_Any_get_0 test_dict_add_key.py(4, 4): ✅ pass - dict add key DETAIL: 3 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_dict_assign.expected b/StrataTest/Languages/Python/expected_laurel/test_dict_assign.expected index f7c7754049..3c6fafeb0a 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_dict_assign.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_dict_assign.expected @@ -1,5 +1,5 @@ test_dict_assign.py(3, 4): ✅ pass - Check Any_sets! exception -test_dict_assign.py(4, 11): ✅ pass - precondition +test_dict_assign.py(4, 4): ✅ pass - assert_assert(62)_calls_Any_get_0 test_dict_assign.py(4, 4): ✅ pass - dict update DETAIL: 3 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_dict_create.expected b/StrataTest/Languages/Python/expected_laurel/test_dict_create.expected index 2f04dc4c1b..5613842891 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_dict_create.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_dict_create.expected @@ -1,6 +1,6 @@ -test_dict_create.py(3, 11): ✅ pass - precondition +test_dict_create.py(3, 4): ✅ pass - assert_assert(53)_calls_Any_get_0 test_dict_create.py(3, 4): ✅ pass - dict access -test_dict_create.py(4, 11): ✅ pass - precondition +test_dict_create.py(4, 4): ✅ pass - assert_assert(91)_calls_Any_get_0 test_dict_create.py(4, 4): ✅ pass - dict access b DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_dict_in.expected b/StrataTest/Languages/Python/expected_laurel/test_dict_in.expected index b00542f78d..57ab802313 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_dict_in.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_dict_in.expected @@ -1,6 +1,6 @@ -test_dict_in.py(3, 11): ✅ pass - precondition +test_dict_in.py(3, 4): ✅ pass - assert_assert(49)_calls_PIn_0 test_dict_in.py(3, 4): ✅ pass - key in dict -test_dict_in.py(4, 11): ✅ pass - precondition +test_dict_in.py(4, 4): ✅ pass - assert_assert(84)_calls_PNotIn_0 test_dict_in.py(4, 4): ✅ pass - key not in dict DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_dict_overwrite.expected b/StrataTest/Languages/Python/expected_laurel/test_dict_overwrite.expected index 48261dcb07..a326876db0 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_dict_overwrite.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_dict_overwrite.expected @@ -1,6 +1,6 @@ test_dict_overwrite.py(3, 4): ✅ pass - Check Any_sets! exception test_dict_overwrite.py(4, 4): ✅ pass - Check Any_sets! exception -test_dict_overwrite.py(5, 11): ✅ pass - precondition +test_dict_overwrite.py(5, 4): ✅ pass - assert_assert(78)_calls_Any_get_0 test_dict_overwrite.py(5, 4): ✅ pass - dict overwrite DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_empty_dict_access.expected b/StrataTest/Languages/Python/expected_laurel/test_empty_dict_access.expected index 26b71e1c82..cb7f5dfa7d 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_empty_dict_access.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_empty_dict_access.expected @@ -1,6 +1,6 @@ +test_empty_dict_access.py(5, 8): ✅ pass - set_r_calls_Any_get_0 test_empty_dict_access.py(3, 4): ✅ pass - assert(33) -test_empty_dict_access.py(4, 7): ✅ pass - precondition -test_empty_dict_access.py(5, 8): ✅ pass - precondition +test_empty_dict_access.py(4, 4): ✅ pass - ite_cond_calls_PIn_0 test_empty_dict_access.py(7, 12): ✅ pass - Check PNeg exception test_empty_dict_access.py(8, 16): ✅ pass - Check PNeg exception test_empty_dict_access.py(8, 4): ✅ pass - missing key guarded diff --git a/StrataTest/Languages/Python/expected_laurel/test_flag_pattern.expected b/StrataTest/Languages/Python/expected_laurel/test_flag_pattern.expected index 5d9741fddb..1ae36f0f29 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_flag_pattern.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_flag_pattern.expected @@ -1,8 +1,9 @@ test_flag_pattern.py(3, 4): ✅ pass - assert(54) -test_flag_pattern.py(4, 4): ✅ pass - precondition -test_flag_pattern.py(5, 11): ✅ pass - precondition +test_flag_pattern.py(4, 4): ✅ pass - assume_assume(81)_calls_PIn_0 +test_flag_pattern.py(5, 11): ✅ pass - assert_assert(108)_calls_PMod_0 test_flag_pattern.py(5, 11): ✅ pass - Check PMod exception +test_flag_pattern.py(5, 8): ✅ pass - ite_cond_calls_PMod_0 test_flag_pattern.py(7, 11): ❓ unknown - Check PNot exception test_flag_pattern.py(7, 4): ❓ unknown - no even numbers -DETAIL: 4 passed, 0 failed, 2 inconclusive +DETAIL: 5 passed, 0 failed, 2 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_for_else_break.expected b/StrataTest/Languages/Python/expected_laurel/test_for_else_break.expected index 480b1225ab..5d1dcabdd9 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_for_else_break.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_for_else_break.expected @@ -1,5 +1,5 @@ test_for_else_break.py(2, 4): ✅ pass - assert(31) -test_for_else_break.py(3, 4): ✅ pass - precondition +test_for_else_break.py(3, 4): ✅ pass - assume_assume(46)_calls_PIn_0 test_for_else_break.py(8, 4): ✅ pass - for else skipped on break DETAIL: 3 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_for_loop.expected b/StrataTest/Languages/Python/expected_laurel/test_for_loop.expected index 6a4e916b20..77a760ea8f 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_for_loop.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_for_loop.expected @@ -1,14 +1,14 @@ test_for_loop.py(3, 4): ✅ pass - assert(64) -test_for_loop.py(4, 4): ✅ pass - precondition +test_for_loop.py(4, 4): ✅ pass - assume_assume(83)_calls_PIn_0 test_for_loop.py(5, 16): ❓ unknown - Check PAdd exception test_for_loop.py(6, 4): ❓ unknown - sum of list should be 15 test_for_loop.py(11, 4): ✅ pass - assert(274) -test_for_loop.py(12, 4): ✅ pass - precondition +test_for_loop.py(12, 4): ✅ pass - assume_assume(293)_calls_PIn_0 test_for_loop.py(13, 11): ✅ pass - Check PGt exception test_for_loop.py(14, 20): ❓ unknown - Check PAdd exception test_for_loop.py(15, 4): ❓ unknown - should count 3 items greater than 3 test_for_loop.py(20, 4): ✅ pass - assert(512) -test_for_loop.py(21, 4): ✅ pass - precondition +test_for_loop.py(21, 4): ✅ pass - assume_assume(531)_calls_PIn_0 test_for_loop.py(25, 4): ❓ unknown (pass on 1 path, unknown on 2 paths) - should have found 30 DETAIL: 7 passed, 0 failed, 5 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_for_range.expected b/StrataTest/Languages/Python/expected_laurel/test_for_range.expected index 2c7d54f6ef..03b0272495 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_for_range.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_for_range.expected @@ -1,13 +1,13 @@ -test_for_range.py(3, 9): ✅ pass - precondition +test_for_range.py(10, 0): ✅ pass - set_i_calls_range_0 +test_for_range.py(3, 0): ✅ pass - set_i_calls_range_0 test_for_range.py(4, 11): ✅ pass - Check PLt exception test_for_range.py(4, 4): ✅ pass - assert(46) test_for_range.py(5, 11): ✅ pass - Check PGe exception test_for_range.py(5, 4): ✅ pass - assert(63) -test_for_range.py(6, 4): ✅ pass - precondition +test_for_range.py(6, 4): ✅ pass - set_j_calls_Any_get_0 test_for_range.py(7, 11): ✅ pass - Check PLt exception test_for_range.py(7, 4): ✅ pass - assert(101) test_for_range.py(10, 15): ✅ pass - Check PNeg exception -test_for_range.py(10, 9): ✅ pass - precondition test_for_range.py(13, 0): ✅ pass - assert(156) DETAIL: 11 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_function_def_calls.expected b/StrataTest/Languages/Python/expected_laurel/test_function_def_calls.expected index 8e006bfe41..62499427b9 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_function_def_calls.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_function_def_calls.expected @@ -1,4 +1,6 @@ -test_function_def_calls.py(6, 4): ❓ unknown - (Origin_test_helper_procedure_Requires)req_name_is_foo, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar +test_function_def_calls.py(6, 4): ❓ unknown - (Origin_test_helper_procedure_Requires)req_name_is_foo +test_function_def_calls.py(6, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str +test_function_def_calls.py(6, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar test_function_def_calls.py(9, 4): ✅ pass - (my_f requires) Type constraint of s -DETAIL: 1 passed, 0 failed, 1 inconclusive +DETAIL: 3 passed, 0 failed, 1 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_if_elif.expected b/StrataTest/Languages/Python/expected_laurel/test_if_elif.expected index 18cd9da09a..2c4b59ca73 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_if_elif.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_if_elif.expected @@ -1,4 +1,5 @@ test_if_elif.py(2, 7): ✅ pass - Check PLt exception +test_if_elif.py(1, 24): ✅ pass - (classify ensures) Return type constraint test_if_elif.py(6, 9): ✅ pass - Check PLt exception test_if_elif.py(12, 23): ✅ pass - Check PNeg exception test_if_elif.py(12, 4): ✅ pass - (classify requires) Type constraint of x @@ -13,5 +14,5 @@ test_if_elif.py(19, 4): ❓ unknown - should be small test_if_elif.py(21, 4): ✅ pass - (classify requires) Type constraint of x test_if_elif.py(21, 4): ✅ pass - assert(416) test_if_elif.py(22, 4): ❓ unknown - should be large -DETAIL: 11 passed, 0 failed, 4 inconclusive +DETAIL: 12 passed, 0 failed, 4 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_int_bool_conversion.expected b/StrataTest/Languages/Python/expected_laurel/test_int_bool_conversion.expected index aec1037296..5076678699 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_int_bool_conversion.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_int_bool_conversion.expected @@ -1,5 +1,5 @@ -test_int_bool_conversion.py(2, 4): ✅ pass - assert(36) test_int_bool_conversion.py(4, 8): ✅ pass - assert(65) +test_int_bool_conversion.py(2, 4): ✅ pass - assert(36) test_int_bool_conversion.py(6, 8): ✅ pass - assert(94) test_int_bool_conversion.py(7, 4): ✅ pass - zero is falsy DETAIL: 4 passed, 0 failed, 0 inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_int_floordiv.expected b/StrataTest/Languages/Python/expected_laurel/test_int_floordiv.expected index 7bce6b7cd0..a62d7083eb 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_int_floordiv.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_int_floordiv.expected @@ -1,8 +1,8 @@ test_int_floordiv.py(2, 4): ✅ pass - assert(29) test_int_floordiv.py(3, 4): ✅ pass - assert(45) -test_int_floordiv.py(4, 13): ✅ pass - precondition +test_int_floordiv.py(4, 13): ✅ pass - assert_assert(69)_calls_PFloorDiv_0 test_int_floordiv.py(4, 13): ✅ pass - Check PFloorDiv exception -test_int_floordiv.py(4, 4): ✅ pass - precondition +test_int_floordiv.py(4, 4): ✅ pass - set_c_calls_PFloorDiv_0 test_int_floordiv.py(4, 4): ✅ pass - assert(60) test_int_floordiv.py(5, 4): ✅ pass - int floor division DETAIL: 7 passed, 0 failed, 0 inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_int_mod.expected b/StrataTest/Languages/Python/expected_laurel/test_int_mod.expected index c93755257b..1a2a0276e6 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_int_mod.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_int_mod.expected @@ -1,8 +1,8 @@ test_int_mod.py(2, 4): ✅ pass - assert(24) test_int_mod.py(3, 4): ✅ pass - assert(40) -test_int_mod.py(4, 13): ✅ pass - precondition +test_int_mod.py(4, 13): ✅ pass - assert_assert(64)_calls_PMod_0 test_int_mod.py(4, 13): ✅ pass - Check PMod exception -test_int_mod.py(4, 4): ✅ pass - precondition +test_int_mod.py(4, 4): ✅ pass - set_c_calls_PMod_0 test_int_mod.py(4, 4): ✅ pass - assert(55) test_int_mod.py(5, 4): ✅ pass - int modulo DETAIL: 7 passed, 0 failed, 0 inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_int_negative_floordiv.expected b/StrataTest/Languages/Python/expected_laurel/test_int_negative_floordiv.expected index e723a2ed74..527ca97690 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_int_negative_floordiv.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_int_negative_floordiv.expected @@ -1,6 +1,7 @@ -test_int_negative_floordiv.py(2, 11): ✅ pass - precondition +test_int_negative_floordiv.py(2, 11): ✅ pass - assert_assert(45)_calls_PFloorDiv_0 test_int_negative_floordiv.py(2, 11): ✅ pass - Check PFloorDiv exception test_int_negative_floordiv.py(2, 24): ✅ pass - Check PNeg exception +test_int_negative_floordiv.py(2, 4): ✅ pass - assert_assert(38)_calls_PFloorDiv_0 test_int_negative_floordiv.py(2, 4): ✅ pass - floor division rounds toward negative infinity -DETAIL: 4 passed, 0 failed, 0 inconclusive +DETAIL: 5 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_int_negative_mod.expected b/StrataTest/Languages/Python/expected_laurel/test_int_negative_mod.expected index e14ddf7e30..6f8a8fbc25 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_int_negative_mod.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_int_negative_mod.expected @@ -1,5 +1,6 @@ -test_int_negative_mod.py(2, 11): ✅ pass - precondition +test_int_negative_mod.py(2, 11): ✅ pass - assert_assert(40)_calls_PMod_0 test_int_negative_mod.py(2, 11): ✅ pass - Check PMod exception +test_int_negative_mod.py(2, 4): ✅ pass - assert_assert(33)_calls_PMod_0 test_int_negative_mod.py(2, 4): ✅ pass - python mod is always non-negative for positive divisor -DETAIL: 3 passed, 0 failed, 0 inconclusive +DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_list_assign.expected b/StrataTest/Languages/Python/expected_laurel/test_list_assign.expected index 6bd8d888f6..032ca9ce08 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_list_assign.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_list_assign.expected @@ -1,5 +1,5 @@ test_list_assign.py(3, 4): ✅ pass - Check Any_sets! exception -test_list_assign.py(4, 11): ✅ pass - precondition +test_list_assign.py(4, 4): ✅ pass - assert_assert(62)_calls_Any_get_0 test_list_assign.py(4, 4): ✅ pass - list element assignment DETAIL: 3 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_list_concat.expected b/StrataTest/Languages/Python/expected_laurel/test_list_concat.expected index c32762406c..bfcfa2f91d 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_list_concat.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_list_concat.expected @@ -1,7 +1,7 @@ test_list_concat.py(4, 8): ✅ pass - Check PAdd exception -test_list_concat.py(5, 11): ✅ pass - precondition +test_list_concat.py(5, 4): ✅ pass - assert_assert(72)_calls_Any_get_0 test_list_concat.py(5, 4): ✅ pass - first -test_list_concat.py(6, 11): ✅ pass - precondition +test_list_concat.py(6, 4): ✅ pass - assert_assert(102)_calls_Any_get_0 test_list_concat.py(6, 4): ✅ pass - last DETAIL: 5 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_list_create.expected b/StrataTest/Languages/Python/expected_laurel/test_list_create.expected index 1d405dd67b..5ff4e591a6 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_list_create.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_list_create.expected @@ -1,6 +1,6 @@ -test_list_create.py(3, 11): ✅ pass - precondition +test_list_create.py(3, 4): ✅ pass - assert_assert(47)_calls_Any_get_0 test_list_create.py(3, 4): ✅ pass - first element -test_list_create.py(4, 11): ✅ pass - precondition +test_list_create.py(4, 4): ✅ pass - assert_assert(86)_calls_Any_get_0 test_list_create.py(4, 4): ✅ pass - last element DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_list_empty.expected b/StrataTest/Languages/Python/expected_laurel/test_list_empty.expected index 84c8f55f2c..3c2df0a452 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_list_empty.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_list_empty.expected @@ -1,5 +1,5 @@ test_list_empty.py(3, 4): ✅ pass - assert(39) -test_list_empty.py(4, 4): ✅ pass - precondition +test_list_empty.py(4, 4): ✅ pass - assume_assume(58)_calls_PIn_0 test_list_empty.py(5, 16): ✅ pass - Check PAdd exception test_list_empty.py(6, 4): ✅ pass - empty list DETAIL: 4 passed, 0 failed, 0 inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_list_in.expected b/StrataTest/Languages/Python/expected_laurel/test_list_in.expected index 22ca9da73b..de531eb5d5 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_list_in.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_list_in.expected @@ -1,6 +1,6 @@ -test_list_in.py(3, 11): ✅ pass - precondition +test_list_in.py(3, 4): ✅ pass - assert_assert(49)_calls_PIn_0 test_list_in.py(3, 4): ✅ pass - element in list -test_list_in.py(4, 11): ✅ pass - precondition +test_list_in.py(4, 4): ✅ pass - assert_assert(87)_calls_PNotIn_0 test_list_in.py(4, 4): ✅ pass - element not in list DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_loops.expected b/StrataTest/Languages/Python/expected_laurel/test_loops.expected index 40c66e20c0..4adb7f6b70 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_loops.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_loops.expected @@ -1,22 +1,26 @@ test_loops.py(3, 4): ✅ pass - assert(38) -test_loops.py(4, 4): ✅ pass - precondition +test_loops.py(4, 4): ✅ pass - assume_assume(53)_calls_PIn_0 test_loops.py(5, 12): ❓ unknown - Check PAdd exception test_loops.py(6, 11): ❓ unknown - Check PGt exception test_loops.py(6, 4): ❓ unknown - simple loop incremented test_loops.py(9, 4): ✅ pass - assert(174) -test_loops.py(10, 4): ❓ unknown - precondition +test_loops.py(10, 4): ❓ unknown - set_a_calls_Any_get_0 +test_loops.py(10, 4): ❓ unknown - set_b_calls_Any_get_0 test_loops.py(11, 13): ❓ unknown - Check PSub exception test_loops.py(12, 11): ❓ unknown - Check PLt exception test_loops.py(12, 4): ❓ unknown - tuple unpacking decremented test_loops.py(15, 4): ✅ pass - assert(337) -test_loops.py(16, 4): ❓ unknown - precondition +test_loops.py(16, 4): ❓ unknown - set_x_calls_Any_get_0 +test_loops.py(16, 4): ❓ unknown - set_tuple_360_calls_Any_get_0 +test_loops.py(16, 4): ❓ unknown - set_y_calls_Any_get_0 +test_loops.py(16, 4): ❓ unknown - set_z_calls_Any_get_0 test_loops.py(17, 13): ❓ unknown - Check PAdd exception test_loops.py(18, 11): ❓ unknown - Check PGt exception test_loops.py(18, 4): ❓ unknown - nested unpacking incremented test_loops.py(21, 4): ✅ pass - assert(477) test_loops.py(22, 10): ✅ pass - Check PGt exception test_loops.py(23, 13): ❓ unknown - Check PSub exception -test_loops.py(24, 11): ✅ pass - Check PLe exception -test_loops.py(24, 4): ✅ pass - while loop did not increase n4 -DETAIL: 8 passed, 0 failed, 12 inconclusive +test_loops.py(24, 11): ❓ unknown - Check PLe exception +test_loops.py(24, 4): ❓ unknown - while loop did not increase n4 +DETAIL: 6 passed, 0 failed, 18 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_method_call_with_kwargs.expected b/StrataTest/Languages/Python/expected_laurel/test_method_call_with_kwargs.expected index 2503bcbd16..315f62f13d 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_method_call_with_kwargs.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_method_call_with_kwargs.expected @@ -1,4 +1,9 @@ -test_method_call_with_kwargs.py(8, 0): ✅ pass - (MyClass@__init__ requires) Type constraint of ip1, (MyClass@__init__ requires) Type constraint of ip2, (MyClass@__init__ requires) Type constraint of ip3 -test_method_call_with_kwargs.py(9, 0): ✅ pass - (MyClass@some_method requires) Type constraint of ip1, (MyClass@some_method requires) Type constraint of ip2, (MyClass@some_method requires) Type constraint of ip3 -DETAIL: 2 passed, 0 failed, 0 inconclusive +test_method_call_with_kwargs.py(5, 82): ✅ pass - (MyClass@some_method ensures) Return type constraint +test_method_call_with_kwargs.py(8, 0): ✅ pass - (MyClass@__init__ requires) Type constraint of ip1 +test_method_call_with_kwargs.py(8, 0): ✅ pass - (MyClass@__init__ requires) Type constraint of ip2 +test_method_call_with_kwargs.py(8, 0): ✅ pass - (MyClass@__init__ requires) Type constraint of ip3 +test_method_call_with_kwargs.py(9, 0): ✅ pass - (MyClass@some_method requires) Type constraint of ip1 +test_method_call_with_kwargs.py(9, 0): ✅ pass - (MyClass@some_method requires) Type constraint of ip2 +test_method_call_with_kwargs.py(9, 0): ✅ pass - (MyClass@some_method requires) Type constraint of ip3 +DETAIL: 7 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_method_kwargs_no_hierarchy.expected b/StrataTest/Languages/Python/expected_laurel/test_method_kwargs_no_hierarchy.expected index 14ec6f436e..56de827e26 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_method_kwargs_no_hierarchy.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_method_kwargs_no_hierarchy.expected @@ -8,5 +8,5 @@ test_method_kwargs_no_hierarchy.py(11, 18): ✅ pass - (Calculator@add requires) test_method_kwargs_no_hierarchy.py(11, 4): ✅ pass - assert(254) test_method_kwargs_no_hierarchy.py(12, 4): ❓ unknown - assert(286) test_method_kwargs_no_hierarchy.py(8, 14): ✅ pass - (main ensures) Return type constraint -DETAIL: 6 passed, 0 failed, 2 inconclusive +DETAIL: 8 passed, 0 failed, 2 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_mixed_types_list.expected b/StrataTest/Languages/Python/expected_laurel/test_mixed_types_list.expected index d320eab756..6ea1964407 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_mixed_types_list.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_mixed_types_list.expected @@ -1,6 +1,6 @@ -test_mixed_types_list.py(3, 11): ✅ pass - precondition +test_mixed_types_list.py(3, 4): ✅ pass - assert_assert(56)_calls_Any_get_0 test_mixed_types_list.py(3, 4): ✅ pass - int element -test_mixed_types_list.py(4, 11): ✅ pass - precondition +test_mixed_types_list.py(4, 4): ✅ pass - assert_assert(93)_calls_Any_get_0 test_mixed_types_list.py(4, 4): ✅ pass - str element DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_module_level.expected b/StrataTest/Languages/Python/expected_laurel/test_module_level.expected index e1d5280f6e..d6bc9a6556 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_module_level.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_module_level.expected @@ -1,13 +1,13 @@ -test_module_level.py(9, 7): ✅ pass - precondition +test_module_level.py(9, 0): ✅ pass - assert_assert(115)_calls_Any_get_0 test_module_level.py(9, 0): ✅ pass - assert(115) -test_module_level.py(10, 7): ✅ pass - precondition +test_module_level.py(10, 0): ✅ pass - assert_assert(145)_calls_Any_get_0 test_module_level.py(10, 0): ✅ pass - assert(145) test_module_level.py(12, 0): ✅ pass - Check Any_sets! exception -test_module_level.py(13, 7): ✅ pass - precondition +test_module_level.py(13, 0): ✅ pass - assert_assert(201)_calls_Any_get_0 test_module_level.py(13, 0): ✅ pass - assert(201) -test_module_level.py(14, 7): ✅ pass - precondition +test_module_level.py(14, 0): ✅ pass - assert_assert(236)_calls_PIn_0 test_module_level.py(14, 0): ✅ pass - assert(236) -test_module_level.py(15, 7): ✅ pass - precondition +test_module_level.py(15, 0): ✅ pass - assert_assert(258)_calls_PNotIn_0 test_module_level.py(15, 0): ✅ pass - assert(258) DETAIL: 11 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_multi_function.expected b/StrataTest/Languages/Python/expected_laurel/test_multi_function.expected index f0782a29e9..1408f7cb98 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_multi_function.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_multi_function.expected @@ -1,12 +1,18 @@ -test_multi_function.py(9, 7): ✅ pass - precondition -test_multi_function.py(11, 7): ✅ pass - precondition -test_multi_function.py(16, 4): ✅ pass - (create_config requires) Type constraint of name, (create_config requires) Type constraint of value +test_multi_function.py(4, 44): ✅ pass - (create_config ensures) Return type constraint +test_multi_function.py(9, 4): ✅ pass - ite_cond_calls_PNotIn_0 +test_multi_function.py(8, 47): ✅ pass - (validate_config ensures) Return type constraint +test_multi_function.py(11, 4): ✅ pass - ite_cond_calls_PNotIn_0 +test_multi_function.py(16, 4): ✅ pass - (create_config requires) Type constraint of name +test_multi_function.py(16, 4): ✅ pass - (create_config requires) Type constraint of value test_multi_function.py(17, 4): ✅ pass - (validate_config requires) Type constraint of config test_multi_function.py(17, 4): ✅ pass - assert(485) test_multi_function.py(18, 7): ✅ pass - Check PNot exception -test_multi_function.py(20, 4): ❓ unknown - precondition -test_multi_function.py(23, 4): ✅ pass - (process_config requires) Type constraint of name, (process_config requires) Type constraint of value +test_multi_function.py(20, 4): ❓ unknown - set_LaurelResult_calls_Any_get_0 +test_multi_function.py(23, 4): ✅ pass - (process_config requires) Type constraint of name +test_multi_function.py(23, 4): ✅ pass - (process_config requires) Type constraint of value test_multi_function.py(24, 4): ❓ unknown - process_config should return value -test_multi_function.py(26, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar -DETAIL: 8 passed, 0 failed, 2 inconclusive +test_multi_function.py(26, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo +test_multi_function.py(26, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str +test_multi_function.py(26, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar +DETAIL: 14 passed, 0 failed, 2 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_nested_calls.expected b/StrataTest/Languages/Python/expected_laurel/test_nested_calls.expected index bc29103fab..0f4bb96d26 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_nested_calls.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_nested_calls.expected @@ -1,5 +1,7 @@ test_nested_calls.py(2, 11): ✅ pass - Check PMul exception +test_nested_calls.py(1, 22): ✅ pass - (double ensures) Return type constraint test_nested_calls.py(5, 11): ✅ pass - Check PAdd exception +test_nested_calls.py(4, 23): ✅ pass - (add_one ensures) Return type constraint test_nested_calls.py(8, 4): ✅ pass - (double requires) Type constraint of x test_nested_calls.py(8, 4): ✅ pass - assert(107) test_nested_calls.py(9, 4): ✅ pass - (double requires) Type constraint of x @@ -15,5 +17,5 @@ test_nested_calls.py(16, 4): ✅ pass - assert(309) test_nested_calls.py(17, 4): ✅ pass - (double requires) Type constraint of x test_nested_calls.py(17, 4): ✅ pass - assert(333) test_nested_calls.py(18, 4): ❓ unknown - double(add_one(4)) should be 10 -DETAIL: 14 passed, 0 failed, 3 inconclusive +DETAIL: 16 passed, 0 failed, 3 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_nested_optional.expected b/StrataTest/Languages/Python/expected_laurel/test_nested_optional.expected index 6aeed2d9e5..e32ca89fd6 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_nested_optional.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_nested_optional.expected @@ -1,4 +1,4 @@ -test_nested_optional.py(5, 11): ✅ pass - precondition +test_nested_optional.py(5, 4): ✅ pass - assert_assert(90)_calls_Any_get_0 test_nested_optional.py(5, 4): ✅ pass - nested optional list DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_none_in_list.expected b/StrataTest/Languages/Python/expected_laurel/test_none_in_list.expected index ce2c8b27ee..11f44bb821 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_none_in_list.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_none_in_list.expected @@ -1,4 +1,4 @@ -test_none_in_list.py(3, 11): ✅ pass - precondition +test_none_in_list.py(3, 4): ✅ pass - assert_assert(38)_calls_Any_get_0 test_none_in_list.py(3, 4): ✅ pass - None in list DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_param_reassign.expected b/StrataTest/Languages/Python/expected_laurel/test_param_reassign.expected index 286d5bc701..57dfaf7a9e 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_param_reassign.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_param_reassign.expected @@ -1,25 +1,33 @@ test_param_reassign.py(2, 8): ✅ pass - Check PAdd exception +test_param_reassign.py(1, 37): ✅ pass - (single_param_reassign ensures) Return type constraint test_param_reassign.py(6, 8): ✅ pass - Check PAdd exception test_param_reassign.py(7, 8): ✅ pass - Check PMul exception test_param_reassign.py(8, 11): ✅ pass - Check PAdd exception +test_param_reassign.py(5, 44): ✅ pass - (multi_param_reassign ensures) Return type constraint test_param_reassign.py(11, 11): ✅ pass - Check PAdd exception +test_param_reassign.py(10, 41): ✅ pass - (no_param_reassign ensures) Return type constraint test_param_reassign.py(14, 8): ✅ pass - Check PAdd exception +test_param_reassign.py(13, 46): ✅ pass - (partial_param_reassign ensures) Return type constraint test_param_reassign.py(18, 8): ✅ pass - Check PAdd exception test_param_reassign.py(19, 8): ✅ pass - Check PMul exception +test_param_reassign.py(17, 36): ✅ pass - (param_reassign_twice ensures) Return type constraint test_param_reassign.py(23, 4): ✅ pass - (single_param_reassign requires) Type constraint of x test_param_reassign.py(23, 4): ✅ pass - assert(422) test_param_reassign.py(24, 4): ❓ unknown - single reassign -test_param_reassign.py(26, 4): ✅ pass - (multi_param_reassign requires) Type constraint of a, (multi_param_reassign requires) Type constraint of b +test_param_reassign.py(26, 4): ✅ pass - (multi_param_reassign requires) Type constraint of a +test_param_reassign.py(26, 4): ✅ pass - (multi_param_reassign requires) Type constraint of b test_param_reassign.py(26, 4): ✅ pass - assert(500) test_param_reassign.py(27, 4): ❓ unknown - multi reassign -test_param_reassign.py(29, 4): ✅ pass - (no_param_reassign requires) Type constraint of x, (no_param_reassign requires) Type constraint of y +test_param_reassign.py(29, 4): ✅ pass - (no_param_reassign requires) Type constraint of x +test_param_reassign.py(29, 4): ✅ pass - (no_param_reassign requires) Type constraint of y test_param_reassign.py(29, 4): ✅ pass - assert(580) test_param_reassign.py(30, 4): ❓ unknown - no reassign -test_param_reassign.py(32, 4): ✅ pass - (partial_param_reassign requires) Type constraint of x, (partial_param_reassign requires) Type constraint of y +test_param_reassign.py(32, 4): ✅ pass - (partial_param_reassign requires) Type constraint of x +test_param_reassign.py(32, 4): ✅ pass - (partial_param_reassign requires) Type constraint of y test_param_reassign.py(32, 4): ✅ pass - assert(653) test_param_reassign.py(33, 4): ❓ unknown - partial reassign test_param_reassign.py(35, 4): ✅ pass - (param_reassign_twice requires) Type constraint of x test_param_reassign.py(35, 4): ✅ pass - assert(738) test_param_reassign.py(36, 4): ❓ unknown - reassign twice -DETAIL: 18 passed, 0 failed, 5 inconclusive +DETAIL: 26 passed, 0 failed, 5 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_param_reassign_kwargs.expected b/StrataTest/Languages/Python/expected_laurel/test_param_reassign_kwargs.expected index 996c36bc2f..330fc8092f 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_param_reassign_kwargs.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_param_reassign_kwargs.expected @@ -1,6 +1,8 @@ test_param_reassign_kwargs.py(2, 11): ✅ pass - Check PAdd exception -test_param_reassign_kwargs.py(6, 4): ✅ pass - (greet requires) Type constraint of name, (greet requires) Type constraint of greeting +test_param_reassign_kwargs.py(1, 59): ✅ pass - (greet ensures) Return type constraint +test_param_reassign_kwargs.py(6, 4): ✅ pass - (greet requires) Type constraint of name +test_param_reassign_kwargs.py(6, 4): ✅ pass - (greet requires) Type constraint of greeting test_param_reassign_kwargs.py(6, 4): ✅ pass - assert(137) test_param_reassign_kwargs.py(7, 4): ❓ unknown - kwargs call -DETAIL: 3 passed, 0 failed, 1 inconclusive +DETAIL: 5 passed, 0 failed, 1 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_precondition_verification.expected b/StrataTest/Languages/Python/expected_laurel/test_precondition_verification.expected index 97ff1a49a1..30acce18e1 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_precondition_verification.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_precondition_verification.expected @@ -1,6 +1,14 @@ -test_precondition_verification.py(8, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar -test_precondition_verification.py(11, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar -test_precondition_verification.py(14, 4): ❓ unknown - (Origin_test_helper_procedure_Requires)req_name_is_foo, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar -test_precondition_verification.py(17, 4): ❓ unknown - (Origin_test_helper_procedure_Requires)req_name_is_foo, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str, (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar -DETAIL: 2 passed, 0 failed, 2 inconclusive +test_precondition_verification.py(8, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo +test_precondition_verification.py(8, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str +test_precondition_verification.py(8, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar +test_precondition_verification.py(11, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo +test_precondition_verification.py(11, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str +test_precondition_verification.py(11, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar +test_precondition_verification.py(14, 4): ❓ unknown - (Origin_test_helper_procedure_Requires)req_name_is_foo +test_precondition_verification.py(14, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str +test_precondition_verification.py(14, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar +test_precondition_verification.py(17, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo +test_precondition_verification.py(17, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str +test_precondition_verification.py(17, 4): ❓ unknown - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar +DETAIL: 10 passed, 0 failed, 2 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_procedure_in_assert.expected b/StrataTest/Languages/Python/expected_laurel/test_procedure_in_assert.expected index 9f62e33697..8d71e8b122 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_procedure_in_assert.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_procedure_in_assert.expected @@ -1,7 +1,11 @@ test_procedure_in_assert.py(4, 4): ✅ pass - assert(55) -test_procedure_in_assert.py(5, 4): ✅ pass - (Origin_timedelta_Requires), (Origin_timedelta_Requires)hours_type, (Origin_timedelta_Requires)days_pos, (Origin_timedelta_Requires)hours_pos +test_procedure_in_assert.py(5, 4): ✅ pass - (Origin_timedelta_Requires) +test_procedure_in_assert.py(5, 4): ✅ pass - (Origin_timedelta_Requires)hours_type +test_procedure_in_assert.py(5, 4): ✅ pass - (Origin_timedelta_Requires)days_pos +test_procedure_in_assert.py(5, 4): ✅ pass - (Origin_timedelta_Requires)hours_pos test_procedure_in_assert.py(5, 17): ✅ pass - Check PSub exception test_procedure_in_assert.py(6, 4): ✅ pass - assert(117) test_procedure_in_assert.py(7, 4): ✅ pass - should pass -DETAIL: 5 passed, 0 failed, 0 inconclusive +test_procedure_in_assert.py(3, 14): ✅ pass - (main ensures) Return type constraint +DETAIL: 9 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_regex_negative.expected b/StrataTest/Languages/Python/expected_laurel/test_regex_negative.expected index ffc6566e42..2d85b2d64a 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_regex_negative.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_regex_negative.expected @@ -1,51 +1,51 @@ -test_regex_negative.py(9, 4): ✅ pass - precondition +test_regex_negative.py(9, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_negative.py(10, 4): ❓ unknown - EXPECTED_FAIL: fullmatch a on b -test_regex_negative.py(12, 4): ✅ pass - precondition +test_regex_negative.py(12, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_negative.py(13, 4): ❓ unknown - EXPECTED_FAIL: fullmatch abc on abd -test_regex_negative.py(15, 4): ✅ pass - precondition +test_regex_negative.py(15, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_negative.py(16, 4): ❓ unknown - EXPECTED_FAIL: fullmatch [a-z]+ on ABC -test_regex_negative.py(19, 4): ✅ pass - precondition +test_regex_negative.py(19, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_negative.py(20, 4): ❓ unknown - EXPECTED_FAIL: fullmatch ^abc$ on abcd -test_regex_negative.py(22, 4): ✅ pass - precondition +test_regex_negative.py(22, 4): ✅ pass - set_m_calls_re_search_0 test_regex_negative.py(23, 4): ❓ unknown - EXPECTED_FAIL: search ^abc in xabc -test_regex_negative.py(25, 4): ✅ pass - precondition +test_regex_negative.py(25, 4): ✅ pass - set_m_calls_re_search_0 test_regex_negative.py(26, 4): ❓ unknown - EXPECTED_FAIL: search abc$ in abcx -test_regex_negative.py(28, 4): ✅ pass - precondition +test_regex_negative.py(28, 4): ✅ pass - set_m_calls_re_match_0 test_regex_negative.py(29, 4): ❓ unknown - EXPECTED_FAIL: match ^a$ in ab -test_regex_negative.py(32, 4): ✅ pass - precondition -test_regex_negative.py(33, 4): ✅ pass - precondition +test_regex_negative.py(32, 4): ✅ pass - set_p_calls_re_compile_0 +test_regex_negative.py(33, 4): ✅ pass - set_m_calls_re_search_0 test_regex_negative.py(34, 4): ❓ unknown - EXPECTED_FAIL: compiled ^abc$ search xabc -test_regex_negative.py(36, 4): ✅ pass - precondition +test_regex_negative.py(36, 4): ✅ pass - set_m_calls_re_match_0 test_regex_negative.py(37, 4): ❓ unknown - EXPECTED_FAIL: compiled ^abc$ match abcx -test_regex_negative.py(44, 8): ✅ pass - precondition +test_regex_negative.py(44, 8): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_negative.py(47, 4): ❓ unknown - malformed: unmatched paren should raise -test_regex_negative.py(51, 8): ✅ pass - precondition +test_regex_negative.py(51, 8): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_negative.py(54, 4): ❓ unknown - malformed: nothing to repeat should raise -test_regex_negative.py(58, 8): ✅ pass - precondition +test_regex_negative.py(58, 8): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_negative.py(61, 4): ❓ unknown - malformed: bad bounds should raise -test_regex_negative.py(65, 8): ✅ pass - precondition +test_regex_negative.py(65, 8): ✅ pass - set_m_calls_re_search_0 test_regex_negative.py(68, 4): ❓ unknown - malformed: search with bad pattern should raise -test_regex_negative.py(72, 8): ✅ pass - precondition +test_regex_negative.py(72, 8): ✅ pass - set_m_calls_re_match_0 test_regex_negative.py(75, 4): ❓ unknown - malformed: match with bad pattern should raise -test_regex_negative.py(83, 4): ✅ pass - precondition +test_regex_negative.py(83, 4): ✅ pass - set_m_calls_re_search_0 test_regex_negative.py(84, 4): ❓ unknown - unsupported: search \S+ should match non-empty non-whitespace -test_regex_negative.py(86, 4): ✅ pass - precondition +test_regex_negative.py(86, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_negative.py(87, 4): ❓ unknown - unsupported: fullmatch \d+ on digit string -test_regex_negative.py(89, 4): ✅ pass - precondition +test_regex_negative.py(89, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_negative.py(90, 4): ❓ unknown - unsupported: fullmatch \w+ on word string -test_regex_negative.py(92, 4): ✅ pass - precondition +test_regex_negative.py(92, 4): ✅ pass - set_m_calls_re_search_0 test_regex_negative.py(93, 4): ❓ unknown - unsupported: search \s+ finds whitespace -test_regex_negative.py(96, 4): ✅ pass - precondition +test_regex_negative.py(96, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_negative.py(97, 4): ❓ unknown - unsupported: fullmatch [a-z\d]+ on alphanumeric -test_regex_negative.py(99, 4): ✅ pass - precondition +test_regex_negative.py(99, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_negative.py(100, 4): ❓ unknown - unsupported: fullmatch [\w\-]+ on word with dash -test_regex_negative.py(103, 4): ✅ pass - precondition +test_regex_negative.py(103, 4): ✅ pass - set_m_calls_re_search_0 test_regex_negative.py(104, 4): ❓ unknown - unsupported: search \t+ on tab string -test_regex_negative.py(106, 4): ✅ pass - precondition +test_regex_negative.py(106, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_negative.py(107, 4): ❓ unknown - unsupported: fullmatch [^\n]+ on non-newline string -test_regex_negative.py(110, 4): ✅ pass - precondition +test_regex_negative.py(110, 4): ✅ pass - set_m_calls_re_search_0 test_regex_negative.py(111, 4): ❓ unknown - unsupported: non-greedy .*? quantifier -test_regex_negative.py(113, 4): ✅ pass - precondition +test_regex_negative.py(113, 4): ✅ pass - set_m_calls_re_search_0 test_regex_negative.py(114, 4): ❓ unknown - unsupported: positive lookahead (?=foo) DETAIL: 25 passed, 0 failed, 24 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_regex_positive.expected b/StrataTest/Languages/Python/expected_laurel/test_regex_positive.expected index c4291c66a4..58993070b1 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_regex_positive.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_regex_positive.expected @@ -1,284 +1,284 @@ -test_regex_positive.py(7, 4): ✅ pass - precondition +test_regex_positive.py(7, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(8, 4): ✅ pass - fullmatch literal should match -test_regex_positive.py(10, 4): ✅ pass - precondition +test_regex_positive.py(10, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(11, 4): ✅ pass - fullmatch literal should reject extra chars -test_regex_positive.py(14, 4): ✅ pass - precondition +test_regex_positive.py(14, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(15, 4): ✅ pass - fullmatch char class should match -test_regex_positive.py(17, 4): ✅ pass - precondition +test_regex_positive.py(17, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(18, 4): ✅ pass - fullmatch char class should reject uppercase -test_regex_positive.py(21, 4): ✅ pass - precondition +test_regex_positive.py(21, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(22, 4): ✅ pass - fullmatch negated class should match non-digits -test_regex_positive.py(24, 4): ✅ pass - precondition +test_regex_positive.py(24, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(25, 4): ✅ pass - fullmatch negated class should reject digits -test_regex_positive.py(28, 4): ✅ pass - precondition +test_regex_positive.py(28, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(29, 4): ✅ pass - fullmatch dot-plus should match non-empty -test_regex_positive.py(31, 4): ✅ pass - precondition +test_regex_positive.py(31, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(32, 4): ✅ pass - fullmatch single dot should reject two chars -test_regex_positive.py(35, 4): ✅ pass - precondition +test_regex_positive.py(35, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(36, 4): ✅ pass - fullmatch a* should match empty -test_regex_positive.py(38, 4): ✅ pass - precondition +test_regex_positive.py(38, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(39, 4): ✅ pass - fullmatch a* should match repeated -test_regex_positive.py(41, 4): ✅ pass - precondition +test_regex_positive.py(41, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(42, 4): ✅ pass - fullmatch a* should reject non-a -test_regex_positive.py(45, 4): ✅ pass - precondition +test_regex_positive.py(45, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(46, 4): ✅ pass - fullmatch a+ should reject empty -test_regex_positive.py(48, 4): ✅ pass - precondition +test_regex_positive.py(48, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(49, 4): ✅ pass - fullmatch a+ should match one-or-more -test_regex_positive.py(52, 4): ✅ pass - precondition +test_regex_positive.py(52, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(53, 4): ✅ pass - fullmatch ab?c should match without b -test_regex_positive.py(55, 4): ✅ pass - precondition +test_regex_positive.py(55, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(56, 4): ✅ pass - fullmatch ab?c should match with b -test_regex_positive.py(58, 4): ✅ pass - precondition +test_regex_positive.py(58, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(59, 4): ✅ pass - fullmatch ab?c should reject two b's -test_regex_positive.py(62, 4): ✅ pass - precondition +test_regex_positive.py(62, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(63, 4): ✅ pass - fullmatch alternation should match first -test_regex_positive.py(65, 4): ✅ pass - precondition +test_regex_positive.py(65, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(66, 4): ✅ pass - fullmatch alternation should match second -test_regex_positive.py(68, 4): ✅ pass - precondition +test_regex_positive.py(68, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(69, 4): ✅ pass - fullmatch alternation should reject other -test_regex_positive.py(72, 4): ✅ pass - precondition +test_regex_positive.py(72, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(73, 4): ✅ pass - fullmatch concat should match -test_regex_positive.py(75, 4): ✅ pass - precondition +test_regex_positive.py(75, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(76, 4): ✅ pass - fullmatch concat should reject wrong order -test_regex_positive.py(80, 4): ✅ pass - precondition +test_regex_positive.py(80, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(81, 4): ✅ pass - match should match at start -test_regex_positive.py(83, 4): ✅ pass - precondition +test_regex_positive.py(83, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(84, 4): ✅ pass - match should reject when not at start -test_regex_positive.py(86, 4): ✅ pass - precondition +test_regex_positive.py(86, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(87, 4): ✅ pass - match should match prefix -test_regex_positive.py(89, 4): ✅ pass - precondition +test_regex_positive.py(89, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(90, 4): ✅ pass - match should reject non-prefix -test_regex_positive.py(94, 4): ✅ pass - precondition +test_regex_positive.py(94, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(95, 4): ✅ pass - search should find digits in middle -test_regex_positive.py(97, 4): ✅ pass - precondition +test_regex_positive.py(97, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(98, 4): ✅ pass - search should reject when no digits -test_regex_positive.py(100, 4): ✅ pass - precondition +test_regex_positive.py(100, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(101, 4): ✅ pass - search should find substring -test_regex_positive.py(103, 4): ✅ pass - precondition +test_regex_positive.py(103, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(104, 4): ✅ pass - search should reject missing substring -test_regex_positive.py(108, 4): ✅ pass - precondition -test_regex_positive.py(110, 4): ✅ pass - precondition -test_regex_positive.py(111, 4): ❓ unknown - compiled fullmatch should match -test_regex_positive.py(113, 4): ✅ pass - precondition -test_regex_positive.py(114, 4): ❓ unknown - compiled fullmatch should reject uppercase -test_regex_positive.py(116, 4): ✅ pass - precondition -test_regex_positive.py(117, 4): ❓ unknown - compiled match should match prefix -test_regex_positive.py(119, 4): ✅ pass - precondition -test_regex_positive.py(120, 4): ❓ unknown - compiled search should find in middle -test_regex_positive.py(125, 4): ✅ pass - precondition +test_regex_positive.py(108, 4): ✅ pass - set_p_calls_re_compile_0 +test_regex_positive.py(110, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(111, 4): ✅ pass - compiled fullmatch should match +test_regex_positive.py(113, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(114, 4): ✅ pass - compiled fullmatch should reject uppercase +test_regex_positive.py(116, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(117, 4): ✅ pass - compiled match should match prefix +test_regex_positive.py(119, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(120, 4): ✅ pass - compiled search should find in middle +test_regex_positive.py(125, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(126, 4): ✅ pass - fullmatch empty pattern on empty string -test_regex_positive.py(128, 4): ✅ pass - precondition +test_regex_positive.py(128, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(129, 4): ✅ pass - fullmatch empty pattern on non-empty string -test_regex_positive.py(132, 4): ✅ pass - precondition +test_regex_positive.py(132, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(133, 4): ✅ pass - fullmatch single char -test_regex_positive.py(135, 4): ✅ pass - precondition +test_regex_positive.py(135, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(136, 4): ✅ pass - fullmatch single char mismatch -test_regex_positive.py(139, 4): ✅ pass - precondition +test_regex_positive.py(139, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(140, 4): ✅ pass - fullmatch nested group-plus -test_regex_positive.py(142, 4): ✅ pass - precondition +test_regex_positive.py(142, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(143, 4): ✅ pass - fullmatch nested group-plus mismatch -test_regex_positive.py(146, 4): ✅ pass - precondition +test_regex_positive.py(146, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(147, 4): ✅ pass - fullmatch loop min -test_regex_positive.py(149, 4): ✅ pass - precondition +test_regex_positive.py(149, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(150, 4): ✅ pass - fullmatch loop max -test_regex_positive.py(152, 4): ✅ pass - precondition +test_regex_positive.py(152, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(153, 4): ✅ pass - fullmatch loop below min -test_regex_positive.py(155, 4): ✅ pass - precondition +test_regex_positive.py(155, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(156, 4): ✅ pass - fullmatch loop above max -test_regex_positive.py(159, 4): ✅ pass - precondition +test_regex_positive.py(159, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(160, 4): ✅ pass - fullmatch group loop match -test_regex_positive.py(162, 4): ✅ pass - precondition +test_regex_positive.py(162, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(163, 4): ✅ pass - fullmatch group loop too few -test_regex_positive.py(165, 4): ✅ pass - precondition +test_regex_positive.py(165, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(166, 4): ✅ pass - fullmatch group loop 3 reps -test_regex_positive.py(168, 4): ✅ pass - precondition +test_regex_positive.py(168, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(169, 4): ✅ pass - fullmatch group loop 1 rep -test_regex_positive.py(174, 4): ✅ pass - precondition +test_regex_positive.py(174, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(175, 4): ✅ pass - fullmatch ^a match -test_regex_positive.py(177, 4): ✅ pass - precondition +test_regex_positive.py(177, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(178, 4): ✅ pass - fullmatch ^a reject -test_regex_positive.py(180, 4): ✅ pass - precondition +test_regex_positive.py(180, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(181, 4): ✅ pass - fullmatch a$ match -test_regex_positive.py(183, 4): ✅ pass - precondition +test_regex_positive.py(183, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(184, 4): ✅ pass - fullmatch a$ reject -test_regex_positive.py(186, 4): ✅ pass - precondition +test_regex_positive.py(186, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(187, 4): ✅ pass - fullmatch ^a$ match -test_regex_positive.py(189, 4): ✅ pass - precondition +test_regex_positive.py(189, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(190, 4): ✅ pass - fullmatch ^a$ reject trailing -test_regex_positive.py(192, 4): ✅ pass - precondition +test_regex_positive.py(192, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(193, 4): ✅ pass - fullmatch ^a$ reject leading -test_regex_positive.py(196, 4): ✅ pass - precondition +test_regex_positive.py(196, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(197, 4): ✅ pass - fullmatch ^$ on empty -test_regex_positive.py(199, 4): ✅ pass - precondition +test_regex_positive.py(199, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(200, 4): ✅ pass - fullmatch ^$ on non-empty -test_regex_positive.py(202, 4): ✅ pass - precondition +test_regex_positive.py(202, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(203, 4): ✅ pass - match ^$ on empty -test_regex_positive.py(205, 4): ✅ pass - precondition +test_regex_positive.py(205, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(206, 4): ✅ pass - match ^$ on non-empty -test_regex_positive.py(208, 4): ✅ pass - precondition +test_regex_positive.py(208, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(209, 4): ✅ pass - search ^$ on empty -test_regex_positive.py(211, 4): ✅ pass - precondition +test_regex_positive.py(211, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(212, 4): ✅ pass - search ^$ on non-empty -test_regex_positive.py(217, 4): ✅ pass - precondition +test_regex_positive.py(217, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(218, 4): ✅ pass - match ^a -test_regex_positive.py(220, 4): ✅ pass - precondition +test_regex_positive.py(220, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(221, 4): ✅ pass - match ^a trailing ok -test_regex_positive.py(223, 4): ✅ pass - precondition +test_regex_positive.py(223, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(224, 4): ✅ pass - match ^a reject -test_regex_positive.py(227, 4): ✅ pass - precondition +test_regex_positive.py(227, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(228, 4): ✅ pass - match ^a$ exact -test_regex_positive.py(230, 4): ✅ pass - precondition +test_regex_positive.py(230, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(231, 4): ✅ pass - match ^a$ reject trailing -test_regex_positive.py(233, 4): ✅ pass - precondition +test_regex_positive.py(233, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(234, 4): ✅ pass - match a$ exact -test_regex_positive.py(236, 4): ✅ pass - precondition +test_regex_positive.py(236, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(237, 4): ✅ pass - match a$ reject trailing -test_regex_positive.py(239, 4): ✅ pass - precondition +test_regex_positive.py(239, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(240, 4): ✅ pass - match a.*$ accepts -test_regex_positive.py(242, 4): ✅ pass - precondition +test_regex_positive.py(242, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(243, 4): ✅ pass - match a.*$ rejects -test_regex_positive.py(248, 4): ✅ pass - precondition +test_regex_positive.py(248, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(249, 4): ✅ pass - search a in middle -test_regex_positive.py(251, 4): ✅ pass - precondition +test_regex_positive.py(251, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(252, 4): ✅ pass - search a not found -test_regex_positive.py(255, 4): ✅ pass - precondition +test_regex_positive.py(255, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(256, 4): ✅ pass - search ^a at start -test_regex_positive.py(258, 4): ✅ pass - precondition +test_regex_positive.py(258, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(259, 4): ✅ pass - search ^a reject non-start -test_regex_positive.py(261, 4): ✅ pass - precondition +test_regex_positive.py(261, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(262, 4): ✅ pass - search ^a exact -test_regex_positive.py(265, 4): ✅ pass - precondition +test_regex_positive.py(265, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(266, 4): ✅ pass - search a$ at end -test_regex_positive.py(268, 4): ✅ pass - precondition +test_regex_positive.py(268, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(269, 4): ✅ pass - search a$ reject non-end -test_regex_positive.py(271, 4): ✅ pass - precondition +test_regex_positive.py(271, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(272, 4): ✅ pass - search a$ deep end -test_regex_positive.py(274, 4): ✅ pass - precondition +test_regex_positive.py(274, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(275, 4): ✅ pass - search a$ reject trailing -test_regex_positive.py(278, 4): ✅ pass - precondition +test_regex_positive.py(278, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(279, 4): ✅ pass - search ^a$ exact -test_regex_positive.py(281, 4): ✅ pass - precondition +test_regex_positive.py(281, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(282, 4): ✅ pass - search ^a$ reject prefix -test_regex_positive.py(284, 4): ✅ pass - precondition +test_regex_positive.py(284, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(285, 4): ✅ pass - search ^a$ reject suffix -test_regex_positive.py(289, 4): ✅ pass - precondition +test_regex_positive.py(289, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(290, 4): ✅ pass - search ^abc at start -test_regex_positive.py(292, 4): ✅ pass - precondition +test_regex_positive.py(292, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(293, 4): ✅ pass - search ^abc reject non-start -test_regex_positive.py(295, 4): ✅ pass - precondition +test_regex_positive.py(295, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(296, 4): ✅ pass - search abc$ at end -test_regex_positive.py(298, 4): ✅ pass - precondition +test_regex_positive.py(298, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(299, 4): ✅ pass - search abc$ reject non-end -test_regex_positive.py(301, 4): ✅ pass - precondition +test_regex_positive.py(301, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(302, 4): ✅ pass - search ^abc$ exact -test_regex_positive.py(304, 4): ✅ pass - precondition +test_regex_positive.py(304, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(305, 4): ✅ pass - search ^abc$ reject prefix -test_regex_positive.py(307, 4): ✅ pass - precondition +test_regex_positive.py(307, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(308, 4): ✅ pass - search ^abc$ reject suffix -test_regex_positive.py(312, 4): ✅ pass - precondition +test_regex_positive.py(312, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(313, 4): ✅ pass - fullmatch ^a{3}$ match -test_regex_positive.py(315, 4): ✅ pass - precondition +test_regex_positive.py(315, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(316, 4): ✅ pass - fullmatch ^a{3}$ too few -test_regex_positive.py(318, 4): ✅ pass - precondition +test_regex_positive.py(318, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(319, 4): ✅ pass - fullmatch ^a{3}$ too many -test_regex_positive.py(321, 4): ✅ pass - precondition +test_regex_positive.py(321, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(322, 4): ✅ pass - match ^a{3}$ exact -test_regex_positive.py(324, 4): ✅ pass - precondition +test_regex_positive.py(324, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(325, 4): ✅ pass - match ^a{3}$ reject trailing -test_regex_positive.py(327, 4): ✅ pass - precondition +test_regex_positive.py(327, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(328, 4): ✅ pass - match a{3} trailing ok -test_regex_positive.py(332, 4): ✅ pass - precondition +test_regex_positive.py(332, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(333, 4): ✅ pass - escaped dot matches literal -test_regex_positive.py(335, 4): ✅ pass - precondition +test_regex_positive.py(335, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(336, 4): ✅ pass - escaped dot rejects non-dot -test_regex_positive.py(338, 4): ✅ pass - precondition +test_regex_positive.py(338, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(339, 4): ✅ pass - escaped plus matches literal -test_regex_positive.py(341, 4): ✅ pass - precondition +test_regex_positive.py(341, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(342, 4): ✅ pass - escaped plus rejects -test_regex_positive.py(344, 4): ✅ pass - precondition +test_regex_positive.py(344, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(345, 4): ✅ pass - escaped star matches literal -test_regex_positive.py(347, 4): ✅ pass - precondition +test_regex_positive.py(347, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(348, 4): ✅ pass - escaped star rejects -test_regex_positive.py(350, 4): ✅ pass - precondition +test_regex_positive.py(350, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(351, 4): ✅ pass - escaped question matches literal -test_regex_positive.py(353, 4): ✅ pass - precondition +test_regex_positive.py(353, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(354, 4): ✅ pass - escaped question rejects -test_regex_positive.py(356, 4): ✅ pass - precondition +test_regex_positive.py(356, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(357, 4): ✅ pass - escaped parens match literal -test_regex_positive.py(359, 4): ✅ pass - precondition +test_regex_positive.py(359, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(360, 4): ✅ pass - escaped parens reject -test_regex_positive.py(362, 4): ✅ pass - precondition +test_regex_positive.py(362, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(363, 4): ✅ pass - escaped backslash matches literal -test_regex_positive.py(365, 4): ✅ pass - precondition +test_regex_positive.py(365, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(366, 4): ✅ pass - escaped backslash rejects -test_regex_positive.py(369, 4): ✅ pass - precondition +test_regex_positive.py(369, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(370, 4): ✅ pass - search escaped dot -test_regex_positive.py(372, 4): ✅ pass - precondition +test_regex_positive.py(372, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(373, 4): ✅ pass - search escaped backslash -test_regex_positive.py(375, 4): ✅ pass - precondition +test_regex_positive.py(375, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(376, 4): ✅ pass - search escaped backslash reject -test_regex_positive.py(380, 4): ✅ pass - precondition +test_regex_positive.py(380, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(381, 4): ✅ pass - colon literal match -test_regex_positive.py(383, 4): ✅ pass - precondition +test_regex_positive.py(383, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(384, 4): ✅ pass - colon literal reject -test_regex_positive.py(386, 4): ✅ pass - precondition +test_regex_positive.py(386, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(387, 4): ✅ pass - colon class match -test_regex_positive.py(389, 4): ✅ pass - precondition +test_regex_positive.py(389, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(390, 4): ✅ pass - colon class reject -test_regex_positive.py(392, 4): ✅ pass - precondition +test_regex_positive.py(392, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(393, 4): ✅ pass - search colon class -test_regex_positive.py(395, 4): ✅ pass - precondition +test_regex_positive.py(395, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(396, 4): ✅ pass - match anchored colon -test_regex_positive.py(398, 4): ✅ pass - precondition +test_regex_positive.py(398, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(399, 4): ✅ pass - match anchored colon reject trailing -test_regex_positive.py(403, 4): ✅ pass - precondition +test_regex_positive.py(403, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(404, 4): ✅ pass - wildcard empty middle -test_regex_positive.py(406, 4): ✅ pass - precondition +test_regex_positive.py(406, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(407, 4): ✅ pass - wildcard non-empty middle -test_regex_positive.py(409, 4): ✅ pass - precondition +test_regex_positive.py(409, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(410, 4): ✅ pass - wildcard wrong ending -test_regex_positive.py(412, 4): ✅ pass - precondition +test_regex_positive.py(412, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(413, 4): ✅ pass - search wildcard -test_regex_positive.py(416, 4): ✅ pass - precondition +test_regex_positive.py(416, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(417, 4): ✅ pass - multi-char alt first -test_regex_positive.py(419, 4): ✅ pass - precondition +test_regex_positive.py(419, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(420, 4): ✅ pass - multi-char alt second -test_regex_positive.py(422, 4): ✅ pass - precondition +test_regex_positive.py(422, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(423, 4): ✅ pass - multi-char alt reject concat -test_regex_positive.py(425, 4): ✅ pass - precondition +test_regex_positive.py(425, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(426, 4): ✅ pass - search multi-char alt -test_regex_positive.py(430, 4): ✅ pass - precondition +test_regex_positive.py(430, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(431, 4): ✅ pass - fullmatch ^a|b$ first branch -test_regex_positive.py(433, 4): ✅ pass - precondition +test_regex_positive.py(433, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(434, 4): ✅ pass - fullmatch ^a|b$ second branch -test_regex_positive.py(436, 4): ✅ pass - precondition +test_regex_positive.py(436, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(437, 4): ✅ pass - fullmatch ^a|b$ reject -test_regex_positive.py(439, 4): ✅ pass - precondition +test_regex_positive.py(439, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(440, 4): ✅ pass - search ^a|b$ start anchor -test_regex_positive.py(442, 4): ✅ pass - precondition +test_regex_positive.py(442, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(443, 4): ✅ pass - search ^a|b$ end anchor -test_regex_positive.py(445, 4): ✅ pass - precondition +test_regex_positive.py(445, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(446, 4): ✅ pass - search ^a|b$ neither -test_regex_positive.py(450, 4): ✅ pass - precondition -test_regex_positive.py(452, 4): ✅ pass - precondition -test_regex_positive.py(453, 4): ❓ unknown - compiled ^abc$ fullmatch -test_regex_positive.py(455, 4): ✅ pass - precondition -test_regex_positive.py(456, 4): ❓ unknown - compiled ^abc$ search exact -test_regex_positive.py(458, 4): ✅ pass - precondition -test_regex_positive.py(459, 4): ❓ unknown - compiled ^abc$ search reject prefix -test_regex_positive.py(461, 4): ✅ pass - precondition -test_regex_positive.py(462, 4): ❓ unknown - compiled ^abc$ match exact -test_regex_positive.py(464, 4): ✅ pass - precondition -test_regex_positive.py(465, 4): ❓ unknown - compiled ^abc$ match reject trailing -test_regex_positive.py(472, 4): ✅ pass - precondition +test_regex_positive.py(450, 4): ✅ pass - set_p_calls_re_compile_0 +test_regex_positive.py(452, 4): ✅ pass - set_m_calls_re_fullmatch_0 +test_regex_positive.py(453, 4): ✅ pass - compiled ^abc$ fullmatch +test_regex_positive.py(455, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(456, 4): ✅ pass - compiled ^abc$ search exact +test_regex_positive.py(458, 4): ✅ pass - set_m_calls_re_search_0 +test_regex_positive.py(459, 4): ✅ pass - compiled ^abc$ search reject prefix +test_regex_positive.py(461, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(462, 4): ✅ pass - compiled ^abc$ match exact +test_regex_positive.py(464, 4): ✅ pass - set_m_calls_re_match_0 +test_regex_positive.py(465, 4): ✅ pass - compiled ^abc$ match reject trailing +test_regex_positive.py(472, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(473, 4): ✅ pass - malformed: unmatched paren is exception, not None -test_regex_positive.py(475, 4): ✅ pass - precondition +test_regex_positive.py(475, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(476, 4): ✅ pass - malformed: nothing to repeat is exception, not None -test_regex_positive.py(478, 4): ✅ pass - precondition +test_regex_positive.py(478, 4): ✅ pass - set_m_calls_re_fullmatch_0 test_regex_positive.py(479, 4): ✅ pass - malformed: bad bounds is exception, not None -test_regex_positive.py(481, 4): ✅ pass - precondition +test_regex_positive.py(481, 4): ✅ pass - set_m_calls_re_search_0 test_regex_positive.py(482, 4): ✅ pass - malformed: search with bad pattern is exception, not None -test_regex_positive.py(484, 4): ✅ pass - precondition +test_regex_positive.py(484, 4): ✅ pass - set_m_calls_re_match_0 test_regex_positive.py(485, 4): ✅ pass - malformed: match with bad pattern is exception, not None -DETAIL: 273 passed, 0 failed, 9 inconclusive -RESULT: Inconclusive +DETAIL: 282 passed, 0 failed, 0 inconclusive +RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_return_types.expected b/StrataTest/Languages/Python/expected_laurel/test_return_types.expected index 95def4a888..ba027981f3 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_return_types.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_return_types.expected @@ -1,14 +1,20 @@ +test_return_types.py(1, 20): ✅ pass - (get_number ensures) Return type constraint +test_return_types.py(4, 22): ✅ pass - (get_greeting ensures) Return type constraint +test_return_types.py(7, 18): ✅ pass - (get_flag ensures) Return type constraint test_return_types.py(11, 4): ✅ pass - assert(159) +test_return_types.py(10, 21): ✅ pass - (get_nothing ensures) Return type constraint test_return_types.py(15, 18): ✅ pass - Check PAdd exception test_return_types.py(15, 4): ✅ pass - assert(223) +test_return_types.py(14, 27): ✅ pass - (add ensures) Return type constraint test_return_types.py(19, 4): ✅ pass - assert(278) test_return_types.py(20, 4): ❓ unknown - get_number returned wrong value test_return_types.py(22, 4): ✅ pass - assert(359) test_return_types.py(23, 4): ❓ unknown - get_greeting returned wrong value test_return_types.py(25, 4): ✅ pass - assert(449) test_return_types.py(26, 4): ❓ unknown - get_flag returned wrong value -test_return_types.py(28, 4): ✅ pass - (add requires) Type constraint of a, (add requires) Type constraint of b +test_return_types.py(28, 4): ✅ pass - (add requires) Type constraint of a +test_return_types.py(28, 4): ✅ pass - (add requires) Type constraint of b test_return_types.py(28, 4): ✅ pass - assert(529) test_return_types.py(29, 4): ❓ unknown - add returned wrong value -DETAIL: 8 passed, 0 failed, 4 inconclusive +DETAIL: 14 passed, 0 failed, 4 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_timedelta_expr.expected b/StrataTest/Languages/Python/expected_laurel/test_timedelta_expr.expected index 396109c554..270b1ae3c0 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_timedelta_expr.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_timedelta_expr.expected @@ -1,6 +1,9 @@ -test_timedelta_expr.py(4, 0): ✅ pass - (Origin_timedelta_Requires), (Origin_timedelta_Requires)hours_type, (Origin_timedelta_Requires)days_pos, (Origin_timedelta_Requires)hours_pos +test_timedelta_expr.py(4, 0): ✅ pass - (Origin_timedelta_Requires) +test_timedelta_expr.py(4, 0): ✅ pass - (Origin_timedelta_Requires)hours_type +test_timedelta_expr.py(4, 0): ✅ pass - (Origin_timedelta_Requires)days_pos +test_timedelta_expr.py(4, 0): ✅ pass - (Origin_timedelta_Requires)hours_pos test_timedelta_expr.py(5, 18): ✅ pass - Check PSub exception test_timedelta_expr.py(6, 7): ✅ pass - Check PLe exception test_timedelta_expr.py(6, 0): ✅ pass - assert(140) -DETAIL: 4 passed, 0 failed, 0 inconclusive +DETAIL: 7 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_try_except_modeled.expected b/StrataTest/Languages/Python/expected_laurel/test_try_except_modeled.expected index 6361a61064..b7eb16a95b 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_try_except_modeled.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_try_except_modeled.expected @@ -1,13 +1,16 @@ test_try_except_modeled.py(8, 4): ✅ pass - assert(337) -test_try_except_modeled.py(10, 8): ✅ pass - precondition +test_try_except_modeled.py(10, 8): ✅ pass - set_result_calls_Any_get_0 test_try_except_modeled.py(13, 4): ✅ pass - dict access should succeed +test_try_except_modeled.py(6, 30): ✅ pass - (test_try_dict_access ensures) Return type constraint test_try_except_modeled.py(17, 4): ✅ pass - assert(541) test_try_except_modeled.py(18, 4): ✅ pass - assert(557) test_try_except_modeled.py(19, 4): ✅ pass - assert(572) test_try_except_modeled.py(21, 17): ✅ pass - Check PAdd exception test_try_except_modeled.py(24, 4): ✅ pass - addition should succeed +test_try_except_modeled.py(16, 29): ✅ pass - (test_try_arithmetic ensures) Return type constraint test_try_except_modeled.py(32, 4): ✅ pass - assert(950) -test_try_except_modeled.py(35, 12): ✅ pass - precondition +test_try_except_modeled.py(35, 12): ✅ pass - set_result_calls_Any_get_0 test_try_except_modeled.py(38, 4): ✅ pass - nested dict access should succeed -DETAIL: 11 passed, 0 failed, 0 inconclusive +test_try_except_modeled.py(30, 37): ✅ pass - (test_try_nested_dict_access ensures) Return type constraint +DETAIL: 14 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_try_except_nested.expected b/StrataTest/Languages/Python/expected_laurel/test_try_except_nested.expected index 500af0a390..bc5270d557 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_try_except_nested.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_try_except_nested.expected @@ -1,5 +1,7 @@ +test_try_except_nested.py(5, 26): ✅ pass - (might_fail ensures) Return type constraint test_try_except_nested.py(9, 4): ✅ pass - assert(256) test_try_except_nested.py(12, 12): ✅ pass - (might_fail requires) Type constraint of x test_try_except_nested.py(15, 4): ❓ unknown - should succeed -DETAIL: 2 passed, 0 failed, 1 inconclusive +test_try_except_nested.py(8, 28): ✅ pass - (test_nested_except ensures) Return type constraint +DETAIL: 4 passed, 0 failed, 1 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_tuple_create.expected b/StrataTest/Languages/Python/expected_laurel/test_tuple_create.expected index e6e7836d25..eb841a55ce 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_tuple_create.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_tuple_create.expected @@ -1,6 +1,6 @@ -test_tuple_create.py(3, 11): ✅ pass - precondition +test_tuple_create.py(3, 4): ✅ pass - assert_assert(47)_calls_Any_get_0 test_tuple_create.py(3, 4): ✅ pass - tuple first -test_tuple_create.py(4, 11): ✅ pass - precondition +test_tuple_create.py(4, 4): ✅ pass - assert_assert(83)_calls_Any_get_0 test_tuple_create.py(4, 4): ✅ pass - tuple last DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_tuple_swap.expected b/StrataTest/Languages/Python/expected_laurel/test_tuple_swap.expected index 10317bc474..bce7234e3f 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_tuple_swap.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_tuple_swap.expected @@ -1,7 +1,8 @@ test_tuple_swap.py(2, 4): ✅ pass - assert(27) test_tuple_swap.py(3, 4): ✅ pass - assert(42) -test_tuple_swap.py(4, 4): ✅ pass - precondition +test_tuple_swap.py(4, 4): ✅ pass - set_a_calls_Any_get_0 +test_tuple_swap.py(4, 4): ✅ pass - set_b_calls_Any_get_0 test_tuple_swap.py(5, 11): ✅ pass - Check PAnd exception test_tuple_swap.py(5, 4): ✅ pass - tuple swap -DETAIL: 5 passed, 0 failed, 0 inconclusive +DETAIL: 6 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_tuple_type.expected b/StrataTest/Languages/Python/expected_laurel/test_tuple_type.expected index 1b3a39bfa0..211a0b9df0 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_tuple_type.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_tuple_type.expected @@ -1,4 +1,4 @@ -test_tuple_type.py(5, 11): ✅ pass - precondition +test_tuple_type.py(5, 4): ✅ pass - assert_assert(80)_calls_Any_get_0 test_tuple_type.py(5, 4): ✅ pass - typed tuple DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_tuple_unpack.expected b/StrataTest/Languages/Python/expected_laurel/test_tuple_unpack.expected index 5a47e1ae13..2e58c8bb13 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_tuple_unpack.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_tuple_unpack.expected @@ -1,5 +1,6 @@ -test_tuple_unpack.py(3, 4): ✅ pass - precondition +test_tuple_unpack.py(3, 4): ✅ pass - set_a_calls_Any_get_0 +test_tuple_unpack.py(3, 4): ✅ pass - set_b_calls_Any_get_0 test_tuple_unpack.py(4, 4): ✅ pass - unpack first test_tuple_unpack.py(5, 4): ✅ pass - unpack second -DETAIL: 3 passed, 0 failed, 0 inconclusive +DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_type_dict_annotation.expected b/StrataTest/Languages/Python/expected_laurel/test_type_dict_annotation.expected index 38622ce174..2b25064dde 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_type_dict_annotation.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_type_dict_annotation.expected @@ -1,4 +1,4 @@ -test_type_dict_annotation.py(5, 11): ✅ pass - precondition +test_type_dict_annotation.py(5, 4): ✅ pass - assert_assert(95)_calls_Any_get_0 test_type_dict_annotation.py(5, 4): ✅ pass - typed dict DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_type_list_annotation.expected b/StrataTest/Languages/Python/expected_laurel/test_type_list_annotation.expected index 6680893b7f..db7eb8de67 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_type_list_annotation.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_type_list_annotation.expected @@ -1,4 +1,4 @@ -test_type_list_annotation.py(5, 11): ✅ pass - precondition +test_type_list_annotation.py(5, 4): ✅ pass - assert_assert(92)_calls_Any_get_0 test_type_list_annotation.py(5, 4): ✅ pass - typed list DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_var_shadow_func.expected b/StrataTest/Languages/Python/expected_laurel/test_var_shadow_func.expected index 601f7f9897..7aee788a67 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_var_shadow_func.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_var_shadow_func.expected @@ -1,8 +1,9 @@ test_var_shadow_func.py(2, 8): ✅ pass - Check PAdd exception +test_var_shadow_func.py(1, 17): ✅ pass - (f ensures) Return type constraint test_var_shadow_func.py(6, 4): ✅ pass - assert(83) test_var_shadow_func.py(7, 4): ✅ pass - (f requires) Type constraint of x test_var_shadow_func.py(7, 4): ✅ pass - assert(98) test_var_shadow_func.py(8, 4): ❓ unknown - param modified inside test_var_shadow_func.py(9, 4): ✅ pass - original unchanged -DETAIL: 5 passed, 0 failed, 1 inconclusive +DETAIL: 6 passed, 0 failed, 1 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_variable_reassign.expected b/StrataTest/Languages/Python/expected_laurel/test_variable_reassign.expected index 1ac67414a3..afbbef3e87 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_variable_reassign.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_variable_reassign.expected @@ -8,10 +8,10 @@ test_variable_reassign.py(12, 4): ✅ pass - assert(242) test_variable_reassign.py(13, 10): ✅ pass - Check PLt exception test_variable_reassign.py(14, 16): ❓ unknown - Check PAdd exception test_variable_reassign.py(15, 12): ❓ unknown - Check PAdd exception -test_variable_reassign.py(16, 4): ✅ pass - loop sum should be 10 +test_variable_reassign.py(16, 4): ❓ unknown - loop sum should be 10 test_variable_reassign.py(19, 4): ✅ pass - assert(398) test_variable_reassign.py(20, 4): ✅ pass - assert(415) test_variable_reassign.py(25, 4): ✅ pass - should be 100 test_variable_reassign.py(32, 4): ✅ pass - should be 200 -DETAIL: 13 passed, 0 failed, 2 inconclusive +DETAIL: 12 passed, 0 failed, 3 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_while_loop.expected b/StrataTest/Languages/Python/expected_laurel/test_while_loop.expected index 6905eca1fb..18aceeb417 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_while_loop.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_while_loop.expected @@ -3,15 +3,18 @@ test_while_loop.py(3, 4): ✅ pass - assert(54) test_while_loop.py(4, 10): ✅ pass - Check PGt exception test_while_loop.py(5, 16): ❓ unknown - Check PAdd exception test_while_loop.py(6, 12): ❓ unknown - Check PSub exception -test_while_loop.py(7, 4): ✅ pass - countdown sum should be 15 +test_while_loop.py(7, 4): ❓ unknown - countdown sum should be 15 +test_while_loop.py(1, 30): ❓ unknown - (test_while_countdown ensures) Return type constraint test_while_loop.py(11, 4): ✅ pass - assert(241) test_while_loop.py(13, 16): ❓ unknown - Check PAdd exception test_while_loop.py(16, 4): ✅ pass - should have counted to 10 +test_while_loop.py(10, 31): ❓ unknown (pass on 1 path, unknown on 1 path) - (test_while_true_break ensures) Return type constraint test_while_loop.py(20, 4): ✅ pass - assert(453) test_while_loop.py(21, 4): ✅ pass - assert(468) test_while_loop.py(22, 10): ✅ pass - Check PLt exception test_while_loop.py(23, 12): ❓ unknown - Check PAdd exception -test_while_loop.py(27, 4): ✅ pass - sum excluding 5 should be 50 +test_while_loop.py(27, 4): ❓ unknown - sum excluding 5 should be 50 +test_while_loop.py(19, 34): ❓ unknown - (test_while_with_continue ensures) Return type constraint test_while_loop.py(26, 16): ❓ unknown - Check PAdd exception -DETAIL: 10 passed, 0 failed, 5 inconclusive +DETAIL: 8 passed, 0 failed, 10 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/run_py_analyze_sarif.py b/StrataTest/Languages/Python/run_py_analyze_sarif.py index 0b63cb9bbb..1e12630061 100755 --- a/StrataTest/Languages/Python/run_py_analyze_sarif.py +++ b/StrataTest/Languages/Python/run_py_analyze_sarif.py @@ -67,11 +67,7 @@ "test_with_statement", "test_fstrings", } -SKIP_TESTS_LAUREL = BOTH_SKIP | { - "test_try_except", # TVoid type from raise statements not supported in function copies - "test_multiple_except", # TVoid type from raise statements not supported in function copies - "test_datetime_now_tz", # Resolution failure: timezone/utc not defined -} +SKIP_TESTS_LAUREL = BOTH_SKIP def run(test_file: str, *, laurel: bool) -> bool: diff --git a/StrataTest/Languages/Python/tests/cbmc_expected.txt b/StrataTest/Languages/Python/tests/cbmc_expected.txt index b078d4aaff..0dc6283317 100644 --- a/StrataTest/Languages/Python/tests/cbmc_expected.txt +++ b/StrataTest/Languages/Python/tests/cbmc_expected.txt @@ -33,7 +33,6 @@ test_if_elif.py.ion SKIP test_variable_reassign.py.ion SKIP test_datetime_now_tz.py.ion SKIP test_timedelta_expr.py.ion SKIP -test_composite_return.py.ion PASS test_multi_assign.py.ion FAIL test_multi_assign_triple.py.ion FAIL test_multi_assign_side_effect.py.ion SKIP From 60c9ca39c85f26b8e7c3e98c1596499d9b6b49a5 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 13:31:53 +0000 Subject: [PATCH 031/115] Fixes --- Strata/Languages/Laurel/LaurelCompilationPipeline.lean | 2 -- .../Laurel/Examples/Objects/WIP/9. Closures.lr.st | 9 --------- 2 files changed, 11 deletions(-) diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index 1a77b46949..e5ba84b9ea 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -11,10 +11,8 @@ import Strata.Languages.Laurel.DesugarShortCircuit import Strata.Languages.Laurel.EliminateReturnsInExpression import Strata.Languages.Laurel.EliminateReturnStatements import Strata.Languages.Laurel.EliminateValuesInReturns -import Strata.Languages.Laurel.InlineLocalVariablesInExpressions import Strata.Languages.Laurel.ConstrainedTypeElim import Strata.Languages.Laurel.ContractPass -import Strata.Languages.Laurel.EliminateMultipleOutputs import Strata.Languages.Laurel.TypeAliasElim import Strata.Languages.Core.Verifier import Strata.Util.Statistics diff --git a/StrataTest/Languages/Laurel/Examples/Objects/WIP/9. Closures.lr.st b/StrataTest/Languages/Laurel/Examples/Objects/WIP/9. Closures.lr.st index c5e7f94c87..3449eb9246 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/WIP/9. Closures.lr.st +++ b/StrataTest/Languages/Laurel/Examples/Objects/WIP/9. Closures.lr.st @@ -102,10 +102,6 @@ procedure hasClosure() returns (r: int) // Option B: type closures composite ATrait { procedure foo() returns (r: int) ensures r > 0 -<<<<<<< HEAD -======= - opaque ->>>>>>> 5e61ec87a (Add contract pass) { abstract }; @@ -117,12 +113,7 @@ procedure hasClosure() returns (r: int) { var x = 3; var aClosure := closure extends ATrait { -<<<<<<< HEAD procedure foo() returns (r: int) -======= - procedure foo() returns (r: int) - opaque ->>>>>>> 5e61ec87a (Add contract pass) { r = x + 4; }; From a526dad952a57b2ad85750aec849b18b8eefb14d Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 13:43:23 +0000 Subject: [PATCH 032/115] Add missing bindings for PrimitiveOp --- Strata/Languages/Laurel/DesugarShortCircuit.lean | 2 +- Strata/Languages/Laurel/HeapParameterization.lean | 4 ++-- Strata/Languages/Laurel/InferHoleTypes.lean | 2 +- Strata/Languages/Laurel/LaurelToCoreTranslator.lean | 2 +- Strata/Languages/Laurel/LiftImperativeExpressions.lean | 10 +++++----- Strata/Languages/Laurel/TypeHierarchy.lean | 2 +- Strata/Languages/Python/PythonToLaurel.lean | 6 +++--- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Strata/Languages/Laurel/DesugarShortCircuit.lean b/Strata/Languages/Laurel/DesugarShortCircuit.lean index c5703f5910..8c0f602bfa 100644 --- a/Strata/Languages/Laurel/DesugarShortCircuit.lean +++ b/Strata/Languages/Laurel/DesugarShortCircuit.lean @@ -30,7 +30,7 @@ private def desugarShortCircuitNode (imperativeCallees : List String) (expr : St let source := expr.source let wrap (v : StmtExpr) : StmtExprMd := ⟨v, source⟩ match expr.val with - | .PrimitiveOp op args => + | .PrimitiveOp op args _ => match op, args with -- With bottom-up traversal, `a` and `b` are already desugared (nested -- short-circuits converted to IfThenElse). The check still works because diff --git a/Strata/Languages/Laurel/HeapParameterization.lean b/Strata/Languages/Laurel/HeapParameterization.lean index 4c83d2c9f8..e467944ef6 100644 --- a/Strata/Languages/Laurel/HeapParameterization.lean +++ b/Strata/Languages/Laurel/HeapParameterization.lean @@ -76,7 +76,7 @@ def collectExpr (expr : StmtExpr) : StateM AnalysisResult Unit := do | .Local _ | .Declare _ => pure () collectExprMd v | .PureFieldUpdate t _ v => collectExprMd t; collectExprMd v - | .PrimitiveOp _ args => for a in args do collectExprMd a + | .PrimitiveOp _ args _ => for a in args do collectExprMd a | .New _ => modify fun s => { s with writesHeapDirectly := true } | .ReferenceEquals l r => collectExprMd l; collectExprMd r | .AsType t _ => collectExprMd t @@ -399,7 +399,7 @@ where return newAssign :: suffixes | .PureFieldUpdate t f v => return [⟨ .PureFieldUpdate (← recurseOne t) f (← recurseOne v), source ⟩] - | .PrimitiveOp op args => + | .PrimitiveOp op args _ => let args' ← args.mapM (recurseOne ·) -- For == and != on Composite types, compare refs instead match op, args with diff --git a/Strata/Languages/Laurel/InferHoleTypes.lean b/Strata/Languages/Laurel/InferHoleTypes.lean index 8f0ae491b3..efb5550b1c 100644 --- a/Strata/Languages/Laurel/InferHoleTypes.lean +++ b/Strata/Languages/Laurel/InferHoleTypes.lean @@ -107,7 +107,7 @@ private def inferExpr (expr : StmtExprMd) (expectedType : HighTypeMd) : InferHol else modify fun s => { s with statistics := s.statistics.increment s!"{InferHoleTypesStats.holesAnnotated}" } return ⟨.Hole det (some expectedType), source⟩ - | .PrimitiveOp op args => + | .PrimitiveOp op args _ => let argType := match op with | .Eq | .Neq | .Lt | .Leq | .Gt | .Geq => inferComparisonArgType model args source | _ => diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index eca7c8b099..fcccd001b5 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -177,7 +177,7 @@ def translateExpr (expr : StmtExprMd) return .fvar () ⟨name.text, ()⟩ (some (← translateType astNode.getType)) | .Var (.Declare _) => throwExprDiagnostic $ md.toDiagnostic "variable declaration in expression context should have been lowered" DiagnosticType.StrataBug - | .PrimitiveOp op [e] => + | .PrimitiveOp op [e] _ => match op with | .Not => let re ← translateExpr e boundVars isPureContext diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index 7720d46342..ad315921a2 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -154,7 +154,7 @@ def containsAssignmentOrImperativeCall (imperativeCallees : List String) (expr : | .StaticCall name args1 => imperativeCallees.contains name.text || args1.attach.any (fun x => containsAssignmentOrImperativeCall imperativeCallees x.val) - | .PrimitiveOp _ args2 => args2.attach.any (fun x => containsAssignmentOrImperativeCall imperativeCallees x.val) + | .PrimitiveOp _ args2 _ => args2.attach.any (fun x => containsAssignmentOrImperativeCall imperativeCallees x.val) | .Block stmts _ => stmts.attach.any (fun x => containsAssignmentOrImperativeCall imperativeCallees x.val) | .IfThenElse cond th el => containsAssignmentOrImperativeCall imperativeCallees cond || @@ -201,7 +201,7 @@ def containsBareAssignment (expr : StmtExprMd) : Bool := match val with | .Assign .. => true | .StaticCall _ args => args.attach.any (fun x => containsBareAssignment x.val) - | .PrimitiveOp _ args => args.attach.any (fun x => containsBareAssignment x.val) + | .PrimitiveOp _ args _ => args.attach.any (fun x => containsBareAssignment x.val) | .Block _ _ => false | .IfThenElse cond th el => containsBareAssignment cond || containsBareAssignment th || @@ -221,7 +221,7 @@ def containsImperativeCall (model : SemanticModel) (expr : StmtExprMd) : Bool := | .staticProcedure proc => !proc.isFunctional | _ => false) || args.attach.any (fun x => containsImperativeCall model x.val) - | .PrimitiveOp _ args => args.attach.any (fun x => containsImperativeCall model x.val) + | .PrimitiveOp _ args _ => args.attach.any (fun x => containsImperativeCall model x.val) | .Block stmts _ => stmts.attach.any (fun x => containsImperativeCall model x.val) | .IfThenElse cond th el => containsImperativeCall model cond || @@ -298,7 +298,7 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do return resultExpr - | .PrimitiveOp op args => + | .PrimitiveOp op args _ => -- Process arguments right to left let seqArgs ← args.reverse.mapM transformExpr return ⟨.PrimitiveOp op seqArgs.reverse, source⟩ @@ -614,7 +614,7 @@ def transformStmt (stmt : StmtExprMd) : LiftM (List StmtExprMd) := do modify fun s => { s with subst := [] } return prepends ++ [⟨.Return (some seqRet), source⟩] - | .PrimitiveOp name args => + | .PrimitiveOp name args _ => let seqArgs ← args.mapM transformExpr let prepends ← takePrepends return prepends ++ [⟨.PrimitiveOp name seqArgs, source⟩] diff --git a/Strata/Languages/Laurel/TypeHierarchy.lean b/Strata/Languages/Laurel/TypeHierarchy.lean index 26b72ff23f..9afbe99f69 100644 --- a/Strata/Languages/Laurel/TypeHierarchy.lean +++ b/Strata/Languages/Laurel/TypeHierarchy.lean @@ -168,7 +168,7 @@ def validateDiamondFieldAccessesForStmtExpr (model : SemanticModel) invs.attach.foldl (fun acc ⟨inv, _⟩ => acc ++ validateDiamondFieldAccessesForStmtExpr model inv) errs | .Assert cond => validateDiamondFieldAccessesForStmtExpr model cond.condition | .Assume cond => validateDiamondFieldAccessesForStmtExpr model cond - | .PrimitiveOp _ args => + | .PrimitiveOp _ args _ => args.attach.foldl (fun acc ⟨a, _⟩ => acc ++ validateDiamondFieldAccessesForStmtExpr model a) [] | .StaticCall _ args => args.attach.foldl (fun acc ⟨a, _⟩ => acc ++ validateDiamondFieldAccessesForStmtExpr model a) [] diff --git a/Strata/Languages/Python/PythonToLaurel.lean b/Strata/Languages/Python/PythonToLaurel.lean index e272c562fd..081dd2b5e3 100644 --- a/Strata/Languages/Python/PythonToLaurel.lean +++ b/Strata/Languages/Python/PythonToLaurel.lean @@ -1358,7 +1358,7 @@ def extractMultiOutputCalls (ctx : TranslationContext) (e : StmtExprMd) return ([], e) else return (preamble, mkStmtExprMdWithLoc (.StaticCall callee.text newArgs) e.source) - | .PrimitiveOp op args => + | .PrimitiveOp op args _ => let results ← args.attach.mapM fun ⟨arg, _⟩ => extractMultiOutputCalls ctx arg let preamble := (results.map (fun (pre, _) => pre)).flatten let newArgs := results.map (·.2) @@ -1649,7 +1649,7 @@ partial def getMaybeExceptionExprs (ctx : TranslationContext) (e : StmtExprMd) : if isMaybeExceptAnyFunc ctx funcname.text then [e] else args.flatMap $ getMaybeExceptionExprs ctx - | .PrimitiveOp _ args => args.flatMap $ getMaybeExceptionExprs ctx + | .PrimitiveOp _ args _ => args.flatMap $ getMaybeExceptionExprs ctx | .IfThenElse cond thenBranch elseBranch => ([cond, thenBranch] ++ elseBranch.toList).flatMap $ getMaybeExceptionExprs ctx | _ => [] @@ -1675,7 +1675,7 @@ partial def containsUserCall (ctx : TranslationContext) (e : StmtExprMd) : Bool callee.text ∈ ctx.userFunctions || withException ctx callee.text || args.any (containsUserCall ctx) - | .PrimitiveOp _ args => args.any (containsUserCall ctx) + | .PrimitiveOp _ args _ => args.any (containsUserCall ctx) | .IfThenElse cond thenBranch elseBranch => containsUserCall ctx cond || containsUserCall ctx thenBranch || elseBranch.any (containsUserCall ctx) From ace39ca56d88120b644e3a7684a3439cf6452844 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 13:44:15 +0000 Subject: [PATCH 033/115] Improve comment --- Strata/Languages/Laurel/Laurel.lean | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Strata/Languages/Laurel/Laurel.lean b/Strata/Languages/Laurel/Laurel.lean index 4e96774dc2..629fe01040 100644 --- a/Strata/Languages/Laurel/Laurel.lean +++ b/Strata/Languages/Laurel/Laurel.lean @@ -297,7 +297,8 @@ inductive StmtExpr : Type where /-- Call a static procedure by name with the given arguments. -/ | StaticCall (callee : Identifier) (arguments : List (AstNode StmtExpr)) /-- Apply a primitive operation to the given arguments. - The skipProof property is used internally. -/ + The skipProof property is used internally. + It means that any precondition of the operator, such as division has, should be ignored. -/ | PrimitiveOp (operator : Operation) (arguments : List (AstNode StmtExpr)) (skipProof: Bool := false) /-- Create new object (`new`). -/ From 2e94fc53632f3b00dfa0be6fe93afbe53effdb6f Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 13:43:23 +0000 Subject: [PATCH 034/115] Add missing bindings for PrimitiveOp --- Strata/Languages/Laurel/DesugarShortCircuit.lean | 2 +- Strata/Languages/Laurel/HeapParameterization.lean | 4 ++-- Strata/Languages/Laurel/InferHoleTypes.lean | 2 +- Strata/Languages/Laurel/LaurelToCoreTranslator.lean | 2 +- Strata/Languages/Laurel/LiftImperativeExpressions.lean | 8 ++++---- Strata/Languages/Laurel/TypeHierarchy.lean | 2 +- Strata/Languages/Python/PythonToLaurel.lean | 6 +++--- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Strata/Languages/Laurel/DesugarShortCircuit.lean b/Strata/Languages/Laurel/DesugarShortCircuit.lean index ef5982430e..aa870c57e9 100644 --- a/Strata/Languages/Laurel/DesugarShortCircuit.lean +++ b/Strata/Languages/Laurel/DesugarShortCircuit.lean @@ -30,7 +30,7 @@ private def bare (v : StmtExpr) : StmtExprMd := ⟨v, none⟩ private def desugarShortCircuitNode (model : SemanticModel) (expr : StmtExprMd) : StmtExprMd := let source := expr.source match expr.val with - | .PrimitiveOp op args => + | .PrimitiveOp op args _ => match op, args with -- With bottom-up traversal, `a` and `b` are already desugared (nested -- short-circuits converted to IfThenElse). The check still works because diff --git a/Strata/Languages/Laurel/HeapParameterization.lean b/Strata/Languages/Laurel/HeapParameterization.lean index 0243113aed..aacff2d37a 100644 --- a/Strata/Languages/Laurel/HeapParameterization.lean +++ b/Strata/Languages/Laurel/HeapParameterization.lean @@ -76,7 +76,7 @@ def collectExpr (expr : StmtExpr) : StateM AnalysisResult Unit := do | .Local _ | .Declare _ => pure () collectExprMd v | .PureFieldUpdate t _ v => collectExprMd t; collectExprMd v - | .PrimitiveOp _ args => for a in args do collectExprMd a + | .PrimitiveOp _ args _ => for a in args do collectExprMd a | .New _ => modify fun s => { s with writesHeapDirectly := true } | .ReferenceEquals l r => collectExprMd l; collectExprMd r | .AsType t _ => collectExprMd t @@ -399,7 +399,7 @@ where return newAssign :: suffixes | .PureFieldUpdate t f v => return [⟨ .PureFieldUpdate (← recurseOne t) f (← recurseOne v), source ⟩] - | .PrimitiveOp op args => + | .PrimitiveOp op args _ => let args' ← args.mapM (recurseOne ·) -- For == and != on Composite types, compare refs instead match op, args with diff --git a/Strata/Languages/Laurel/InferHoleTypes.lean b/Strata/Languages/Laurel/InferHoleTypes.lean index 8f0ae491b3..efb5550b1c 100644 --- a/Strata/Languages/Laurel/InferHoleTypes.lean +++ b/Strata/Languages/Laurel/InferHoleTypes.lean @@ -107,7 +107,7 @@ private def inferExpr (expr : StmtExprMd) (expectedType : HighTypeMd) : InferHol else modify fun s => { s with statistics := s.statistics.increment s!"{InferHoleTypesStats.holesAnnotated}" } return ⟨.Hole det (some expectedType), source⟩ - | .PrimitiveOp op args => + | .PrimitiveOp op args _ => let argType := match op with | .Eq | .Neq | .Lt | .Leq | .Gt | .Geq => inferComparisonArgType model args source | _ => diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index 4209435b01..94fb99fbc8 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -180,7 +180,7 @@ def translateExpr (expr : StmtExprMd) return .fvar () ⟨name.text, ()⟩ (some (← translateType astNode.getType)) | .Var (.Declare _) => throwExprDiagnostic $ md.toDiagnostic "variable declaration in expression context should have been lowered" DiagnosticType.StrataBug - | .PrimitiveOp op [e] => + | .PrimitiveOp op [e] _ => match op with | .Not => let re ← translateExpr e boundVars isPureContext diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index 4a033cfc2d..df135ad155 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -158,7 +158,7 @@ def containsAssignment (expr : StmtExprMd) : Bool := match val with | .Assign .. => true | .StaticCall _ args => args.attach.any (fun x => containsAssignment x.val) - | .PrimitiveOp _ args => args.attach.any (fun x => containsAssignment x.val) + | .PrimitiveOp _ args _ => args.attach.any (fun x => containsAssignment x.val) | .Block stmts _ => stmts.attach.any (fun x => containsAssignment x.val) | .IfThenElse cond th el => containsAssignment cond || containsAssignment th || @@ -176,7 +176,7 @@ def containsBareAssignment (expr : StmtExprMd) : Bool := match val with | .Assign .. => true | .StaticCall _ args => args.attach.any (fun x => containsBareAssignment x.val) - | .PrimitiveOp _ args => args.attach.any (fun x => containsBareAssignment x.val) + | .PrimitiveOp _ args _ => args.attach.any (fun x => containsBareAssignment x.val) | .Block _ _ => false | .IfThenElse cond th el => containsBareAssignment cond || containsBareAssignment th || @@ -196,7 +196,7 @@ def containsImperativeCall (model : SemanticModel) (expr : StmtExprMd) : Bool := | .staticProcedure proc => !proc.isFunctional | _ => false) || args.attach.any (fun x => containsImperativeCall model x.val) - | .PrimitiveOp _ args => args.attach.any (fun x => containsImperativeCall model x.val) + | .PrimitiveOp _ args _ => args.attach.any (fun x => containsImperativeCall model x.val) | .Block stmts _ => stmts.attach.any (fun x => containsImperativeCall model x.val) | .IfThenElse cond th el => containsImperativeCall model cond || @@ -277,7 +277,7 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do return resultExpr - | .PrimitiveOp op args => + | .PrimitiveOp op args _ => -- Process arguments right to left let seqArgs ← args.reverse.mapM transformExpr return ⟨.PrimitiveOp op seqArgs.reverse, source⟩ diff --git a/Strata/Languages/Laurel/TypeHierarchy.lean b/Strata/Languages/Laurel/TypeHierarchy.lean index 26b72ff23f..9afbe99f69 100644 --- a/Strata/Languages/Laurel/TypeHierarchy.lean +++ b/Strata/Languages/Laurel/TypeHierarchy.lean @@ -168,7 +168,7 @@ def validateDiamondFieldAccessesForStmtExpr (model : SemanticModel) invs.attach.foldl (fun acc ⟨inv, _⟩ => acc ++ validateDiamondFieldAccessesForStmtExpr model inv) errs | .Assert cond => validateDiamondFieldAccessesForStmtExpr model cond.condition | .Assume cond => validateDiamondFieldAccessesForStmtExpr model cond - | .PrimitiveOp _ args => + | .PrimitiveOp _ args _ => args.attach.foldl (fun acc ⟨a, _⟩ => acc ++ validateDiamondFieldAccessesForStmtExpr model a) [] | .StaticCall _ args => args.attach.foldl (fun acc ⟨a, _⟩ => acc ++ validateDiamondFieldAccessesForStmtExpr model a) [] diff --git a/Strata/Languages/Python/PythonToLaurel.lean b/Strata/Languages/Python/PythonToLaurel.lean index 0c1a030899..628e878939 100644 --- a/Strata/Languages/Python/PythonToLaurel.lean +++ b/Strata/Languages/Python/PythonToLaurel.lean @@ -1358,7 +1358,7 @@ def extractMultiOutputCalls (ctx : TranslationContext) (e : StmtExprMd) return ([], e) else return (preamble, mkStmtExprMdWithLoc (.StaticCall callee.text newArgs) e.source) - | .PrimitiveOp op args => + | .PrimitiveOp op args _ => let results ← args.attach.mapM fun ⟨arg, _⟩ => extractMultiOutputCalls ctx arg let preamble := (results.map (fun (pre, _) => pre)).flatten let newArgs := results.map (·.2) @@ -1649,7 +1649,7 @@ partial def getMaybeExceptionExprs (ctx : TranslationContext) (e : StmtExprMd) : if isMaybeExceptAnyFunc ctx funcname.text then [e] else args.flatMap $ getMaybeExceptionExprs ctx - | .PrimitiveOp _ args => args.flatMap $ getMaybeExceptionExprs ctx + | .PrimitiveOp _ args _ => args.flatMap $ getMaybeExceptionExprs ctx | .IfThenElse cond thenBranch elseBranch => ([cond, thenBranch] ++ elseBranch.toList).flatMap $ getMaybeExceptionExprs ctx | _ => [] @@ -1675,7 +1675,7 @@ partial def containsUserCall (ctx : TranslationContext) (e : StmtExprMd) : Bool callee.text ∈ ctx.userFunctions || withException ctx callee.text || args.any (containsUserCall ctx) - | .PrimitiveOp _ args => args.any (containsUserCall ctx) + | .PrimitiveOp _ args _ => args.any (containsUserCall ctx) | .IfThenElse cond thenBranch elseBranch => containsUserCall ctx cond || containsUserCall ctx thenBranch || elseBranch.any (containsUserCall ctx) From 200e82815d569d1a875c2d7db02cc9d0ca6a1e07 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 28 May 2026 13:44:15 +0000 Subject: [PATCH 035/115] Improve comment --- Strata/Languages/Laurel/Laurel.lean | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Strata/Languages/Laurel/Laurel.lean b/Strata/Languages/Laurel/Laurel.lean index 1a851785c9..ae592752dc 100644 --- a/Strata/Languages/Laurel/Laurel.lean +++ b/Strata/Languages/Laurel/Laurel.lean @@ -294,7 +294,8 @@ inductive StmtExpr : Type where /-- Call a static procedure by name with the given arguments. -/ | StaticCall (callee : Identifier) (arguments : List (AstNode StmtExpr)) /-- Apply a primitive operation to the given arguments. - The skipProof property is used internally. -/ + The skipProof property is used internally. + It means that any precondition of the operator, such as division has, should be ignored. -/ | PrimitiveOp (operator : Operation) (arguments : List (AstNode StmtExpr)) (skipProof: Bool := false) /-- Create new object (`new`). -/ From 9400328c46fd1ad5ca63bc0e108eed8928c556ec Mon Sep 17 00:00:00 2001 From: keyboardDrummer-bot Date: Thu, 28 May 2026 14:03:25 +0000 Subject: [PATCH 036/115] Generate separate $pre and $post procedures for each condition Instead of generating a single $pre procedure that conjoins all preconditions and a single $post that conjoins all postconditions, generate foo$pre0, foo$pre1, ... and foo$post0, foo$post1, ... one per condition. This gives better error messages since each condition is checked independently. --- Strata/Languages/Laurel/ContractPass.lean | 258 +++++------------- .../Languages/Python/PreludeVerifyTest.lean | 10 +- 2 files changed, 82 insertions(+), 186 deletions(-) diff --git a/Strata/Languages/Laurel/ContractPass.lean b/Strata/Languages/Laurel/ContractPass.lean index e9a99c06fd..fb60852d4f 100644 --- a/Strata/Languages/Laurel/ContractPass.lean +++ b/Strata/Languages/Laurel/ContractPass.lean @@ -15,17 +15,16 @@ 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 all inputs and all - outputs as parameters and returns the conjunction of postconditions. It is - marked as functional and does not call the original procedure. -- Insert `assume foo$pre(inputs)` at the start of the body. -- Insert `assert foo$post(inputs, outputs)` at the end of the body. +- Generate a separate precondition procedure (`foo$pre0`, `foo$pre1`, ...) for each precondition. +- Generate a separate postcondition procedure (`foo$post0`, `foo$post1`, ...) for each postcondition. + Each takes all inputs and all outputs as parameters and returns the condition. +- Insert `assume foo$pre0(inputs); assume foo$pre1(inputs); ...` at the start of the body. +- Insert `assert foo$post0(inputs, outputs); assert foo$post1(inputs, outputs); ...` at the end of the body. For each call to a contracted procedure: - Assign all input arguments to temporary variables before the call. -- Insert `assert foo$pre(temps)` before the call (precondition check). -- After the call, insert `assume foo$post(temps, outputs)` (postcondition assumption). +- Insert `assert foo$pre0(temps); assert foo$pre1(temps); ...` before the call. +- After the call, insert `assume foo$post0(temps, outputs); assume foo$post1(temps, outputs); ...`. -/ namespace Strata.Laurel @@ -35,19 +34,11 @@ public section private def mkMd (e : StmtExpr) : StmtExprMd := { val := e, source := none } private def mkVarMd (v : Variable) : VariableMd := { val := v, source := none } -/-- 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 i-th precondition helper procedure. -/ +def preCondProcName (procName : String) (i : Nat) : String := s!"{procName}$pre{i}" -/-- 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" +/-- Name for the i-th postcondition helper procedure. -/ +def postCondProcName (procName : String) (i : Nat) : String := s!"{procName}$post{i}" /-- Get postconditions from a procedure body. -/ private def getPostconditions (body : Body) : List Condition := @@ -64,32 +55,22 @@ private def mkCall (callee : String) (args : List StmtExprMd) : StmtExprMd := private def paramsToArgs (params : List Parameter) : List StmtExprMd := params.map fun p => mkMd (.Var (.Local p.name)) -/-- Build a helper function that returns the conjunction of the given conditions. -/ +/-- Build a helper function for a single condition. -/ private def mkConditionProc (name : String) (params : List Parameter) - (conditions : List Condition) : Procedure := + (condition : Condition) : Procedure := { name := mkId name inputs := params outputs := [⟨mkId "$result", { val := .TBool, source := none }⟩] preconditions := [] decreases := none isFunctional := true - body := .Transparent (conjoin (conditions.map (·.condition))) } - -/-- Build a postcondition function that takes all inputs and all outputs as - parameters and returns the conjunction of postconditions. The function is - marked as functional and does not call the original procedure. + body := .Transparent condition.condition } - For a procedure `foo(a, b) returns (x, y)` with postcondition `P(a, b, x, y)`, - generates: - ``` - function foo$post(a, b, x, y) returns ($result : bool) { - P(a, b, x, y) - } - ``` --/ +/-- Build a postcondition function for a single condition that takes all inputs + and all outputs as parameters. -/ private def mkPostConditionProc (name : String) (inputParams : List Parameter) (outputParams : List Parameter) - (conditions : List Condition) : Procedure := + (condition : Condition) : Procedure := let allParams := inputParams ++ outputParams { name := mkId name inputs := allParams @@ -97,27 +78,18 @@ private def mkPostConditionProc (name : String) preconditions := [] decreases := none isFunctional := false - body := .Transparent (conjoin (conditions.map (·.condition))) } - -/-- Extract a combined summary from a list of conditions. -/ -private def combinedSummary (clauses : List Condition) : Option String := - let summaries := clauses.filterMap (·.summary) - match summaries with - | [] => none - | [s] => some s - | ss => some (String.intercalate ", " ss) + body := .Transparent condition.condition } /-- Information about a procedure's contracts. -/ private structure ContractInfo where - hasPreCondition : Bool - hasPostCondition : Bool - preName : String - postName : String - preSummary : Option String - postSummary : Option String + preNames : List (String × Option String) -- (procName, summary) for each precondition + postNames : List (String × Option String) -- (procName, summary) for each postcondition inputParams : List Parameter outputParams : List Parameter +private def ContractInfo.hasPreCondition (info : ContractInfo) : Bool := !info.preNames.isEmpty +private def ContractInfo.hasPostCondition (info : ContractInfo) : Bool := !info.postNames.isEmpty + /-- Collect contract info for all procedures with contracts. -/ private def collectContractInfo (procs : List Procedure) : Std.HashMap String ContractInfo := procs.foldl (fun m proc => @@ -125,13 +97,13 @@ private def collectContractInfo (procs : List Procedure) : Std.HashMap String Co let hasPre := !proc.preconditions.isEmpty let hasPost := !postconds.isEmpty if !proc.isFunctional && (hasPre || hasPost) then + let preNames := proc.preconditions.zipIdx.map fun (c, i) => + (preCondProcName proc.name.text i, c.summary) + let postNames := postconds.zipIdx.map fun (c, i) => + (postCondProcName proc.name.text i, c.summary) 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 + preNames := preNames + postNames := postNames inputParams := proc.inputs outputParams := proc.outputs } @@ -141,24 +113,18 @@ private def collectContractInfo (procs : List Procedure) : Std.HashMap String Co private def transformProcBody (proc : Procedure) (info : ContractInfo) : Body := let inputArgs := paramsToArgs proc.inputs let postconds := getPostconditions proc.body - let preAssume : List StmtExprMd := - if info.hasPreCondition then - let preSrc := match proc.preconditions.head? with - | some pc => pc.condition.source - | none => none - [⟨.Assume (mkCall info.preName inputArgs), preSrc⟩] - else [] - let postAssert : List StmtExprMd := - if info.hasPostCondition then - postconds.map fun pc => - let summary := pc.summary.getD "postcondition" - ⟨.Assert { condition := pc.condition, summary := some summary }, pc.condition.source⟩ - else [] + let preAssumes : List StmtExprMd := + info.preNames.map fun (name, _) => + ⟨.Assume (mkCall name inputArgs), none⟩ + let postAsserts : List StmtExprMd := + postconds.zip info.postNames |>.map fun (pc, _name, _summary) => + let summary := pc.summary.getD "postcondition" + ⟨.Assert { condition := pc.condition, summary := some summary }, pc.condition.source⟩ match proc.body with | .Transparent body => - .Transparent ⟨.Block (preAssume ++ [body] ++ postAssert) none, body.source⟩ + .Transparent ⟨.Block (preAssumes ++ [body] ++ postAsserts) none, body.source⟩ | .Opaque _ (some impl) _ => - .Opaque [] (some ⟨.Block (preAssume ++ [impl] ++ postAssert) none, impl.source⟩) [] + .Opaque [] (some ⟨.Block (preAssumes ++ [impl] ++ postAsserts) none, impl.source⟩) [] | .Opaque _ none mods => .Opaque [] none mods | .Abstract _ => @@ -166,10 +132,7 @@ private def transformProcBody (proc : Procedure) (info : ContractInfo) : Body := | b => b /-- Generate temporary variable assignments for input arguments at a call site. - Returns (temp declarations+assignments, temp variable references). - Uses the parameter types from the procedure's contract info so that - resolution can type-check the generated temporaries. - `callIdx` distinguishes multiple calls to the same procedure. -/ + Returns (temp declarations+assignments, temp variable references). -/ private def mkTempAssignments (args : List StmtExprMd) (calleeName : String) (inputParams : List Parameter) (callIdx : Nat) (src : Option FileRange) : List StmtExprMd × List StmtExprMd := @@ -186,101 +149,32 @@ private def mkTempAssignments (args : List StmtExprMd) (calleeName : String) mkMd (.Var (.Local (mkId tempName))) (decls, refs) -/-- 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). - Takes and returns a call counter for generating unique temp variable names. - When `isFunctional` is true, precondition checks use `assume` instead of - `assert` since asserts are not supported in functions during Core translation. +/-- Generate precondition checks (one per precondition) for a call site. -/ +private def mkPreChecks (info : ContractInfo) (isFunctional : Bool) + (tempRefs : List StmtExprMd) (src : Option FileRange) : List StmtExprMd := + if !info.hasPreCondition then [] + else info.preNames.map fun (name, summary) => + let call := mkCall name tempRefs + if isFunctional then + ⟨.Assume call, src⟩ + else + ⟨.Assert { condition := call, summary := some (summary.getD "precondition") }, src⟩ - At call sites: - 1. Assign input arguments to temporary variables. - 2. Assert precondition using temps. - 3. Execute the call using temps as arguments. - 4. Assume postcondition using temps + output variables. -/ -private def rewriteStmt (contractInfoMap : Std.HashMap String ContractInfo) - (isFunctional : Bool) (callCounter : Nat) (e : StmtExprMd) : List StmtExprMd × Nat := - let src := e.source - let mkWithSrc (se : StmtExpr) : StmtExprMd := ⟨se, src⟩ - match e.val with - | .Assign targets (.mk (.StaticCall callee args) callSrc) => - match contractInfoMap.get? callee.text with - | some info => - let (tempDecls, tempRefs) := mkTempAssignments args callee.text info.inputParams callCounter src - let callWithTemps : StmtExprMd := ⟨.Assign targets ⟨.StaticCall callee tempRefs, callSrc⟩, src⟩ - let preCheck := if info.hasPreCondition then - if isFunctional then - [mkWithSrc (.Assume (mkCall info.preName tempRefs))] - else - [mkWithSrc (.Assert { condition := mkCall info.preName tempRefs, summary := some (info.preSummary.getD "precondition") })] - else [] - -- After the call, assume postcondition with temps (inputs) + output variables - let outputArgs := targets.filterMap fun t => - match t.val with - | .Local name => some (mkMd (.Var (.Local name))) - | .Declare param => some (mkMd (.Var (.Local param.name))) - | _ => none - let postAssume := if info.hasPostCondition - then [mkWithSrc (.Assume (mkCall info.postName (tempRefs ++ outputArgs)))] else [] - (tempDecls ++ preCheck ++ [callWithTemps] ++ postAssume, callCounter + 1) - | none => ([e], callCounter) - | .StaticCall callee args => - match contractInfoMap.get? callee.text with - | some info => - let (tempDecls, tempRefs) := mkTempAssignments args callee.text info.inputParams callCounter src - let preCheck := if info.hasPreCondition then - if isFunctional then - [mkWithSrc (.Assume (mkCall info.preName tempRefs))] - else - [mkWithSrc (.Assert { condition := mkCall info.preName tempRefs, summary := some (info.preSummary.getD "precondition") })] - else [] - -- For bare calls with postconditions, capture outputs in temp variables - -- so we can pass them to the $post function. - let (callStmt, postAssume, returnValue) := - if info.hasPostCondition && !info.outputParams.isEmpty then - let outputTempDecls := info.outputParams.zipIdx.map fun (p, i) => - let tempName := s!"${callee.text}${callCounter}$out{i}" - mkVarMd (.Declare { name := mkId tempName, type := p.type }) - let callWithOutputs : StmtExprMd := - ⟨.Assign outputTempDecls ⟨.StaticCall callee tempRefs, src⟩, src⟩ - let outputRefs := info.outputParams.zipIdx.map fun (_, i) => - let tempName := s!"${callee.text}${callCounter}$out{i}" - mkMd (.Var (.Local (mkId tempName))) - let assume := [mkWithSrc (.Assume (mkCall info.postName (tempRefs ++ outputRefs)))] - -- If the procedure has a single output, append the output variable - -- reference so the expanded block evaluates to the call result - -- (needed when the call appears in expression position). - let retVal : List StmtExprMd := match outputRefs with - | [single] => [single] - | _ => [] - (callWithOutputs, assume, retVal) - else - (mkWithSrc (.StaticCall callee tempRefs), [], []) - (tempDecls ++ preCheck ++ [callStmt] ++ postAssume ++ returnValue, callCounter + 1) - | none => ([e], callCounter) - | _ => ([e], callCounter) +/-- Generate postcondition assumes (one per postcondition) for a call site. -/ +private def mkPostAssumes (info : ContractInfo) + (tempRefs : List StmtExprMd) (outputArgs : List StmtExprMd) (src : Option FileRange) : List StmtExprMd := + if !info.hasPostCondition then [] + else info.postNames.map fun (name, _) => + ⟨.Assume (mkCall name (tempRefs ++ outputArgs)), src⟩ -/-- Rewrite call sites in a statement/expression tree. Uses `mapStmtExprFlattenM`: - - `pre` intercepts `Assign targets (StaticCall ...)` to a contracted procedure, - handling it directly so the assignment targets are used as output variables - for the postcondition assume. - - `post` handles bare `StaticCall` to a contracted procedure anywhere in the - tree, returning the expanded list of statements (argument assignments, - precondition assert, call, postcondition assume, output variable reference). - For Block parents the list is flattened; for other parents it is wrapped - in a Block. -/ +/-- Rewrite call sites in a statement/expression tree. -/ private def rewriteCallSites (contractInfoMap : Std.HashMap String ContractInfo) (isFunctional : Bool) (expr : StmtExprMd) : StmtExprMd := let rewriteStaticCall (counter : Nat) (callee : Identifier) (args : List StmtExprMd) (info : ContractInfo) (src : Option FileRange) : List StmtExprMd × Nat := - let mkWithSrc (se : StmtExpr) : StmtExprMd := ⟨se, src⟩ let (tempDecls, tempRefs) := mkTempAssignments args callee.text info.inputParams counter src - let preCheck := if info.hasPreCondition then - if isFunctional then - [mkWithSrc (.Assume (mkCall info.preName tempRefs))] - else - [mkWithSrc (.Assert { condition := mkCall info.preName tempRefs, summary := some (info.preSummary.getD "precondition") })] - else [] + let preCheck := mkPreChecks info isFunctional tempRefs src let (callStmt, postAssume, returnValue) := if info.hasPostCondition && !info.outputParams.isEmpty then let outputTempDecls := info.outputParams.zipIdx.map fun (p, i) => @@ -291,13 +185,13 @@ private def rewriteCallSites (contractInfoMap : Std.HashMap String ContractInfo) let outputRefs := info.outputParams.zipIdx.map fun (_, i) => let tempName := s!"${callee.text}${counter}$out{i}" mkMd (.Var (.Local (mkId tempName))) - let assume := [mkWithSrc (.Assume (mkCall info.postName (tempRefs ++ outputRefs)))] + let assume := mkPostAssumes info tempRefs outputRefs src let retVal : List StmtExprMd := match outputRefs with | [single] => [single] | _ => [] (callWithOutputs, assume, retVal) else - (mkWithSrc (.StaticCall callee tempRefs), [], []) + (⟨.StaticCall callee tempRefs, src⟩, [], []) (tempDecls ++ preCheck ++ [callStmt] ++ postAssume ++ returnValue, counter + 1) let (result, _) := StateT.run (s := (0 : Nat)) <| mapStmtExprFlattenM (m := StateM Nat) @@ -309,8 +203,7 @@ private def rewriteCallSites (contractInfoMap : Std.HashMap String ContractInfo) | some info => let counter ← get let src := e.source - let mkWithSrc (se : StmtExpr) : StmtExprMd := ⟨se, src⟩ - -- Recurse into arguments using mapStmtExprM with the post logic + -- Recurse into arguments let args' ← args.mapM (mapStmtExprM (m := StateM Nat) (fun e' => do match e'.val with | .StaticCall callee' args' => @@ -324,24 +217,18 @@ private def rewriteCallSites (contractInfoMap : Std.HashMap String ContractInfo) | _ => return e')) let (tempDecls, tempRefs) := mkTempAssignments args' callee.text info.inputParams counter src let callWithTemps : StmtExprMd := ⟨.Assign targets ⟨.StaticCall callee tempRefs, callSrc⟩, src⟩ - let preCheck := if info.hasPreCondition then - if isFunctional then - [mkWithSrc (.Assume (mkCall info.preName tempRefs))] - else - [mkWithSrc (.Assert { condition := mkCall info.preName tempRefs, summary := some (info.preSummary.getD "precondition") })] - else [] + let preCheck := mkPreChecks info isFunctional tempRefs src let outputArgs := targets.filterMap fun t => match t.val with | .Local name => some (mkMd (.Var (.Local name))) | .Declare param => some (mkMd (.Var (.Local param.name))) | _ => none - let postAssume := if info.hasPostCondition - then [mkWithSrc (.Assume (mkCall info.postName (tempRefs ++ outputArgs)))] else [] + let postAssume := mkPostAssumes info tempRefs outputArgs src set (counter + 1) return some (tempDecls ++ preCheck ++ [callWithTemps] ++ postAssume) | none => return none | _ => return none) - -- Post: handle bare StaticCall (not direct RHS of Assign to contracted proc) + -- Post: handle bare StaticCall (fun e => do match e.val with | .StaticCall callee args => @@ -372,7 +259,10 @@ private def rewriteCallSitesInProc (contractInfoMap : Std.HashMap String Contrac The trigger controls when the SMT solver instantiates the axiom. -/ private def mkInvokeOnAxiom (params : List Parameter) (trigger : StmtExprMd) (postconds : List Condition) : StmtExprMd := - let body := conjoin (postconds.map (·.condition)) + let body := match postconds.map (·.condition) with + | [] => mkMd (.LiteralBool true) + | [e] => e + | e :: rest => rest.foldl (fun acc x => mkMd (.PrimitiveOp .And [acc, x])) e -- 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) => @@ -387,14 +277,12 @@ def contractPass (program : Program) : Program := -- Generate helper procedures for all procedures with contracts let helperProcs := (program.staticProcedures.filter (fun proc => !proc.isFunctional)).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.inputs proc.outputs postconds] - preProc ++ postProc + let preProcs := proc.preconditions.zipIdx.map fun (c, i) => + mkConditionProc (preCondProcName proc.name.text i) proc.inputs c + let postProcs := postconds.zipIdx.map fun (c, i) => + mkPostConditionProc (postCondProcName proc.name.text i) + proc.inputs proc.outputs c + preProcs ++ postProcs -- Transform procedures: strip contracts, add assume/assert, rewrite call sites let transformedProcs := program.staticProcedures.map fun proc => diff --git a/StrataTest/Languages/Python/PreludeVerifyTest.lean b/StrataTest/Languages/Python/PreludeVerifyTest.lean index ad21d1bcdc..7424006091 100644 --- a/StrataTest/Languages/Python/PreludeVerifyTest.lean +++ b/StrataTest/Languages/Python/PreludeVerifyTest.lean @@ -33,7 +33,15 @@ private def verifyPrelude : IO (Array DiagnosticModel) := do (externalPhases := [Strata.frontEndPhase])) return r.flatMap (fun vcr => (toDiagnosticModel vcr []).toArray) -/-- info: #[] -/ +/-- +info: ⚠️ [addPathCondition] Label clash detected for assume(0), using unique label assume(0)_1. +⚠️ [addPathCondition] Label clash detected for assume(0), using unique label assume(0)_2. +⚠️ [addPathCondition] Label clash detected for assume(0), using unique label assume(0)_3. +⚠️ [addPathCondition] Label clash detected for assume(0), using unique label assume(0)_1. +⚠️ [addPathCondition] Label clash detected for assume(0), using unique label assume(0)_2. +--- +info: #[] +-/ #guard_msgs in #eval verifyPrelude From 65ab6ae5c28d0990016967c014db7141988b4760 Mon Sep 17 00:00:00 2001 From: keyboardDrummer-bot Date: Thu, 28 May 2026 15:01:04 +0000 Subject: [PATCH 037/115] Rename SepFormat.semicolon to SepFormat.semicolonNewline --- Strata/DDM/AST.lean | 6 +++--- Strata/DDM/Elab/Core.lean | 4 ++-- Strata/DDM/Format.lean | 2 +- Strata/DDM/Integration/Lean/Gen.lean | 4 ++-- Strata/DDM/Integration/Lean/ToExpr.lean | 2 +- Strata/DDM/Ion.lean | 8 ++++---- .../Laurel/Grammar/AbstractToConcreteTreeTranslator.lean | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Strata/DDM/AST.lean b/Strata/DDM/AST.lean index 8e947f40b8..3a77bbd1c1 100644 --- a/Strata/DDM/AST.lean +++ b/Strata/DDM/AST.lean @@ -192,7 +192,7 @@ inductive SepFormat where | space -- Space separator (SpaceSepBy) | spacePrefix -- Space before each element (SpacePrefixSepBy) | newline -- Newline separator (NewlineSepBy) -| semicolon -- Semicolon separator (SemicolonSepBy) +| semicolonNewline -- Semicolon+newline separator (SemicolonSepBy) deriving Inhabited, Repr, BEq namespace SepFormat @@ -203,7 +203,7 @@ def toString : SepFormat → String | .space => "spaceSepBy" | .spacePrefix => "spacePrefixSepBy" | .newline => "newlineSepBy" - | .semicolon => "semicolonSepBy" + | .semicolonNewline => "semicolonSepBy" def fromCategoryName? : QualifiedIdent → Option SepFormat | q`Init.Seq => some .none @@ -211,7 +211,7 @@ def fromCategoryName? : QualifiedIdent → Option SepFormat | q`Init.SpaceSepBy => some .space | q`Init.SpacePrefixSepBy => some .spacePrefix | q`Init.NewlineSepBy => some .newline - | q`Init.SemicolonSepBy => some .semicolon + | q`Init.SemicolonSepBy => some .semicolonNewline | _ => .none #guard fromCategoryName? ⟨"Init", "Ident"⟩ == .none diff --git a/Strata/DDM/Elab/Core.lean b/Strata/DDM/Elab/Core.lean index ac7821282d..e2bf6b0d9e 100644 --- a/Strata/DDM/Elab/Core.lean +++ b/Strata/DDM/Elab/Core.lean @@ -1079,7 +1079,7 @@ private def scopeSepFormat (name : QualifiedIdent) match name with | q`Init.Seq => some (.none, Syntax.getArgs) | q`Init.CommaSepBy => some (.comma, Syntax.getSepArgs) - | q`Init.SemicolonSepBy => some (.semicolon, Syntax.getSepArgs) + | q`Init.SemicolonSepBy => some (.semicolonNewline, Syntax.getSepArgs) | q`Init.SpaceSepBy => some (.space, Syntax.getSepArgs) | q`Init.SpacePrefixSepBy => some (.spacePrefix, Syntax.getArgs) | q`Init.NewlineSepBy => some (.newline, Syntax.getArgs) @@ -1668,7 +1668,7 @@ partial def catElaborator (c : SyntaxCat) : TypingContext → Syntax → ElabM T | q`Init.CommaSepBy => elabSeqWith c .comma "commaSepBy" (·.getSepArgs) | q`Init.SemicolonSepBy => - elabSeqWith c .semicolon "semicolonSepBy" (·.getSepArgs) + elabSeqWith c .semicolonNewline "semicolonSepBy" (·.getSepArgs) | q`Init.SpaceSepBy => elabSeqWith c .space "spaceSepBy" (·.getArgs) | q`Init.SpacePrefixSepBy => diff --git a/Strata/DDM/Format.lean b/Strata/DDM/Format.lean index 4d97f30839..4ef93882b8 100644 --- a/Strata/DDM/Format.lean +++ b/Strata/DDM/Format.lean @@ -449,7 +449,7 @@ private partial def ArgF.mformatM {α} : ArgF α → FormatM PrecFormat let f i q s := return s ++ "\n" ++ (← entries[i].mformatM).format let a := (← entries[0].mformatM).format .atom <$> entries.size.foldlM f (start := 1) a - | .semicolon => + | .semicolonNewline => if z : entries.size = 0 then pure (.atom .nil) else do diff --git a/Strata/DDM/Integration/Lean/Gen.lean b/Strata/DDM/Integration/Lean/Gen.lean index e99569dc9f..b4be95915b 100644 --- a/Strata/DDM/Integration/Lean/Gen.lean +++ b/Strata/DDM/Integration/Lean/Gen.lean @@ -918,7 +918,7 @@ partial def toAstApplyArg (vn : Name) (cat : SyntaxCat) | q`Init.CommaSepBy => do toAstApplyArgSeq v cat ``SepFormat.comma | q`Init.SemicolonSepBy => do - toAstApplyArgSeq v cat ``SepFormat.semicolon + toAstApplyArgSeq v cat ``SepFormat.semicolonNewline | q`Init.SpaceSepBy => do toAstApplyArgSeq v cat ``SepFormat.space | q`Init.SpacePrefixSepBy => do @@ -1182,7 +1182,7 @@ partial def genOfAstArgTerm (varName : String) (cat : SyntaxCat) | q`Init.CommaSepBy => do genOfAstSeqArgTerm varName cat e ``SepFormat.comma | q`Init.SemicolonSepBy => do - genOfAstSeqArgTerm varName cat e ``SepFormat.semicolon + genOfAstSeqArgTerm varName cat e ``SepFormat.semicolonNewline | q`Init.SpaceSepBy => do genOfAstSeqArgTerm varName cat e ``SepFormat.space | q`Init.SpacePrefixSepBy => do diff --git a/Strata/DDM/Integration/Lean/ToExpr.lean b/Strata/DDM/Integration/Lean/ToExpr.lean index 2f505cc5ef..74a1688859 100644 --- a/Strata/DDM/Integration/Lean/ToExpr.lean +++ b/Strata/DDM/Integration/Lean/ToExpr.lean @@ -39,7 +39,7 @@ instance : ToExpr SepFormat where | .space => mkConst ``SepFormat.space | .spacePrefix => mkConst ``SepFormat.spacePrefix | .newline => mkConst ``SepFormat.newline - | .semicolon => mkConst ``SepFormat.semicolon + | .semicolonNewline => mkConst ``SepFormat.semicolonNewline end SepFormat diff --git a/Strata/DDM/Ion.lean b/Strata/DDM/Ion.lean index baa268e797..fe073713a6 100644 --- a/Strata/DDM/Ion.lean +++ b/Strata/DDM/Ion.lean @@ -118,7 +118,7 @@ def toIonName : SepFormat → String | .space => "spaceSepList" | .spacePrefix => "spacePrefixedList" | .newline => "newlineSepList" - | .semicolon => "semicolonSepList" + | .semicolonNewline => "semicolonSepList" def fromIonName? : String → Option SepFormat | "seq" => some .none @@ -126,7 +126,7 @@ def fromIonName? : String → Option SepFormat | "spaceSepList" => some .space | "spacePrefixedList" => some .spacePrefix | "newlineSepList" => some .newline - | "semicolonSepList" => some .semicolon + | "semicolonSepList" => some .semicolonNewline | _ => .none theorem fromIonName_toIonName_roundtrip (sep : SepFormat) : @@ -140,7 +140,7 @@ theorem fromIonName_none_of_invalid (s : String) (h : ∀ sep, toIonName sep ≠ split <;> first | rfl | (exfalso; have := h .none; have := h .comma; have := h .space - have := h .spacePrefix; have := h .newline; have := h .semicolon + have := h .spacePrefix; have := h .newline; have := h .semicolonNewline simp_all [toIonName]) end SepFormat @@ -613,7 +613,7 @@ private protected def ArgF.toIon {α} [ToIon α] | .space => ionSymbol! "spaceSepList" | .spacePrefix => ionSymbol! "spacePrefixedList" | .newline => ionSymbol! "newlineSepList" - | .semicolon => ionSymbol! "semicolonSepList" + | .semicolonNewline => ionSymbol! "semicolonSepList" let args : Array (Ion _) := #[ symb, annIon ] let args ← l.attach.mapM_off (init := args) fun ⟨v, _⟩ => ArgF.toIon refs (.inl v) diff --git a/Strata/Languages/Laurel/Grammar/AbstractToConcreteTreeTranslator.lean b/Strata/Languages/Laurel/Grammar/AbstractToConcreteTreeTranslator.lean index 1ed8e93828..f9d45c0ddc 100644 --- a/Strata/Languages/Laurel/Grammar/AbstractToConcreteTreeTranslator.lean +++ b/Strata/Languages/Laurel/Grammar/AbstractToConcreteTreeTranslator.lean @@ -28,7 +28,7 @@ private def optionArg (a : Option Arg) : Arg := .option sr a private def commaSep (args : Array Arg) : Arg := .seq sr .comma args -private def semicolonSep (args : Array Arg) : Arg := .seq sr .semicolon args +private def semicolonSep (args : Array Arg) : Arg := .seq sr .semicolonNewline args private def seqArg (args : Array Arg) : Arg := .seq sr .none args From 6c0c39c02865402ea58309fdb026f058495be12b Mon Sep 17 00:00:00 2001 From: keyboardDrummer-bot Date: Thu, 28 May 2026 15:22:35 +0000 Subject: [PATCH 038/115] Fix contract pass: preserve postconditions in Opaque/Abstract bodies Three issues fixed: 1. Postconditions in Opaque/Abstract bodies were replaced with inline asserts that were unreachable due to 'exit $body' from the EliminateReturnStatements pass. Now postconditions are preserved in the body so the Laurel-to-Core translator generates proper 'ensures' clauses in the procedure spec. 2. Generated assume statements used source=none, causing all of them to get the same label 'assume(0)' and triggering label clash warnings. Now uses the precondition's source location for unique labels. 3. Postconditions without explicit summaries now get 'postcondition' as their default property summary, so verification failures report 'postcondition does not hold' instead of 'assertion does not hold'. --- Strata/Languages/Laurel/ContractPass.lean | 18 +++++++++--------- .../Laurel/LaurelToCoreTranslator.lean | 4 +++- .../Languages/Python/PreludeVerifyTest.lean | 10 +--------- .../expected_laurel/test_class_empty.expected | 2 +- .../test_class_field_init.expected | 4 +++- .../test_class_field_use.expected | 3 ++- .../test_class_methods.expected | 14 ++++++++++---- .../test_class_mixed_init.expected | 4 +++- .../test_class_no_init.expected | 2 +- .../test_class_no_init_multi_field.expected | 2 +- .../test_class_no_init_with_method.expected | 2 +- .../test_class_with_methods.expected | 12 +++++++++--- .../expected_laurel/test_deep_inline.expected | 14 +++++++++----- .../test_havoc_callee_after_hole_call.expected | 3 ++- .../test_with_statement.expected | 6 +++++- 15 files changed, 60 insertions(+), 40 deletions(-) diff --git a/Strata/Languages/Laurel/ContractPass.lean b/Strata/Languages/Laurel/ContractPass.lean index fb60852d4f..8f063d5877 100644 --- a/Strata/Languages/Laurel/ContractPass.lean +++ b/Strata/Languages/Laurel/ContractPass.lean @@ -114,21 +114,21 @@ private def transformProcBody (proc : Procedure) (info : ContractInfo) : Body := let inputArgs := paramsToArgs proc.inputs let postconds := getPostconditions proc.body let preAssumes : List StmtExprMd := - info.preNames.map fun (name, _) => - ⟨.Assume (mkCall name inputArgs), none⟩ - let postAsserts : List StmtExprMd := - postconds.zip info.postNames |>.map fun (pc, _name, _summary) => - let summary := pc.summary.getD "postcondition" - ⟨.Assert { condition := pc.condition, summary := some summary }, pc.condition.source⟩ + proc.preconditions.zip info.preNames |>.map fun (pc, name, _) => + ⟨.Assume (mkCall name inputArgs), pc.condition.source⟩ match proc.body with | .Transparent body => + let postAsserts : List StmtExprMd := + postconds.zip info.postNames |>.map fun (pc, _name, _summary) => + let summary := pc.summary.getD "postcondition" + ⟨.Assert { condition := pc.condition, summary := some summary }, pc.condition.source⟩ .Transparent ⟨.Block (preAssumes ++ [body] ++ postAsserts) none, body.source⟩ | .Opaque _ (some impl) _ => - .Opaque [] (some ⟨.Block (preAssumes ++ [impl] ++ postAsserts) none, impl.source⟩) [] + .Opaque postconds (some ⟨.Block (preAssumes ++ [impl]) none, impl.source⟩) [] | .Opaque _ none mods => - .Opaque [] none mods + .Opaque postconds none mods | .Abstract _ => - .Abstract [] + .Abstract postconds | b => b /-- Generate temporary variable assignments for input arguments at a call site. diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index fcccd001b5..dad747c4b8 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -528,12 +528,13 @@ Translate a list of checks (preconditions or postconditions) to Core checks. Each check gets a label like `"requires"` or `"requires_0"`, `"requires_1"`, etc. -/ private def translateChecks (checks : List Condition) (labelBase : String) (overrideFree: Bool) + (defaultSummary : Option String := none) : TranslateM (ListMap Core.CoreLabel Core.Procedure.Check) := checks.mapIdxM (fun i check => do let label := if checks.length == 1 then labelBase else s!"{labelBase}_{i}" let checkExpr ← translateExpr check.condition [] (isPureContext := true) let baseMd := astNodeToCoreMd check.condition - let md := match check.summary with + let md := match check.summary.orElse (fun _ => defaultSummary) with | some msg => baseMd.pushElem Imperative.MetaData.propertySummary (.msg msg) | none => baseMd let attr := if check.free || overrideFree then Core.Procedure.CheckAttr.Free else .Default @@ -582,6 +583,7 @@ def translateProcedure (proc : Procedure) : TranslateM Core.Procedure := do match proc.body with | .Opaque postconds _ _ | .Abstract postconds => translateChecks postconds s!"postcondition{(bodyStmts.getD []).length}" bodyStmts.isNone + (defaultSummary := "postcondition") | _ => pure [] let body : List Core.Statement := [.block "$body" (bodyStmts.getD []) mdWithUnknownLoc] diff --git a/StrataTest/Languages/Python/PreludeVerifyTest.lean b/StrataTest/Languages/Python/PreludeVerifyTest.lean index 7424006091..ad21d1bcdc 100644 --- a/StrataTest/Languages/Python/PreludeVerifyTest.lean +++ b/StrataTest/Languages/Python/PreludeVerifyTest.lean @@ -33,15 +33,7 @@ private def verifyPrelude : IO (Array DiagnosticModel) := do (externalPhases := [Strata.frontEndPhase])) return r.flatMap (fun vcr => (toDiagnosticModel vcr []).toArray) -/-- -info: ⚠️ [addPathCondition] Label clash detected for assume(0), using unique label assume(0)_1. -⚠️ [addPathCondition] Label clash detected for assume(0), using unique label assume(0)_2. -⚠️ [addPathCondition] Label clash detected for assume(0), using unique label assume(0)_3. -⚠️ [addPathCondition] Label clash detected for assume(0), using unique label assume(0)_1. -⚠️ [addPathCondition] Label clash detected for assume(0), using unique label assume(0)_2. ---- -info: #[] --/ +/-- info: #[] -/ #guard_msgs in #eval verifyPrelude diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_empty.expected b/StrataTest/Languages/Python/expected_laurel/test_class_empty.expected index aab04f3a0d..7f98693936 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_empty.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_empty.expected @@ -1,4 +1,4 @@ -test_class_empty.py(5, 4): ✅ pass - callElimAssert_requires_4 +test_class_empty.py(5, 4): ✅ pass - precondition test_class_empty.py(6, 4): ✅ pass - empty class instantiated DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_field_init.expected b/StrataTest/Languages/Python/expected_laurel/test_class_field_init.expected index 7cb1e2fc89..4757a64ef8 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_field_init.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_field_init.expected @@ -1,2 +1,4 @@ -DETAIL: 0 passed, 0 failed, 0 inconclusive +test_class_field_init.py(20, 4): ✔️ always true if reached - (CircularBuffer@__init__ requires) Type constraint of size +test_class_field_init.py(20, 4): ✔️ always true if reached - (CircularBuffer@__init__ requires) Type constraint of name +DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_field_use.expected b/StrataTest/Languages/Python/expected_laurel/test_class_field_use.expected index 0caaf75c9f..29a689ea2c 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_field_use.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_field_use.expected @@ -1,5 +1,6 @@ +test_class_field_use.py(13, 4): ✔️ always true if reached - (CircularBuffer@__init__ requires) Type constraint of n test_class_field_use.py(14, 4): ✔️ always true if reached - Check PMul exception test_class_field_use.py(14, 4): ✔️ always true if reached - assert(302) test_class_field_use.py(15, 4): ✔️ always true if reached - Doubling of buffer did not work -DETAIL: 3 passed, 0 failed, 0 inconclusive +DETAIL: 4 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_methods.expected b/StrataTest/Languages/Python/expected_laurel/test_class_methods.expected index 36c53a8361..55fbd90dca 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_methods.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_methods.expected @@ -1,11 +1,17 @@ -test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(471)_13 +test_class_methods.py(34, 4): ✔️ always true if reached - (Account@__init__ requires) Type constraint of owner +test_class_methods.py(34, 4): ✔️ always true if reached - (Account@__init__ requires) Type constraint of balance +test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(471)_37 test_class_methods.py(34, 4): ✔️ always true if reached - get_owner should return Alice -test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(564)_15 +test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(564)_40 test_class_methods.py(34, 4): ✔️ always true if reached - get_balance should return 100 -test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(678)_17 +test_class_methods.py(34, 4): ✔️ always true if reached - (Account@set_balance requires) Type constraint of amount +test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(678)_45 test_class_methods.py(34, 4): ✔️ always true if reached - set_balance should update balance +test_class_methods.py(34, 4): ✔️ always true if reached - (Origin_test_helper_procedure_Requires)req_name_is_foo +test_class_methods.py(34, 4): ✔️ always true if reached - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str +test_class_methods.py(34, 4): ✔️ always true if reached - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar test_class_methods.py(31, 4): ✔️ always true if reached - assert_name_is_foo test_class_methods.py(31, 4): ✔️ always true if reached - assert_opt_name_none_or_str test_class_methods.py(31, 4): ✔️ always true if reached - assert_opt_name_none_or_bar -DETAIL: 9 passed, 0 failed, 0 inconclusive +DETAIL: 15 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_mixed_init.expected b/StrataTest/Languages/Python/expected_laurel/test_class_mixed_init.expected index 766329f9a4..ee2f8ef831 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_mixed_init.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_mixed_init.expected @@ -1,4 +1,6 @@ +test_class_mixed_init.py(19, 0): ✔️ always true if reached - (WithInit@__init__ requires) Type constraint of x test_class_mixed_init.py(19, 0): ✔️ always true if reached - class with init +test_class_mixed_init.py(19, 0): ✔️ always true if reached - precondition test_class_mixed_init.py(19, 0): ❓ unknown - class with init -DETAIL: 1 passed, 0 failed, 1 inconclusive +DETAIL: 3 passed, 0 failed, 1 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_no_init.expected b/StrataTest/Languages/Python/expected_laurel/test_class_no_init.expected index 7228247375..a55c76cfe4 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_no_init.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_no_init.expected @@ -1,4 +1,4 @@ -test_class_no_init.py(5, 4): ✅ pass - callElimAssert_requires_4 +test_class_no_init.py(5, 4): ✅ pass - precondition test_class_no_init.py(6, 4): ❓ unknown - class without __init__ DETAIL: 1 passed, 0 failed, 1 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_no_init_multi_field.expected b/StrataTest/Languages/Python/expected_laurel/test_class_no_init_multi_field.expected index 3dbe40b3b6..13ba7f459b 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_no_init_multi_field.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_no_init_multi_field.expected @@ -1,4 +1,4 @@ -test_class_no_init_multi_field.py(7, 4): ✅ pass - callElimAssert_requires_4 +test_class_no_init_multi_field.py(7, 4): ✅ pass - precondition test_class_no_init_multi_field.py(8, 4): ✅ pass - class with multiple annotated fields no init DETAIL: 2 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_no_init_with_method.expected b/StrataTest/Languages/Python/expected_laurel/test_class_no_init_with_method.expected index 29b6682a29..93f5036c2d 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_no_init_with_method.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_no_init_with_method.expected @@ -1,5 +1,5 @@ test_class_no_init_with_method.py(4, 23): ❓ unknown - (WithMethod@get_x ensures) Return type constraint -test_class_no_init_with_method.py(8, 4): ✅ pass - callElimAssert_requires_4 +test_class_no_init_with_method.py(8, 4): ✅ pass - precondition test_class_no_init_with_method.py(9, 4): ✅ pass - class with method but no __init__ DETAIL: 2 passed, 0 failed, 1 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_with_methods.expected b/StrataTest/Languages/Python/expected_laurel/test_class_with_methods.expected index 1085e02e58..86d6f43ff8 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_with_methods.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_with_methods.expected @@ -1,9 +1,15 @@ -test_class_with_methods.py(32, 4): ✔️ always true if reached - main_assert(484)_12 +test_class_with_methods.py(32, 4): ✔️ always true if reached - (DataStore@__init__ requires) Type constraint of name +test_class_with_methods.py(32, 4): ✔️ always true if reached - (DataStore@add requires) Type constraint of amount +test_class_with_methods.py(32, 4): ✔️ always true if reached - (DataStore@add requires) Type constraint of amount +test_class_with_methods.py(32, 4): ✔️ always true if reached - main_assert(484)_39 test_class_with_methods.py(32, 4): ✔️ always true if reached - get_count should return 30 -test_class_with_methods.py(32, 4): ✔️ always true if reached - main_assert(569)_14 +test_class_with_methods.py(32, 4): ✔️ always true if reached - main_assert(569)_42 test_class_with_methods.py(32, 4): ✔️ always true if reached - get_name should return mystore +test_class_with_methods.py(32, 4): ✔️ always true if reached - (Origin_test_helper_procedure_Requires)req_name_is_foo +test_class_with_methods.py(32, 4): ✔️ always true if reached - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str +test_class_with_methods.py(32, 4): ✔️ always true if reached - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar test_class_with_methods.py(29, 4): ✔️ always true if reached - assert_name_is_foo test_class_with_methods.py(29, 4): ✔️ always true if reached - assert_opt_name_none_or_str test_class_with_methods.py(29, 4): ✔️ always true if reached - assert_opt_name_none_or_bar -DETAIL: 7 passed, 0 failed, 0 inconclusive +DETAIL: 13 passed, 0 failed, 0 inconclusive RESULT: Analysis success diff --git a/StrataTest/Languages/Python/expected_laurel/test_deep_inline.expected b/StrataTest/Languages/Python/expected_laurel/test_deep_inline.expected index eac560309d..2fbea7ae57 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_deep_inline.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_deep_inline.expected @@ -1,11 +1,15 @@ +test_deep_inline.py(21, 4): ✔️ always true if reached - (triple_apply requires) Type constraint of x +test_deep_inline.py(15, 4): ✔️ always true if reached - (double_inc requires) Type constraint of x +test_deep_inline.py(10, 4): ✔️ always true if reached - (inc requires) Type constraint of x test_deep_inline.py(6, 4): ✔️ always true if reached - Check PAdd exception -test_deep_inline.py(10, 4): ✔️ always true if reached - double_inc_assert(135)_35 +test_deep_inline.py(10, 4): ✔️ always true if reached - double_inc_assert(135)_76 test_deep_inline.py(10, 4): ✔️ always true if reached - Check PMul exception -test_deep_inline.py(15, 4): ✔️ always true if reached - triple_apply_assert(206)_17 +test_deep_inline.py(15, 4): ✔️ always true if reached - triple_apply_assert(206)_36 +test_deep_inline.py(15, 4): ✔️ always true if reached - (inc requires) Type constraint of x test_deep_inline.py(11, 4): ✔️ always true if reached - Check PAdd exception -test_deep_inline.py(15, 4): ✔️ always true if reached - triple_apply_assert(233)_18 -test_deep_inline.py(21, 4): ✔️ always true if reached - main_assert(279)_5 +test_deep_inline.py(15, 4): ✔️ always true if reached - triple_apply_assert(233)_39 +test_deep_inline.py(21, 4): ✔️ always true if reached - main_assert(279)_10 test_deep_inline.py(21, 4): ✔️ always true if reached - triple_apply(3) should be 9 test_deep_inline.py(21, 4): ✖️ always false if reached - triple_apply(3) should not be 10 -DETAIL: 8 passed, 1 failed, 0 inconclusive +DETAIL: 12 passed, 1 failed, 0 inconclusive RESULT: Failures found diff --git a/StrataTest/Languages/Python/expected_laurel/test_havoc_callee_after_hole_call.expected b/StrataTest/Languages/Python/expected_laurel/test_havoc_callee_after_hole_call.expected index 9a320c707c..85efc6288d 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_havoc_callee_after_hole_call.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_havoc_callee_after_hole_call.expected @@ -2,9 +2,10 @@ test_havoc_callee_after_hole_call.py(8, 0): ❓ unknown - expected unknown becau test_havoc_callee_after_hole_call.py(12, 0): ❓ unknown - expected unknown because xs should be havocked test_havoc_callee_after_hole_call.py(16, 0): ✔️ always true if reached - chained call: receiver not havocked (chained attribute is not a Name) test_havoc_callee_after_hole_call.py(20, 0): ✔️ always true if reached - unrelated variable: nothing should be havocked +test_havoc_callee_after_hole_call.py(22, 0): ✔️ always true if reached - (MyClass@__init__ requires) Type constraint of n test_havoc_callee_after_hole_call.py(25, 0): ✔️ always true if reached - composite arg: heap not havocked (out of scope) test_havoc_callee_after_hole_call.py(30, 0): ❓ unknown - expected unknown because argument locals should be havocked test_havoc_callee_after_hole_call.py(36, 0): ❓ unknown - assume_assume(1193)_calls_PIn_0 test_havoc_callee_after_hole_call.py(37, 4): ✔️ always true if reached - for-loop over unmodeled iterator should not crash -DETAIL: 4 passed, 0 failed, 4 inconclusive +DETAIL: 5 passed, 0 failed, 4 inconclusive RESULT: Inconclusive diff --git a/StrataTest/Languages/Python/expected_laurel/test_with_statement.expected b/StrataTest/Languages/Python/expected_laurel/test_with_statement.expected index c73e76d8cb..8948ea9e4e 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_with_statement.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_with_statement.expected @@ -1,9 +1,13 @@ +test_with_statement.py(16, 4): ✔️ always true if reached - (Resource@__init__ requires) Type constraint of n test_with_statement.py(17, 4): ✔️ always true if reached - assert(364) test_with_statement.py(20, 4): ✔️ always true if reached - assert(426) +test_with_statement.py(23, 4): ✔️ always true if reached - (Resource@__init__ requires) Type constraint of n test_with_statement.py(25, 8): ✔️ always true if reached - assert(525) test_with_statement.py(26, 8): ✔️ always true if reached - assert(558) +test_with_statement.py(29, 4): ✔️ always true if reached - (Resource@__init__ requires) Type constraint of n +test_with_statement.py(30, 4): ✔️ always true if reached - (Resource@__init__ requires) Type constraint of n test_with_statement.py(32, 21): ✔️ always true if reached - Check PAdd exception test_with_statement.py(32, 8): ✔️ always true if reached - assert(697) test_with_statement.py(33, 8): ✔️ always true if reached - assert(724) -DETAIL: 7 passed, 0 failed, 0 inconclusive +DETAIL: 11 passed, 0 failed, 0 inconclusive RESULT: Analysis success From d6bff500a85a9d48e812658e0dab282cf4af1998 Mon Sep 17 00:00:00 2001 From: keyboardDrummer-bot Date: Thu, 28 May 2026 15:56:07 +0000 Subject: [PATCH 039/115] Fix contract pass: make postcondition helpers functional The postcondition helper procedures (foo$post0, etc.) were generated with isFunctional := false, causing the transparency pass to create procedure versions that never assigned their output parameter. The interpreter then failed with 'assume condition did not reduce to bool' because the output variable was uninitialized. Since postcondition helpers only compute a boolean from their inputs, they can safely be marked isFunctional := true, matching the precondition helpers. --- Strata/Languages/Laurel/ContractPass.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Strata/Languages/Laurel/ContractPass.lean b/Strata/Languages/Laurel/ContractPass.lean index 8f063d5877..0ae17de08e 100644 --- a/Strata/Languages/Laurel/ContractPass.lean +++ b/Strata/Languages/Laurel/ContractPass.lean @@ -77,7 +77,7 @@ private def mkPostConditionProc (name : String) outputs := [⟨mkId "$result", { val := .TBool, source := none }⟩] preconditions := [] decreases := none - isFunctional := false + isFunctional := true body := .Transparent condition.condition } /-- Information about a procedure's contracts. -/ From 2cae5e0bc8ebdf0c497bc46e2ede3e92c2d05c12 Mon Sep 17 00:00:00 2001 From: keyboardDrummer-bot Date: Thu, 28 May 2026 16:29:25 +0000 Subject: [PATCH 040/115] Update golden-file expected outputs for changed label counters --- .../Python/expected_laurel/test_class_methods.expected | 6 +++--- .../expected_laurel/test_class_with_methods.expected | 4 ++-- .../Python/expected_laurel/test_deep_inline.expected | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_methods.expected b/StrataTest/Languages/Python/expected_laurel/test_class_methods.expected index 55fbd90dca..f0601e776e 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_methods.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_methods.expected @@ -1,11 +1,11 @@ test_class_methods.py(34, 4): ✔️ always true if reached - (Account@__init__ requires) Type constraint of owner test_class_methods.py(34, 4): ✔️ always true if reached - (Account@__init__ requires) Type constraint of balance -test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(471)_37 +test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(471)_32 test_class_methods.py(34, 4): ✔️ always true if reached - get_owner should return Alice -test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(564)_40 +test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(564)_35 test_class_methods.py(34, 4): ✔️ always true if reached - get_balance should return 100 test_class_methods.py(34, 4): ✔️ always true if reached - (Account@set_balance requires) Type constraint of amount -test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(678)_45 +test_class_methods.py(34, 4): ✔️ always true if reached - main_assert(678)_40 test_class_methods.py(34, 4): ✔️ always true if reached - set_balance should update balance test_class_methods.py(34, 4): ✔️ always true if reached - (Origin_test_helper_procedure_Requires)req_name_is_foo test_class_methods.py(34, 4): ✔️ always true if reached - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str diff --git a/StrataTest/Languages/Python/expected_laurel/test_class_with_methods.expected b/StrataTest/Languages/Python/expected_laurel/test_class_with_methods.expected index 86d6f43ff8..3f5ddaf665 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_class_with_methods.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_class_with_methods.expected @@ -1,9 +1,9 @@ test_class_with_methods.py(32, 4): ✔️ always true if reached - (DataStore@__init__ requires) Type constraint of name test_class_with_methods.py(32, 4): ✔️ always true if reached - (DataStore@add requires) Type constraint of amount test_class_with_methods.py(32, 4): ✔️ always true if reached - (DataStore@add requires) Type constraint of amount -test_class_with_methods.py(32, 4): ✔️ always true if reached - main_assert(484)_39 +test_class_with_methods.py(32, 4): ✔️ always true if reached - main_assert(484)_34 test_class_with_methods.py(32, 4): ✔️ always true if reached - get_count should return 30 -test_class_with_methods.py(32, 4): ✔️ always true if reached - main_assert(569)_42 +test_class_with_methods.py(32, 4): ✔️ always true if reached - main_assert(569)_37 test_class_with_methods.py(32, 4): ✔️ always true if reached - get_name should return mystore test_class_with_methods.py(32, 4): ✔️ always true if reached - (Origin_test_helper_procedure_Requires)req_name_is_foo test_class_with_methods.py(32, 4): ✔️ always true if reached - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str diff --git a/StrataTest/Languages/Python/expected_laurel/test_deep_inline.expected b/StrataTest/Languages/Python/expected_laurel/test_deep_inline.expected index 2fbea7ae57..bc3395d77d 100644 --- a/StrataTest/Languages/Python/expected_laurel/test_deep_inline.expected +++ b/StrataTest/Languages/Python/expected_laurel/test_deep_inline.expected @@ -2,13 +2,13 @@ test_deep_inline.py(21, 4): ✔️ always true if reached - (triple_apply requir test_deep_inline.py(15, 4): ✔️ always true if reached - (double_inc requires) Type constraint of x test_deep_inline.py(10, 4): ✔️ always true if reached - (inc requires) Type constraint of x test_deep_inline.py(6, 4): ✔️ always true if reached - Check PAdd exception -test_deep_inline.py(10, 4): ✔️ always true if reached - double_inc_assert(135)_76 +test_deep_inline.py(10, 4): ✔️ always true if reached - double_inc_assert(135)_54 test_deep_inline.py(10, 4): ✔️ always true if reached - Check PMul exception -test_deep_inline.py(15, 4): ✔️ always true if reached - triple_apply_assert(206)_36 +test_deep_inline.py(15, 4): ✔️ always true if reached - triple_apply_assert(206)_27 test_deep_inline.py(15, 4): ✔️ always true if reached - (inc requires) Type constraint of x test_deep_inline.py(11, 4): ✔️ always true if reached - Check PAdd exception -test_deep_inline.py(15, 4): ✔️ always true if reached - triple_apply_assert(233)_39 -test_deep_inline.py(21, 4): ✔️ always true if reached - main_assert(279)_10 +test_deep_inline.py(15, 4): ✔️ always true if reached - triple_apply_assert(233)_30 +test_deep_inline.py(21, 4): ✔️ always true if reached - main_assert(279)_9 test_deep_inline.py(21, 4): ✔️ always true if reached - triple_apply(3) should be 9 test_deep_inline.py(21, 4): ✖️ always false if reached - triple_apply(3) should not be 10 DETAIL: 12 passed, 1 failed, 0 inconclusive From 5c0a45850473168eddb2311ceeb42adb0318f6d5 Mon Sep 17 00:00:00 2001 From: keyboardDrummer-bot Date: Thu, 28 May 2026 17:06:41 +0000 Subject: [PATCH 041/115] Extract emitCoreDiagnostic helper to reduce repetition --- .../Languages/Laurel/LaurelToCoreTranslator.lean | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index cc33f2dae8..5d8831a37b 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -76,9 +76,12 @@ structure TranslateState where def emitDiagnostic (d : DiagnosticModel) : TranslateM Unit := modify fun s => { s with diagnostics := s.diagnostics ++ [d] } +/-- Emit a core diagnostic that flags the Core program as invalid. -/ +def emitCoreDiagnostic (d : DiagnosticModel) : TranslateM Unit := + modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ [d] } + private def invalidCoreType (source : Option FileRange) (reason : String) : TranslateM LMonoTy := do - modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ - [diagnosticFromSource source reason DiagnosticType.StrataBug] } + emitCoreDiagnostic (diagnosticFromSource source reason DiagnosticType.StrataBug) return .tcons s!"LaurelResolutionErrorPlaceholder" [] /- @@ -102,8 +105,7 @@ def translateType (ty : HighTypeMd) : TranslateM LMonoTy := do | some (.datatypeDefinition dt) => return .tcons dt.name.text [] | some (.datatypeConstructor typeName _) => return .tcons typeName.text [] | _ => do -- resolution should have already emitted a diagnostic - modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ - [diagnosticFromSource ty.source s!"UserDefined type could not be resolved to a composite or datatype" DiagnosticType.StrataBug] } + emitCoreDiagnostic (diagnosticFromSource ty.source s!"UserDefined type could not be resolved to a composite or datatype" DiagnosticType.StrataBug) return .tcons "Composite" [] | .TCore s => return .tcons s [] | .TReal => return LMonoTy.real @@ -135,7 +137,7 @@ private def freshId : TranslateM Nat := do /-- Throw a hard diagnostic error, aborting the current translation -/ def throwExprDiagnostic (d : DiagnosticModel): TranslateM Core.Expression.Expr := do emitDiagnostic d - modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ [d] } + emitCoreDiagnostic d let id ← freshId return LExpr.fvar () (⟨s!"DUMMY_VAR_{id}", ()⟩) none @@ -353,7 +355,7 @@ private def exprAsUnusedInit (expr : StmtExprMd) (md : Imperative.MetaData Core. def throwStmtDiagnostic (d : DiagnosticModel): TranslateM (List Core.Statement) := do emitDiagnostic d - modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ [d] } + emitCoreDiagnostic d return [] /-- @@ -502,7 +504,7 @@ def translateStmt (stmt : StmtExprMd) return [.exit "$body" md] | some _ => let d := md.toDiagnostic "Return statement with value should have been eliminated by EliminateValueReturns pass" DiagnosticType.StrataBug - modify fun s => { s with coreDiagnostics := s.coreDiagnostics ++ [d] } + emitCoreDiagnostic d return [.exit "$body" md] | .While cond invariants decreasesExpr body => let condExpr ← translateExpr cond From ff095226b68462f491d2babdd5c1565b76bfd451 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Mon, 1 Jun 2026 15:09:03 +0000 Subject: [PATCH 042/115] Code review --- .../Languages/Laurel/LaurelCompilationPipeline.lean | 1 - Strata/Languages/Laurel/LaurelToCoreTranslator.lean | 11 +---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index 2e84bd7902..0c0f12e5be 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -226,7 +226,6 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) let (coreProgramOption, translateState) := runTranslateM initState (translateLaurelToCore options program coreWithLaurelTypes) -- Because of the duplication between functions and procedures, this translation is liable to create duplicate diagnostics - -- User errors should be checked in an earlier phase, and all dumb translation errors are Strata bugs let mut allDiagnostics: List DiagnosticModel := passDiags ++ translateState.diagnostics.eraseDups; if translateState.coreDiagnostics.length > 0 && allDiagnostics.isEmpty then diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index 94fb99fbc8..456c2c7c4d 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -585,7 +585,7 @@ def translateProcedure (proc : Procedure) : TranslateM Core.Procedure := do let postconditions : ListMap Core.CoreLabel Core.Procedure.Check ← match proc.body with | .Opaque postconds _ _ | .Abstract postconds => - translateChecks postconds s!"postcondition{(bodyStmts.getD []).length}" bodyStmts.isNone + translateChecks postconds s!"postcondition" bodyStmts.isNone | _ => pure [] let body : List Core.Statement := [.block "$body" (bodyStmts.getD []) mdWithUnknownLoc] @@ -762,15 +762,6 @@ def translateLaurelToCore (options: LaurelTranslateOptions) (program : Program) body := body } mdWithUnknownLoc] - - -- Emit diagnostics for composite types with instance procedures. - for td in program.types do - if let .Composite ct := td then - for proc in ct.instanceProcedures do - emitDiagnostic $ diagnosticFromSource proc.name.source - s!"Instance procedure '{proc.name.text}' on composite type '{ct.name.text}' is not yet supported" - DiagnosticType.NotYetImplemented - pure { decls := coreDecls } end -- public section From afaf9a22908732b535b12eff52852f98c0da28ea Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Mon, 1 Jun 2026 15:12:20 +0000 Subject: [PATCH 043/115] Stop dropping source location from callee --- Strata/Languages/Laurel/TransparencyPass.lean | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Strata/Languages/Laurel/TransparencyPass.lean b/Strata/Languages/Laurel/TransparencyPass.lean index 8b30043007..c2ed44e5fb 100644 --- a/Strata/Languages/Laurel/TransparencyPass.lean +++ b/Strata/Languages/Laurel/TransparencyPass.lean @@ -60,14 +60,14 @@ def stripAssertAssume (expr : StmtExprMd) : StmtExprMd := Tester names also contain `..` but start with `is` after the separator. - `proof = true` → use safe selectors (strip `!` suffix) - `proof = false` → use unsafe selectors (add `!` suffix) -/ -private def adjustSelectorName (name : String) : String := +private def adjustSelectorName (name : Identifier) : Identifier := -- Only adjust destructor names (contain ".." but are not testers) - match name.splitOn ".." with + match name.text.splitOn ".." with | [_, suffix] => if suffix.startsWith "is" then name -- tester, leave unchanged else -- Unsafe: add trailing "!" if not already present - if name.endsWith "!" then name else name ++ "!" + if name.text.endsWith "!" then name else { text := name.text ++ "!", source := name.source } | _ => name -- not a destructor name, leave unchanged /-- Rewrite StaticCall callees to their `$asFunction` versions, @@ -80,7 +80,7 @@ private def rewriteCallsToFunctional (asFunctionNames : List String) (expr : Stm let funcCallee := { callee with text := callee.text ++ "$asFunction", uniqueId := none } ⟨.StaticCall funcCallee args, e.source⟩ else - let newName := adjustSelectorName callee.text + let newName := adjustSelectorName callee ⟨ .StaticCall newName args, e.source⟩ | .PrimitiveOp operator arguments _ => ⟨ .PrimitiveOp operator arguments true, e.source⟩ | _ => e) expr From 9f65463f8c3237d0861b58a65e7d6768df6307fd Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Mon, 1 Jun 2026 15:29:45 +0000 Subject: [PATCH 044/115] Code review --- Strata/Languages/Laurel/TransparencyPass.lean | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Strata/Languages/Laurel/TransparencyPass.lean b/Strata/Languages/Laurel/TransparencyPass.lean index c2ed44e5fb..633ec451da 100644 --- a/Strata/Languages/Laurel/TransparencyPass.lean +++ b/Strata/Languages/Laurel/TransparencyPass.lean @@ -108,16 +108,11 @@ private def mkFunctionCopy (asFunctionNames : List String) (proc : Procedure) : | x => x { proc with name := funcName, isFunctional := true, body := body, preconditions := [] } -/-- Check whether a function copy has a body (i.e. the procedure was transparent). -/ -private def functionHasBody (proc : Procedure) : Bool := - match proc.body with - | .Transparent _ => true - | _ => false - /-- Append a free postcondition to a procedure's body postconditions. For Opaque and Abstract bodies, the free condition is appended to the existing postcondition list. For Transparent bodies, the body is promoted - to Opaque so the free postcondition can be carried. -/ + to Opaque so the free postcondition can be carried. + This change in opaqueness is fine since the function copy now carries the transparent semantics. -/ private def addFreePostcondition (proc : Procedure) (freePost : StmtExprMd) : Procedure := match freePost.val with | .LiteralBool true => proc -- trivial, skip From e1caee4b99b487c4884c82a8e1476c9030fed8ae Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Mon, 1 Jun 2026 15:34:09 +0000 Subject: [PATCH 045/115] Trigger CI From 3e76abcc83eeb00e012cec0121244150a9f4c1db Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Mon, 1 Jun 2026 15:41:26 +0000 Subject: [PATCH 046/115] Fix nit --- Strata/Languages/Laurel/LaurelCompilationPipeline.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index 0c0f12e5be..edac035336 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -228,7 +228,7 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) -- Because of the duplication between functions and procedures, this translation is liable to create duplicate diagnostics let mut allDiagnostics: List DiagnosticModel := passDiags ++ translateState.diagnostics.eraseDups; - if translateState.coreDiagnostics.length > 0 && allDiagnostics.isEmpty then + if !translateState.coreDiagnostics.isEmpty && allDiagnostics.isEmpty then allDiagnostics := allDiagnostics ++ translateState.coreDiagnostics if coreProgramOption.isSome then From 6f0d365b5509d2d5bfbc62847faaf89324fbc409 Mon Sep 17 00:00:00 2001 From: keyboardDrummer-bot Date: Mon, 1 Jun 2026 15:53:35 +0000 Subject: [PATCH 047/115] Use HashSet for asFunctionNames and add isDestructorName/isTesterName to TypeFactory - Change asFunctionNames from List String to Std.HashSet String for O(1) lookup in rewriteCallsToFunctional (addresses Josh's efficiency comment) - Add destructorSeparator, isSelectorName, isTesterName, and isDestructorName to TypeFactory.lean so naming conventions are defined in one place - Use Lambda.isDestructorName/isTesterName in TransparencyPass.adjustSelectorName to avoid silent breakage if Core naming conventions change --- Strata/DL/Lambda/TypeFactory.lean | 18 +++++++++++++++- Strata/Languages/Laurel/TransparencyPass.lean | 21 +++++++++---------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/Strata/DL/Lambda/TypeFactory.lean b/Strata/DL/Lambda/TypeFactory.lean index 4b859ca12b..cf45cd39e6 100644 --- a/Strata/DL/Lambda/TypeFactory.lean +++ b/Strata/DL/Lambda/TypeFactory.lean @@ -509,10 +509,26 @@ def destructorConcreteEval {T: LExprParams} [BEq T.Identifier] (d: LDatatype T.I then a[idx]? else none) | _ => none -def destructorFuncName {IDMeta} (d: LDatatype IDMeta) (name: Identifier IDMeta) := d.name ++ ".." ++ name.name +def destructorSeparator := ".." + +def destructorFuncName {IDMeta} (d: LDatatype IDMeta) (name: Identifier IDMeta) := d.name ++ destructorSeparator ++ name.name def unsafeDestructorSuffix := "!" +/-- Check whether a name is a destructor or tester name (contains the `..` separator). -/ +def isSelectorName (name : String) : Bool := + (name.splitOn destructorSeparator).length == 2 + +/-- Check whether a name is a tester name (e.g. `IntList..isCons`). -/ +def isTesterName (name : String) : Bool := + match name.splitOn destructorSeparator with + | [_, suffix] => suffix.startsWith "is" + | _ => false + +/-- Check whether a name is a destructor name (e.g. `IntList..head` or `IntList..head!`). -/ +def isDestructorName (name : String) : Bool := + isSelectorName name && !isTesterName name + def unsafeDestructorFuncName {IDMeta} (d: LDatatype IDMeta) (name: Identifier IDMeta) := destructorFuncName d name ++ unsafeDestructorSuffix diff --git a/Strata/Languages/Laurel/TransparencyPass.lean b/Strata/Languages/Laurel/TransparencyPass.lean index 633ec451da..efa801ad07 100644 --- a/Strata/Languages/Laurel/TransparencyPass.lean +++ b/Strata/Languages/Laurel/TransparencyPass.lean @@ -8,6 +8,7 @@ module public import Strata.Languages.Laurel.MapStmtExpr public import Strata.Languages.Laurel.Laurel import Strata.Languages.Laurel.Grammar.AbstractToConcreteTreeTranslator +import Strata.DL.Lambda.TypeFactory /-! ## Transparency Pass @@ -61,18 +62,16 @@ def stripAssertAssume (expr : StmtExprMd) : StmtExprMd := - `proof = true` → use safe selectors (strip `!` suffix) - `proof = false` → use unsafe selectors (add `!` suffix) -/ private def adjustSelectorName (name : Identifier) : Identifier := - -- Only adjust destructor names (contain ".." but are not testers) - match name.text.splitOn ".." with - | [_, suffix] => - if suffix.startsWith "is" then name -- tester, leave unchanged - else - -- Unsafe: add trailing "!" if not already present - if name.text.endsWith "!" then name else { text := name.text ++ "!", source := name.source } - | _ => name -- not a destructor name, leave unchanged + if Lambda.isTesterName name.text then name + else if Lambda.isDestructorName name.text then + -- Unsafe: add trailing "!" if not already present + if name.text.endsWith Lambda.unsafeDestructorSuffix then name + else { text := name.text ++ Lambda.unsafeDestructorSuffix, source := name.source } + else name /-- Rewrite StaticCall callees to their `$asFunction` versions, but only for procedures whose names appear in `nonExternalNames`. -/ -private def rewriteCallsToFunctional (asFunctionNames : List String) (expr : StmtExprMd) : StmtExprMd := +private def rewriteCallsToFunctional (asFunctionNames : Std.HashSet String) (expr : StmtExprMd) : StmtExprMd := mapStmtExpr (fun e => match e.val with | .StaticCall callee args => @@ -100,7 +99,7 @@ private def mkFreePostcondition (proc : Procedure) : StmtExprMd := /-- Create the function copy of a procedure (suffixed `$asFunction`). If the procedure is transparent, include a functional body. Otherwise the function is opaque. -/ -private def mkFunctionCopy (asFunctionNames : List String) (proc : Procedure) : Procedure := +private def mkFunctionCopy (asFunctionNames : Std.HashSet String) (proc : Procedure) : Procedure := let funcName := { proc.name with text := proc.name.text ++ "$asFunction", uniqueId := none } let body := match proc.body with | .Transparent b => .Transparent (rewriteCallsToFunctional asFunctionNames (stripAssertAssume b)) @@ -140,7 +139,7 @@ def transparencyPass (program : Program) : UnorderedCoreWithLaurelTypes := -- Skip functions until we introduce a contract pass, -- which enables lifting procedure calls from contracts p.isFunctional) - let asFunctionNames := notSkipped.map (fun p => p.name.text) + let asFunctionNames : Std.HashSet String := notSkipped.foldl (fun s p => s.insert p.name.text) {} let asFunctions := notSkipped.map (mkFunctionCopy asFunctionNames) -- External procedures get a plain function copy (they have no $asFunction version) From 1e9710b8ae15977637f743d61943e1782eb0b8b5 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Mon, 1 Jun 2026 16:23:18 +0000 Subject: [PATCH 048/115] Fix warning --- Strata/Languages/Laurel/LaurelCompilationPipeline.lean | 2 +- Strata/Languages/Laurel/LaurelToCoreTranslator.lean | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index edac035336..a363970b28 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -224,7 +224,7 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) emit "CoreWithLaurelTypes" "core.st" coreWithLaurelTypes let initState : TranslateState := { model := model, overflowChecks := options.overflowChecks } let (coreProgramOption, translateState) := - runTranslateM initState (translateLaurelToCore options program coreWithLaurelTypes) + runTranslateM initState (translateLaurelToCore options coreWithLaurelTypes) -- Because of the duplication between functions and procedures, this translation is liable to create duplicate diagnostics let mut allDiagnostics: List DiagnosticModel := passDiags ++ translateState.diagnostics.eraseDups; diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index 46646c22e1..3af1cc24d8 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -727,7 +727,7 @@ abbrev TranslateResult := (Option Core.Program) × (List DiagnosticModel) Translate a `CoreWithLaurelTypes` program to a `Core.Program`. The `program` parameter is the lowered Laurel program, used for type definitions. -/ -def translateLaurelToCore (options: LaurelTranslateOptions) (program : Program) (ordered : CoreWithLaurelTypes): TranslateM Core.Program := do +def translateLaurelToCore (options: LaurelTranslateOptions) (ordered : CoreWithLaurelTypes): TranslateM Core.Program := do let coreDecls ← ordered.decls.flatMapM fun | .funcs funcs isRecursive => do From 20225bd10035c175b8a13fac1b6e3873eacc3a3d Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 2 Jun 2026 11:58:33 +0000 Subject: [PATCH 049/115] Trigger CI From b52f50b693de846ee614d85a08523a37f8894d3b Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 2 Jun 2026 12:21:06 +0000 Subject: [PATCH 050/115] Trigger CI From b6f0f64cad09aa255d57d1331f813140489cd1e3 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 2 Jun 2026 14:15:54 +0000 Subject: [PATCH 051/115] Don't generate intermediate files --- .../Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean | 2 +- .../Laurel/Examples/Fundamentals/T8_Postconditions.lean | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean index ad8387e6d0..6c3102032b 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean @@ -149,7 +149,7 @@ procedure addProcCaller(): int " #guard_msgs (error, drop all) in -#eval! testInputWithOffset "NestedImpureStatements" program 14 processLaurelFileKeepIntermediates +#eval! testInputWithOffset "NestedImpureStatements" program 14 processLaurelFile end Laurel diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean index bb3f492f0d..c83488e72a 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean @@ -40,4 +40,4 @@ procedure invalidPostcondition(x: int) " #guard_msgs (drop info, error) in -#eval testInputWithOffset "Postconditions" program 14 processLaurelFileKeepIntermediates +#eval testInputWithOffset "Postconditions" program 14 processLaurelFile From 6ee3c7eab0763a69d75c5ffd3931f5637c1b4a95 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 2 Jun 2026 14:21:02 +0000 Subject: [PATCH 052/115] Exclude hidden files from the lake cache hash --- .github/actions/restore-lake-cache/action.yml | 6 +++--- .github/actions/save-lake-cache/action.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/actions/restore-lake-cache/action.yml b/.github/actions/restore-lake-cache/action.yml index 9151866cfd..3f156fa761 100644 --- a/.github/actions/restore-lake-cache/action.yml +++ b/.github/actions/restore-lake-cache/action.yml @@ -66,9 +66,9 @@ runs: uses: actions/cache/restore@v5 with: path: ${{ inputs.path }} - key: ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}-${{ hashFiles('**/*.st') }}-${{ github.sha }} + key: ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}-${{ hashFiles('[^.]*/**/*.st', '*.st') }}-${{ github.sha }} restore-keys: | - ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}-${{ hashFiles('**/*.st') }} + ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}-${{ hashFiles('[^.]*/**/*.st', '*.st') }} ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }} ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }} fail-on-cache-miss: ${{ inputs.fail-on-cache-miss }} @@ -78,5 +78,5 @@ runs: uses: actions/cache/restore@v5 with: path: ${{ inputs.path }} - key: ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}-${{ hashFiles('**/*.st') }}-${{ github.sha }} + key: ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}-${{ hashFiles('[^.]*/**/*.st', '*.st') }}-${{ github.sha }} fail-on-cache-miss: ${{ inputs.fail-on-cache-miss }} diff --git a/.github/actions/save-lake-cache/action.yml b/.github/actions/save-lake-cache/action.yml index 5754371b87..e4834362e9 100644 --- a/.github/actions/save-lake-cache/action.yml +++ b/.github/actions/save-lake-cache/action.yml @@ -32,4 +32,4 @@ runs: uses: actions/cache/save@v5 with: path: ${{ inputs.path }} - key: ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}-${{ hashFiles('**/*.st') }}-${{ github.sha }} + key: ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}-${{ hashFiles('[^.]*/**/*.st', '*.st') }}-${{ github.sha }} From 4b44fbffdb39309db59a66902d34d1d5f8aea73c Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 2 Jun 2026 14:21:02 +0000 Subject: [PATCH 053/115] Exclude hidden files from the lake cache hash --- .github/actions/restore-lake-cache/action.yml | 6 +++--- .github/actions/save-lake-cache/action.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/actions/restore-lake-cache/action.yml b/.github/actions/restore-lake-cache/action.yml index 9151866cfd..3f156fa761 100644 --- a/.github/actions/restore-lake-cache/action.yml +++ b/.github/actions/restore-lake-cache/action.yml @@ -66,9 +66,9 @@ runs: uses: actions/cache/restore@v5 with: path: ${{ inputs.path }} - key: ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}-${{ hashFiles('**/*.st') }}-${{ github.sha }} + key: ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}-${{ hashFiles('[^.]*/**/*.st', '*.st') }}-${{ github.sha }} restore-keys: | - ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}-${{ hashFiles('**/*.st') }} + ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}-${{ hashFiles('[^.]*/**/*.st', '*.st') }} ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }} ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }} fail-on-cache-miss: ${{ inputs.fail-on-cache-miss }} @@ -78,5 +78,5 @@ runs: uses: actions/cache/restore@v5 with: path: ${{ inputs.path }} - key: ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}-${{ hashFiles('**/*.st') }}-${{ github.sha }} + key: ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}-${{ hashFiles('[^.]*/**/*.st', '*.st') }}-${{ github.sha }} fail-on-cache-miss: ${{ inputs.fail-on-cache-miss }} diff --git a/.github/actions/save-lake-cache/action.yml b/.github/actions/save-lake-cache/action.yml index 5754371b87..e4834362e9 100644 --- a/.github/actions/save-lake-cache/action.yml +++ b/.github/actions/save-lake-cache/action.yml @@ -32,4 +32,4 @@ runs: uses: actions/cache/save@v5 with: path: ${{ inputs.path }} - key: ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}-${{ hashFiles('**/*.st') }}-${{ github.sha }} + key: ${{ inputs.key-prefix }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('lean-toolchain') }}-${{ hashFiles('lake-manifest.json') }}-${{ hashFiles('[^.]*/**/*.st', '*.st') }}-${{ github.sha }} From 1c5a9e1720531b938bf140b3bf56e03276fc1de2 Mon Sep 17 00:00:00 2001 From: keyboardDrummer-bot Date: Tue, 2 Jun 2026 20:00:27 +0000 Subject: [PATCH 054/115] Add polymorphism comment and recursive transparent procedure test - Add comment on LTy.forAll [] ty explaining the empty type-variable list is valid because Laurel does not currently support polymorphism (requested by @joscoh). - Add T20_RecursiveTransparent.lean with test programs for recursive transparent procedures on both a datatype (listSum over IntList) and a number (factorial). The test eval is currently commented out due to a bug where the generated $asFunction is not placed in a recFuncBlock. --- .../Laurel/LaurelToCoreTranslator.lean | 2 + .../T20_RecursiveTransparent.lean | 56 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 StrataTest/Languages/Laurel/Examples/Fundamentals/T20_RecursiveTransparent.lean diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index 3af1cc24d8..907a1f4c1e 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -351,6 +351,8 @@ private def exprAsUnusedInit (expr : StmtExprMd) (md : Imperative.MetaData Core. let model := (← get).model let ident : Core.CoreIdent := ⟨s!"$unused_{id}", ()⟩ let ty ← translateType (computeExprType model expr) + -- The empty type-variable list is valid because Laurel does not currently + -- support polymorphism. If polymorphism is added, this will need updating. let coreType := LTy.forAll [] ty return [Core.Statement.init ident coreType (.det coreExpr) md] diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_RecursiveTransparent.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_RecursiveTransparent.lean new file mode 100644 index 0000000000..8ee515d967 --- /dev/null +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_RecursiveTransparent.lean @@ -0,0 +1,56 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import StrataTest.Util.TestDiagnostics +import StrataTest.Languages.Laurel.TestExamples + +open StrataTest.Util + +namespace Strata +namespace Laurel + +/- +Recursive transparent procedures. The transparency pass produces a recursive +function (`$asFunction`) for each transparent procedure and ties them via a +free postcondition. +-/ +def recursiveTransparentProgram := r" +// Recursion on a datatype +datatype IntList { + Nil(), + Cons(head: int, tail: IntList) +} + +procedure listSum(xs: IntList): int +{ + if IntList..isNil(xs) then 0 + else IntList..head!(xs) + listSum(IntList..tail!(xs)) +}; + +procedure testListSum() opaque { + var xs: IntList := Cons(1, Cons(2, Cons(3, Nil()))); + assert listSum(xs) == 6 +}; + +// Recursion on a number +procedure factorial(n: int): int +{ + if n == 0 then 1 + else n * factorial(n - 1) +}; + +procedure testFactorial() opaque { + assert factorial(0) == 1; + assert factorial(3) == 6 +}; +" + +-- TODO: This test currently does not pass due to a bug where the generated +-- $asFunction is not placed in a recFuncBlock, causing a Core type checking error. +-- #guard_msgs(drop info, error) in +-- #eval testInputWithOffset "RecursiveTransparent" recursiveTransparentProgram 14 processLaurelFile + +end Laurel From 81b558c356036227872885445bd38e8abed5146e Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 2 Jun 2026 22:38:54 +0000 Subject: [PATCH 055/115] Fixes --- .../Laurel/CoreGroupingAndOrdering.lean | 2 +- .../T20_RecursiveTransparent.lean | 23 ++++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean b/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean index 77b51d869f..58489e0a05 100644 --- a/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean +++ b/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean @@ -53,7 +53,7 @@ def collectStaticCallNames (expr : StmtExprMd) : List String := match val with | .StaticCall callee args => callee.text :: args.flatMap (fun a => collectStaticCallNames a) - | .PrimitiveOp _ args => args.flatMap (fun a => collectStaticCallNames a) + | .PrimitiveOp _ args _ => args.flatMap (fun a => collectStaticCallNames a) | .IfThenElse cond t e => collectStaticCallNames cond ++ collectStaticCallNames t ++ diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_RecursiveTransparent.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_RecursiveTransparent.lean index 8ee515d967..32c6e9d591 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_RecursiveTransparent.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_RecursiveTransparent.lean @@ -35,17 +35,18 @@ procedure testListSum() opaque { assert listSum(xs) == 6 }; -// Recursion on a number -procedure factorial(n: int): int -{ - if n == 0 then 1 - else n * factorial(n - 1) -}; - -procedure testFactorial() opaque { - assert factorial(0) == 1; - assert factorial(3) == 6 -}; +// Recursing on numbers is not possible yet because Core functions +// do not support that. +//procedure factorial(n: int): int +//{ +// if n == 0 then 1 +// else n * factorial(n - 1) +//}; + +//procedure testFactorial() opaque { +// assert factorial(0) == 1; +// assert factorial(3) == 6 +//}; " -- TODO: This test currently does not pass due to a bug where the generated From ac0fdb1d7cf184b09b065a0ad5c7e9bf0a28ab08 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Wed, 3 Jun 2026 07:43:08 +0000 Subject: [PATCH 056/115] Update recursive test to use procedures --- ...siveFunction.lean => T18_RecursiveProcedure.lean} | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename StrataTest/Languages/Laurel/Examples/Fundamentals/{T18_RecursiveFunction.lean => T18_RecursiveProcedure.lean} (80%) diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T18_RecursiveFunction.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T18_RecursiveProcedure.lean similarity index 80% rename from StrataTest/Languages/Laurel/Examples/Fundamentals/T18_RecursiveFunction.lean rename to StrataTest/Languages/Laurel/Examples/Fundamentals/T18_RecursiveProcedure.lean index 7257ec2df3..ee47324ad5 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T18_RecursiveFunction.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T18_RecursiveProcedure.lean @@ -22,9 +22,9 @@ datatype IntList { Cons(head: int, tail: IntList) } -function listLen(xs: IntList): int +procedure listLen(xs: IntList): int { - if IntList..isNil(xs) then 0 + return if IntList..isNil(xs) then 0 else 1 + listLen(IntList..tail!(xs)) }; @@ -36,15 +36,15 @@ procedure testListLen() }; // Mutual recursion -function listLenEven(xs: IntList): bool +procedure listLenEven(xs: IntList): bool { - if IntList..isNil(xs) then true + return if IntList..isNil(xs) then true else listLenOdd(IntList..tail!(xs)) }; -function listLenOdd(xs: IntList): bool +procedure listLenOdd(xs: IntList): bool { - if IntList..isNil(xs) then false + return if IntList..isNil(xs) then false else listLenEven(IntList..tail!(xs)) }; From cf29a09676bf2c886f093b03af431f31a59a3ebf Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Wed, 3 Jun 2026 07:46:00 +0000 Subject: [PATCH 057/115] Delete recursive transparent --- .../T20_RecursiveTransparent.lean | 57 ------------------- 1 file changed, 57 deletions(-) delete mode 100644 StrataTest/Languages/Laurel/Examples/Fundamentals/T20_RecursiveTransparent.lean diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_RecursiveTransparent.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_RecursiveTransparent.lean deleted file mode 100644 index 32c6e9d591..0000000000 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_RecursiveTransparent.lean +++ /dev/null @@ -1,57 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ - -import StrataTest.Util.TestDiagnostics -import StrataTest.Languages.Laurel.TestExamples - -open StrataTest.Util - -namespace Strata -namespace Laurel - -/- -Recursive transparent procedures. The transparency pass produces a recursive -function (`$asFunction`) for each transparent procedure and ties them via a -free postcondition. --/ -def recursiveTransparentProgram := r" -// Recursion on a datatype -datatype IntList { - Nil(), - Cons(head: int, tail: IntList) -} - -procedure listSum(xs: IntList): int -{ - if IntList..isNil(xs) then 0 - else IntList..head!(xs) + listSum(IntList..tail!(xs)) -}; - -procedure testListSum() opaque { - var xs: IntList := Cons(1, Cons(2, Cons(3, Nil()))); - assert listSum(xs) == 6 -}; - -// Recursing on numbers is not possible yet because Core functions -// do not support that. -//procedure factorial(n: int): int -//{ -// if n == 0 then 1 -// else n * factorial(n - 1) -//}; - -//procedure testFactorial() opaque { -// assert factorial(0) == 1; -// assert factorial(3) == 6 -//}; -" - --- TODO: This test currently does not pass due to a bug where the generated --- $asFunction is not placed in a recFuncBlock, causing a Core type checking error. --- #guard_msgs(drop info, error) in --- #eval testInputWithOffset "RecursiveTransparent" recursiveTransparentProgram 14 processLaurelFile - -end Laurel From 3dc9527ff32b7165477065e894f898ea3978564f Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Wed, 3 Jun 2026 15:07:54 +0000 Subject: [PATCH 058/115] Add a test-case --- .../Laurel/Examples/Fundamentals/T20_TransparentBody.lean | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean index a1e82883de..df1e9c81dd 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean @@ -19,8 +19,12 @@ procedure transparentBody(): int 3 }; -procedure transparentProcedureCaller() opaque { - var x: int := transparentBody(); +procedure tranparentCaller(): int { + transparentBody() +}; + +procedure transparentCallerCaller() opaque { + var x: int := tranparentCaller(); assert x == 3 }; " From 70cb26e0ccbfdf336399333d0c1a52e0876e3b22 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 4 Jun 2026 13:26:43 +0000 Subject: [PATCH 059/115] Fixes --- Strata/Languages/Laurel/LaurelAST.lean | 3 ++ .../Laurel/LaurelCompilationPipeline.lean | 4 --- Strata/Languages/Laurel/Resolution.lean | 36 ++++++++++++++++++- .../Fundamentals/T20_TransparentBody.lean | 9 +++-- .../Fundamentals/T3_ControlFlowError2.lean | 7 ++-- 5 files changed, 49 insertions(+), 10 deletions(-) diff --git a/Strata/Languages/Laurel/LaurelAST.lean b/Strata/Languages/Laurel/LaurelAST.lean index 13882a5594..33f45b99fc 100644 --- a/Strata/Languages/Laurel/LaurelAST.lean +++ b/Strata/Languages/Laurel/LaurelAST.lean @@ -564,6 +564,9 @@ structure ConstrainedType where structure DatatypeConstructor where name : Identifier args : List Parameter + /-- Identifier for the auto-generated tester function (e.g. `IntList..isNil`). + Populated with a `uniqueId` during resolution. -/ + testerName : Identifier := mkId "" /-- A Laurel datatype definition with optional type parameters. Zero constructors produces an opaque (abstract) type in Core. diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index 4d0a27edeb..97266ab1d7 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -6,7 +6,6 @@ module public import Strata.Languages.Laurel.LaurelToCoreTranslator -import Strata.Languages.Laurel.DatatypeTesters import Strata.Languages.Laurel.DesugarShortCircuit import Strata.Languages.Laurel.EliminateReturnsInExpression import Strata.Languages.Laurel.EliminateReturnStatements @@ -174,9 +173,6 @@ private def runLaurelPasses (options : LaurelTranslateOptions) types := coreDefinitionsForLaurel.types ++ program.types } - -- Generate external tester functions for datatype constructors - let program := generateDatatypeTesters program - -- Step 0: the input program before any passes emit "Initial" "laurel.st" program diff --git a/Strata/Languages/Laurel/Resolution.lean b/Strata/Languages/Laurel/Resolution.lean index 53d984c4fa..311ceab130 100644 --- a/Strata/Languages/Laurel/Resolution.lean +++ b/Strata/Languages/Laurel/Resolution.lean @@ -675,7 +675,12 @@ def resolveTypeDefinition (td : TypeDefinition) : ResolveM TypeDefinition := do -- parameter's own name should stay unqualified. let destructorId := { p.name with uniqueId := resolved.uniqueId } return ⟨ destructorId, ty' ⟩ - return { name := ctorName', args := args' : DatatypeConstructor } + -- Resolve the tester name so its uniqueId is set. + let testerResolved ← resolveRef (dt.testerName ctor) + let testerName' := { ctor.testerName with + text := testerResolved.text + uniqueId := testerResolved.uniqueId } + return { name := ctorName', args := args', testerName := testerName' : DatatypeConstructor } return .Datatype { name := dtName', typeArgs := dt.typeArgs, constructors := ctors' } | .Alias ta => let target' ← resolveHighType ta.target @@ -691,6 +696,28 @@ def resolveConstant (c : Constant) : ResolveM Constant := do /-! ## Phase 2: Build refToDef map from the resolved program -/ +/-- Generate a virtual tester procedure for a single constructor of a datatype. + The tester takes a single argument of the datatype's type and returns `bool`. + Used during resolution to synthesize the scope entry for tester calls + (e.g. `IntList..isNil(x)`) without requiring a separate AST pass. -/ +private def mkTesterProcedure (dt : DatatypeDefinition) (ctor : DatatypeConstructor) : Procedure := + let tName := dt.testerName ctor + let inputParam : Parameter := { + name := mkId "value" + type := { val := .UserDefined dt.name, source := none } + } + let outputParam : Parameter := { + name := mkId "$result" + type := { val := .TBool, source := none } + } + { name := mkId tName + inputs := [inputParam] + outputs := [outputParam] + preconditions := [] + decreases := none + isFunctional := true + body := .External } + /-- Insert a definition into the refToDef map using the ID already on the identifier. -/ private def register (map : Std.HashMap Nat ResolvedNode) (iden : Identifier) (node : ResolvedNode) : Std.HashMap Nat ResolvedNode := @@ -827,6 +854,9 @@ private def collectTypeDefinition (map : Std.HashMap Nat ResolvedNode) (td : Typ let map := register map dt.name (.datatypeDefinition dt) dt.constructors.foldl (fun map ctor => let map := register map ctor.name (.datatypeConstructor dt.name ctor) + -- Register the tester function in the refToDef map. + let testerProc := mkTesterProcedure dt ctor + let map := register map ctor.testerName (.staticProcedure testerProc) ctor.args.foldl (fun map p => -- The constructor parameter's `uniqueId` (set by `resolveTypeDefinition`) -- is the shared uniqueId of the safe/unsafe destructor scope entries, @@ -885,6 +915,10 @@ private def preRegisterTopLevel (program : Program) : ResolveM Unit := do let _ ← defineNameCheckDup dt.name (.datatypeDefinition dt) for ctor in dt.constructors do let _ ← defineNameCheckDup ctor.name (.datatypeConstructor dt.name ctor) + -- Register the tester function (e.g. `IntList..isNil`) as a static procedure. + let testerProc := mkTesterProcedure dt ctor + let _ ← defineNameCheckDup (mkId (dt.testerName ctor)) + (.staticProcedure testerProc) (some (dt.testerName ctor)) for p in ctor.args do -- Same chaining trick for the safe and unsafe destructor names: both -- point to the same uniqueId so `IntList..head` and `IntList..head!` diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean index 90fc30c535..00fefd979e 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean @@ -3,9 +3,12 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module -import StrataTest.Util.TestDiagnostics -import StrataTest.Languages.Laurel.TestExamples +meta import all StrataTest.Util.TestDiagnostics +meta import all StrataTest.Languages.Laurel.TestExamples + +meta section open StrataTest.Util @@ -20,7 +23,7 @@ procedure transparentBody(): int }; procedure tranparentCaller(): int { - transparentBody() + return transparentBody() }; procedure transparentCallerCaller() opaque { diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError2.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError2.lean index f63e3410ce..cb77d6dc3f 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError2.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError2.lean @@ -3,9 +3,12 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module -import StrataTest.Util.TestDiagnostics -import StrataTest.Languages.Laurel.TestExamples +meta import all StrataTest.Util.TestDiagnostics +meta import all StrataTest.Languages.Laurel.TestExamples + +meta section open StrataTest.Util open Strata From 17c1d8473159e5e0a412743337b3e1ca359e51f8 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 4 Jun 2026 13:28:39 +0000 Subject: [PATCH 060/115] Fix test after merge --- .../Laurel/Examples/Fundamentals/T20_TransparentBody.lean | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean index df1e9c81dd..9f3e6e65a0 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T20_TransparentBody.lean @@ -3,9 +3,12 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ +module -import StrataTest.Util.TestDiagnostics -import StrataTest.Languages.Laurel.TestExamples +meta import all StrataTest.Util.TestDiagnostics +meta import all StrataTest.Languages.Laurel.TestExamples + +meta section open StrataTest.Util From 68cec09bafd4eeafe936eaf703f12c6dcbaebaf8 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 4 Jun 2026 13:55:18 +0000 Subject: [PATCH 061/115] Fix test expectation --- StrataBoole/StrataBooleTest/deterministic.lean | 4 ---- 1 file changed, 4 deletions(-) diff --git a/StrataBoole/StrataBooleTest/deterministic.lean b/StrataBoole/StrataBooleTest/deterministic.lean index a5f1a55315..e38978e6b8 100644 --- a/StrataBoole/StrataBooleTest/deterministic.lean +++ b/StrataBoole/StrataBooleTest/deterministic.lean @@ -46,10 +46,6 @@ procedure Check(x1:int, x2:int) returns () /-- info: -Obligation: Foo_ensures_0_256 -Property: assert -Result: ✅ pass - Obligation: assert_1_562 Property: assert Result: ✅ pass From dbfcb95410880fe056fafffa1f442683e7d08c28 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 4 Jun 2026 13:57:26 +0000 Subject: [PATCH 062/115] Remove unused module --- Strata/Languages/Laurel/DatatypeTesters.lean | 54 ------------------- .../StrataBooleTest/deterministic.lean | 4 -- 2 files changed, 58 deletions(-) delete mode 100644 Strata/Languages/Laurel/DatatypeTesters.lean diff --git a/Strata/Languages/Laurel/DatatypeTesters.lean b/Strata/Languages/Laurel/DatatypeTesters.lean deleted file mode 100644 index 3963391079..0000000000 --- a/Strata/Languages/Laurel/DatatypeTesters.lean +++ /dev/null @@ -1,54 +0,0 @@ -/- - Copyright Strata Contributors - - SPDX-License-Identifier: Apache-2.0 OR MIT --/ -module - -public import Strata.Languages.Laurel.LaurelAST - -/-! -## Datatype Tester Generation - -For each constructor of a datatype, generate an external testing function. -The tester function takes a single argument of the datatype's type and returns -`bool`. Its name is determined by `DatatypeDefinition.testerName`. - -This pass runs at the start of the Laurel pipeline, before resolution, so that -the tester functions are available as normal static procedures. --/ - -namespace Strata.Laurel - -public section - -/-- Generate an external tester function for a single constructor of a datatype. -/ -private def mkTesterFunction (dt : DatatypeDefinition) (ctor : DatatypeConstructor) : Procedure := - let testerName := dt.testerName ctor - let inputParam : Parameter := { - name := mkId "value" - type := { val := .UserDefined dt.name, source := none } - } - let outputParam : Parameter := { - name := mkId "$result" - type := { val := .TBool, source := none } - } - { name := mkId testerName - inputs := [inputParam] - outputs := [outputParam] - preconditions := [] - decreases := none - isFunctional := true - body := .External } - -/-- Generate external tester functions for all constructors of all datatypes in the program. -/ -def generateDatatypeTesters (program : Program) : Program := - let testers := program.types.flatMap fun td => - match td with - | .Datatype dt => dt.constructors.map (mkTesterFunction dt) - | _ => [] - { program with staticProcedures := testers ++ program.staticProcedures } - -end -- public section - -end Strata.Laurel diff --git a/StrataBoole/StrataBooleTest/deterministic.lean b/StrataBoole/StrataBooleTest/deterministic.lean index a5f1a55315..e38978e6b8 100644 --- a/StrataBoole/StrataBooleTest/deterministic.lean +++ b/StrataBoole/StrataBooleTest/deterministic.lean @@ -46,10 +46,6 @@ procedure Check(x1:int, x2:int) returns () /-- info: -Obligation: Foo_ensures_0_256 -Property: assert -Result: ✅ pass - Obligation: assert_1_562 Property: assert Result: ✅ pass From 2f8eb112ad86b2f08df84b60ed9087fa5873493f Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 4 Jun 2026 17:00:38 +0000 Subject: [PATCH 063/115] Fix bug in Resolution.lean --- Strata/Languages/Laurel/Resolution.lean | 5 +++++ StrataPython/StrataPython/PySpecPipeline.lean | 4 ++-- docs/verso/LaurelDoc.lean | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Strata/Languages/Laurel/Resolution.lean b/Strata/Languages/Laurel/Resolution.lean index 311ceab130..4e68f144c6 100644 --- a/Strata/Languages/Laurel/Resolution.lean +++ b/Strata/Languages/Laurel/Resolution.lean @@ -425,7 +425,12 @@ def resolveStmtExpr (exprMd : StmtExprMd) : ResolveM StmtExprMd := do let ty' ← resolveHighType param.type let name' ← defineNameCheckDup param.name (.var param.name ty') pure (⟨.Declare ⟨name', ty'⟩, vs⟩ : VariableMd) + -- The RHS of an assignment is NOT in value context — the assignment captures + -- all outputs, so multi-output calls are fine here. + let saved := (← get).inValueContext + modify fun s => { s with inValueContext := false } let value' ← resolveStmtExpr value + modify fun s => { s with inValueContext := saved } -- Check that LHS target count matches the number of outputs from the RHS let expectedOutputCount ← match value'.val with | .StaticCall callee _ => do diff --git a/StrataPython/StrataPython/PySpecPipeline.lean b/StrataPython/StrataPython/PySpecPipeline.lean index 13e376c2ab..02322f079b 100644 --- a/StrataPython/StrataPython/PySpecPipeline.lean +++ b/StrataPython/StrataPython/PySpecPipeline.lean @@ -404,9 +404,9 @@ public def translateCombinedLaurelWithLowered (combined : Laurel.Program) /-- Translate a combined Laurel program to Core and prepend the full runtime prelude. -/ -public def translateCombinedLaurel (combined : Laurel.Program) +public def translateCombinedLaurel (combined : Laurel.Program) (keepAllFilesPrefix : Option String := none) : IO (Option Core.Program × List DiagnosticModel) := do - let (coreOption, errors, _, _) ← translateCombinedLaurelWithLowered combined + let (coreOption, errors, _, _) ← translateCombinedLaurelWithLowered combined keepAllFilesPrefix return (coreOption, errors) /-- Run the pyAnalyzeLaurel pipeline: read a Python Ion program, diff --git a/docs/verso/LaurelDoc.lean b/docs/verso/LaurelDoc.lean index b8d63d9302..0fdbe1f5e2 100644 --- a/docs/verso/LaurelDoc.lean +++ b/docs/verso/LaurelDoc.lean @@ -6,7 +6,7 @@ import VersoManual -import Strata.Languages.LaurelAST +import Strata.Languages.Laurel.LaurelAST import Strata.Languages.Laurel.LaurelTypes import Strata.Languages.Laurel.LaurelToCoreTranslator import Strata.Languages.Laurel.HeapParameterization From 61c2e83770dbe98ab47156bacc496238bbb3b226 Mon Sep 17 00:00:00 2001 From: keyboardDrummer-bot Date: Thu, 11 Jun 2026 13:39:44 +0000 Subject: [PATCH 064/115] Address quality follow-ups from review - Delete dead code: translateInvokeOnAxiom + buildQuants (no callers) - Extract unwrapReturnBlock to deduplicate Transparent/Opaque arms - Delete dead code: containsBareAssignment, containsImperativeCall (no callers) - Remove dead if/else in LaurelCompilationPipeline (passDiags always empty at that point) - Remove commented-out code in corePipeline - Deduplicate mkPostConditionProc into mkConditionProc - Fix stale doc on eliminateValuesInReturnsInProc - Deduplicate mapStmtExprM as thin wrapper over mapStmtExprPrePostM - Extract shared decreasing_by tactic macro for MapStmtExpr traversals --- Strata/Languages/Laurel/ContractPass.lean | 20 +-- .../Laurel/EliminateValuesInReturns.lean | 7 +- .../Laurel/LaurelCompilationPipeline.lean | 43 +++---- .../Laurel/LaurelToCoreTranslator.lean | 55 ++------ .../Laurel/LiftImperativeExpressions.lean | 39 ------ Strata/Languages/Laurel/MapStmtExpr.lean | 119 ++++-------------- 6 files changed, 57 insertions(+), 226 deletions(-) diff --git a/Strata/Languages/Laurel/ContractPass.lean b/Strata/Languages/Laurel/ContractPass.lean index 0ae17de08e..a2f788f749 100644 --- a/Strata/Languages/Laurel/ContractPass.lean +++ b/Strata/Languages/Laurel/ContractPass.lean @@ -55,7 +55,8 @@ private def mkCall (callee : String) (args : List StmtExprMd) : StmtExprMd := private def paramsToArgs (params : List Parameter) : List StmtExprMd := params.map fun p => mkMd (.Var (.Local p.name)) -/-- Build a helper function for a single condition. -/ +/-- Build a helper function for a single condition over the given parameters. + Preconditions pass `proc.inputs`; postconditions pass `proc.inputs ++ proc.outputs`. -/ private def mkConditionProc (name : String) (params : List Parameter) (condition : Condition) : Procedure := { name := mkId name @@ -66,20 +67,6 @@ private def mkConditionProc (name : String) (params : List Parameter) isFunctional := true body := .Transparent condition.condition } -/-- Build a postcondition function for a single condition that takes all inputs - and all outputs as parameters. -/ -private def mkPostConditionProc (name : String) - (inputParams : List Parameter) (outputParams : List Parameter) - (condition : Condition) : Procedure := - let allParams := inputParams ++ outputParams - { name := mkId name - inputs := allParams - outputs := [⟨mkId "$result", { val := .TBool, source := none }⟩] - preconditions := [] - decreases := none - isFunctional := true - body := .Transparent condition.condition } - /-- Information about a procedure's contracts. -/ private structure ContractInfo where preNames : List (String × Option String) -- (procName, summary) for each precondition @@ -280,8 +267,7 @@ def contractPass (program : Program) : Program := let preProcs := proc.preconditions.zipIdx.map fun (c, i) => mkConditionProc (preCondProcName proc.name.text i) proc.inputs c let postProcs := postconds.zipIdx.map fun (c, i) => - mkPostConditionProc (postCondProcName proc.name.text i) - proc.inputs proc.outputs c + mkConditionProc (postCondProcName proc.name.text i) (proc.inputs ++ proc.outputs) c preProcs ++ postProcs -- Transform procedures: strip contracts, add assume/assert, rewrite call sites diff --git a/Strata/Languages/Laurel/EliminateValuesInReturns.lean b/Strata/Languages/Laurel/EliminateValuesInReturns.lean index cd82f80c79..d56ba26b9e 100644 --- a/Strata/Languages/Laurel/EliminateValuesInReturns.lean +++ b/Strata/Languages/Laurel/EliminateValuesInReturns.lean @@ -53,9 +53,10 @@ def hasValuedReturn (stmt : StmtExprMd) : Bool := all_goals (try term_by_mem) all_goals omega -/-- Apply value-return elimination to a single procedure. Only applies to - non-functional procedures with exactly one output parameter. - Emits an error if a valued return is used with multiple output parameters. -/ +/-- Apply value-return elimination to a single procedure. Rewrites `return expr` + into `outParam := expr; return` for any procedure with exactly one output + parameter (functional and non-functional alike). + Emits an error if a valued return is used with zero or multiple output parameters. -/ def eliminateValuesInReturnsInProc (proc : Procedure) : Procedure × Array DiagnosticModel := match proc.outputs with | [outParam] => diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index 97266ab1d7..9d7a63ed7a 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -246,11 +246,6 @@ structure CorePass where /-- The ordered sequence of passes on the unordered Core representation. -/ private def corePipeline : Array CorePass := #[ - -- { name := "EliminateMultipleOutputs" - -- run := fun uc _m => eliminateMultipleOutputs uc }, - -- { name := "InlineLocalVariablesInExpressions" - -- needsResolves := true - -- run := fun uc _m => inlineLocalVariablesInExpressions uc }, { name := "LiftImperativeExpressionsInCore" needsResolves := true run := fun uc m => liftImperativeExpressionsInCore uc m } @@ -294,28 +289,22 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) emit pass.name "unorderedCoreWithLaurelTypes.st" unorderedCore let coreWithLaurelTypes := orderFunctionsAndProcedures unorderedCore - -- This early return is a simple way to protect against duplicative errors. Without this return, - -- resolution errors reported by Laurel would also be reported by Core. - -- There might be better solution that allows getting some resolution errors from Laurel and some verification errors from Core, - -- but that would need more consideration. - if ! passDiags.isEmpty then - return (none, passDiags, program, stats) - else - emit "CoreWithLaurelTypes" "core.st" coreWithLaurelTypes - let initState : TranslateState := { model := fnModel, overflowChecks := options.overflowChecks } - let (coreProgramOption, translateState) := - runTranslateM initState (translateLaurelToCore options coreWithLaurelTypes) - -- Because of the duplication between functions and procedures, this translation is liable to create duplicate diagnostics - let mut allDiagnostics: List DiagnosticModel := passDiags ++ translateState.diagnostics.eraseDups; - - if !translateState.coreDiagnostics.isEmpty && allDiagnostics.isEmpty then - allDiagnostics := allDiagnostics ++ translateState.coreDiagnostics - - if coreProgramOption.isSome then - emit "Core" "core.st" coreProgramOption.get! - let coreProgramOption := - if translateState.coreDiagnostics.isEmpty then coreProgramOption else none - return (coreProgramOption, allDiagnostics, program, stats) + + emit "CoreWithLaurelTypes" "core.st" coreWithLaurelTypes + let initState : TranslateState := { model := fnModel, overflowChecks := options.overflowChecks } + let (coreProgramOption, translateState) := + runTranslateM initState (translateLaurelToCore options coreWithLaurelTypes) + -- Because of the duplication between functions and procedures, this translation is liable to create duplicate diagnostics + let mut allDiagnostics: List DiagnosticModel := passDiags ++ translateState.diagnostics.eraseDups; + + if !translateState.coreDiagnostics.isEmpty && allDiagnostics.isEmpty then + allDiagnostics := allDiagnostics ++ translateState.coreDiagnostics + + if coreProgramOption.isSome then + emit "Core" "core.st" coreProgramOption.get! + let coreProgramOption := + if translateState.coreDiagnostics.isEmpty then coreProgramOption else none + return (coreProgramOption, allDiagnostics, program, stats) /-- Translate Laurel Program to Core Program. diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index a0814f9555..866176af8b 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -577,42 +577,6 @@ def translateProcedure (proc : Procedure) : TranslateM Core.Procedure := do let spec : Core.Procedure.Spec := { preconditions, postconditions } return { header, spec, body } -def translateInvokeOnAxiom (proc : Procedure) (trigger : StmtExprMd) - : TranslateM (Option Core.Decl) := do - let postconds := match proc.body with - | .Opaque postconds _ _ | .Abstract postconds => postconds - | _ => [] - if postconds.isEmpty then return none - -- All input param names become bound variables. - -- buildQuants nests ∀ p1, ∀ p2, ..., ∀ pn :: body, so inside body the innermost - -- binder (pn) is de Bruijn index 0, and the outermost (p1) is index n-1. - -- translateExpr uses findIdx? on boundVars, so we must list params innermost-first - -- (i.e. reversed) so that pn → 0, ..., p1 → n-1. - let boundVars := proc.inputs.reverse.map (·.name) - -- Translate postconditions and trigger with the full bound-var context - let postcondExprs ← postconds.mapM (fun pc => translateExpr pc.condition boundVars (isPureContext := true)) - let bodyExpr : Core.Expression.Expr := match postcondExprs with - | [] => .const () (.boolConst true) - | [e] => e - | e :: rest => rest.foldl (fun acc x => LExpr.mkApp () boolAndOp [acc, x]) e - let triggerExpr ← translateExpr trigger boundVars (isPureContext := true) - -- Wrap in ∀ from outermost (first param) to innermost (last param). - -- The trigger is placed on the innermost quantifier. - let quantified ← buildQuants proc.inputs bodyExpr triggerExpr - return some (.ax { name := s!"invokeOn_{proc.name.text}", e := quantified } (identifierToCoreMd proc.name)) -where - /-- Build `∀ p1 ... pn :: { trigger } body`. The trigger is on the innermost quantifier. -/ - buildQuants (params : List Parameter) - (body : Core.Expression.Expr) (trigger : Core.Expression.Expr) - : TranslateM Core.Expression.Expr := do - match params with - | [] => return body - | [p] => - return LExpr.allTr () p.name.text (some (← translateType p.type)) trigger body - | p :: rest => do - let inner ← buildQuants rest body trigger - return LExpr.all () p.name.text (some (← translateType p.type)) inner - structure LaurelTranslateOptions where emitResolutionErrors : Bool := true inlineFunctionsWhenPossible : Bool := false @@ -629,6 +593,13 @@ structure LaurelVerifyOptions where instance : Inhabited LaurelVerifyOptions where default := {} +/-- Unwrap the pattern produced by EliminateValuesInReturns + EliminateReturnStatements: + `{ result := ; exit "$return" } $return` → `` -/ +private def unwrapReturnBlock (b : StmtExprMd) : StmtExprMd := + match b.val with + | .Block [⟨.Assign [⟨.Local _, _⟩] value, _⟩, ⟨.Exit "$return", _⟩] (some "$return") => value + | _ => b + /-- Translate a Laurel Procedure to a Core Function (when applicable) using `TranslateM`. Diagnostics for disallowed constructs in the function body are emitted into the monad state. @@ -665,18 +636,10 @@ def translateProcedureToFunction (options: LaurelTranslateOptions) (isRecursive: let body ← match proc.body with | .Transparent bodyExpr => - -- Unwrap the pattern produced by EliminateValuesInReturns + EliminateReturnStatements: - -- { result := ; exit "$return" } $return → - let unwrapped := match bodyExpr.val with - | .Block [⟨.Assign [⟨.Local _, _⟩] value, _⟩, ⟨.Exit "$return", _⟩] (some "$return") => value - | _ => bodyExpr - some <$> translateExpr unwrapped [] (isPureContext := true) + some <$> translateExpr (unwrapReturnBlock bodyExpr) [] (isPureContext := true) | .Opaque _ (some bodyExpr) _ => emitDiagnostic (diagnosticFromSource proc.name.source "functions with postconditions are not yet supported") - let unwrapped := match bodyExpr.val with - | .Block [⟨.Assign [⟨.Local _, _⟩] value, _⟩, ⟨.Exit "$return", _⟩] (some "$return") => value - | _ => bodyExpr - some <$> translateExpr unwrapped [] (isPureContext := true) + some <$> translateExpr (unwrapReturnBlock bodyExpr) [] (isPureContext := true) | _ => pure none let f : Core.Function := { name := ⟨proc.name.text, ()⟩ diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index c18b667639..80cdab5987 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -190,45 +190,6 @@ def containsAssignmentOrImperativeCall (imperativeCallees : List String) (expr : all_goals (try term_by_mem) all_goals omega -/-- Like containsAssignment but does NOT recurse into Blocks (treats them as opaque). - Used by assert/assume handlers to allow generated Block wrappers through. -/ -def containsBareAssignment (expr : StmtExprMd) : Bool := - match expr with - | AstNode.mk val _ => - match val with - | .Assign .. => true - | .StaticCall _ args => args.attach.any (fun x => containsBareAssignment x.val) - | .PrimitiveOp _ args _ => args.attach.any (fun x => containsBareAssignment x.val) - | .Block _ _ => false - | .IfThenElse cond th el => - containsBareAssignment cond || containsBareAssignment th || - match el with | some e => containsBareAssignment e | none => false - | _ => false - termination_by expr - decreasing_by - all_goals ((try cases x); simp_all; try term_by_mem) - -/-- Check if an expression contains any non-functional procedure calls (recursively). -/ -def containsImperativeCall (model : SemanticModel) (expr : StmtExprMd) : Bool := - match expr with - | AstNode.mk val _ => - match val with - | .StaticCall name args => - (match model.get name with - | .staticProcedure proc => !proc.isFunctional - | _ => false) || - args.attach.any (fun x => containsImperativeCall model x.val) - | .PrimitiveOp _ args _ => args.attach.any (fun x => containsImperativeCall model x.val) - | .Block stmts _ => stmts.attach.any (fun x => containsImperativeCall model x.val) - | .IfThenElse cond th el => - containsImperativeCall model cond || - containsImperativeCall model th || - match el with | some e => containsImperativeCall model e | none => false - | _ => false - termination_by expr - decreasing_by - all_goals ((try cases x); simp_all; try term_by_mem) - /-- Shared logic for lifting an assignment in expression position: prepends the assignment, creates before-snapshots for all targets, diff --git a/Strata/Languages/Laurel/MapStmtExpr.lean b/Strata/Languages/Laurel/MapStmtExpr.lean index 76fd11f58c..59e6d96d83 100644 --- a/Strata/Languages/Laurel/MapStmtExpr.lean +++ b/Strata/Languages/Laurel/MapStmtExpr.lean @@ -22,89 +22,19 @@ Also provides `mapProcedureBodiesM` and `mapProgramM` to eliminate the namespace Strata.Laurel -public section +/-- Shared termination tactic for `mapStmtExpr*` traversals. The argument is + the variable representing the expression being recursed on. -/ +local syntax "map_stmt_expr_decreasing" ident : tactic +macro_rules + | `(tactic| map_stmt_expr_decreasing $x:ident) => + `(tactic| ( + all_goals simp_wf + all_goals (try have := AstNode.sizeOf_val_lt $x) + all_goals (try have := Condition.sizeOf_condition_lt ‹_›) + all_goals (try term_by_mem) + all_goals (revert $x; intro x; cases x; simp_all; omega))) -/-- -Bottom-up monadic traversal of `StmtExprMd`. Recurses into all `StmtExprMd` -children first, then applies `f` to the rebuilt node. --/ -def mapStmtExprM [Monad m] (f : StmtExprMd → m StmtExprMd) (expr : StmtExprMd) : m StmtExprMd := do - let source := expr.source - -- `.attach` wraps each element with a proof of membership, which the - -- termination checker uses to show the recursive call is on a smaller value. - let rebuilt ← match _h : expr.val with - | .IfThenElse cond th el => - pure ⟨.IfThenElse (← mapStmtExprM f cond) (← mapStmtExprM f th) - (← el.attach.mapM fun ⟨e, _⟩ => mapStmtExprM f e), source⟩ - | .Block stmts label => - pure ⟨.Block (← stmts.attach.mapM fun ⟨e, _⟩ => mapStmtExprM f e) label, source⟩ - | .While cond invs dec body => - pure ⟨.While (← mapStmtExprM f cond) - (← invs.attach.mapM fun ⟨e, _⟩ => mapStmtExprM f e) - (← dec.attach.mapM fun ⟨e, _⟩ => mapStmtExprM f e) - (← mapStmtExprM f body), source⟩ - | .Return v => - pure ⟨.Return (← v.attach.mapM fun ⟨e, _⟩ => mapStmtExprM f e), source⟩ - | .Assign targets value => - let targets' ← targets.attach.mapM fun ⟨v, _⟩ => do - let ⟨vv, vs⟩ := v - match vv with - | .Field target fieldName => - pure ⟨Variable.Field (← mapStmtExprM f target) fieldName, vs⟩ - | .Local _ | .Declare _ => pure v - pure ⟨.Assign targets' (← mapStmtExprM f value), source⟩ - | .Var (.Field target fieldName) => - pure ⟨.Var (.Field (← mapStmtExprM f target) fieldName), source⟩ - | .PureFieldUpdate target fieldName newValue => - pure ⟨.PureFieldUpdate (← mapStmtExprM f target) fieldName (← mapStmtExprM f newValue), source⟩ - | .StaticCall callee args => - pure ⟨.StaticCall callee (← args.attach.mapM fun ⟨e, _⟩ => mapStmtExprM f e), source⟩ - | .PrimitiveOp op args skipProof => - pure ⟨.PrimitiveOp op (← args.attach.mapM fun ⟨e, _⟩ => mapStmtExprM f e) skipProof, source⟩ - | .ReferenceEquals lhs rhs => - pure ⟨.ReferenceEquals (← mapStmtExprM f lhs) (← mapStmtExprM f rhs), source⟩ - | .AsType target ty => - pure ⟨.AsType (← mapStmtExprM f target) ty, source⟩ - | .IsType target ty => - pure ⟨.IsType (← mapStmtExprM f target) ty, source⟩ - | .InstanceCall target callee args => - pure ⟨.InstanceCall (← mapStmtExprM f target) callee - (← args.attach.mapM fun ⟨e, _⟩ => mapStmtExprM f e), source⟩ - | .Quantifier mode param trigger body => - pure ⟨.Quantifier mode param (← trigger.attach.mapM fun ⟨e, _⟩ => mapStmtExprM f e) - (← mapStmtExprM f body), source⟩ - | .Assigned name => - pure ⟨.Assigned (← mapStmtExprM f name), source⟩ - | .Old value => - pure ⟨.Old (← mapStmtExprM f value), source⟩ - | .Fresh value => - pure ⟨.Fresh (← mapStmtExprM f value), source⟩ - | .Assert cond => - pure ⟨.Assert { cond with condition := ← mapStmtExprM f cond.condition }, source⟩ - | .Assume cond => - pure ⟨.Assume (← mapStmtExprM f cond), source⟩ - | .ProveBy value proof => - pure ⟨.ProveBy (← mapStmtExprM f value) (← mapStmtExprM f proof), source⟩ - | .ContractOf ty func => - pure ⟨.ContractOf ty (← mapStmtExprM f func), source⟩ - -- Leaves: no StmtExprMd children. - -- ⚠ If a new StmtExpr constructor with StmtExprMd children is added, - -- it must get its own arm above; otherwise all passes will silently - -- skip recursion into those children. - | .Exit _ | .LiteralInt _ | .LiteralBool _ | .LiteralString _ | .LiteralDecimal _ - | .Var (.Local _) | .Var (.Declare _) | .New _ | .This | .Abstract | .All | .Hole .. => pure expr - f rebuilt -termination_by sizeOf expr -decreasing_by - all_goals simp_wf - all_goals (try have := AstNode.sizeOf_val_lt expr) - all_goals (try have := Condition.sizeOf_condition_lt ‹_›) - all_goals (try term_by_mem) - all_goals (cases expr; simp_all; omega) - -/-- Pure bottom-up traversal of `StmtExprMd`. -/ -def mapStmtExpr (f : StmtExprMd → StmtExprMd) (expr : StmtExprMd) : StmtExprMd := - (mapStmtExprM (m := Id) f expr) +public section /-- Bottom-up monadic traversal with a pre-filter. Before recursing into a node's @@ -177,12 +107,18 @@ def mapStmtExprPrePostM [Monad m] (pre : StmtExprMd → m (Option StmtExprMd)) | .Var (.Local _) | .Var (.Declare _) | .New _ | .This | .Abstract | .All | .Hole .. => pure expr post rebuilt termination_by sizeOf expr -decreasing_by - all_goals simp_wf - all_goals (try have := AstNode.sizeOf_val_lt expr) - all_goals (try have := Condition.sizeOf_condition_lt ‹_›) - all_goals (try term_by_mem) - all_goals (cases expr; simp_all; omega) +decreasing_by map_stmt_expr_decreasing expr + +/-- +Bottom-up monadic traversal of `StmtExprMd`. Recurses into all `StmtExprMd` +children first, then applies `f` to the rebuilt node. +-/ +def mapStmtExprM [Monad m] (f : StmtExprMd → m StmtExprMd) (expr : StmtExprMd) : m StmtExprMd := + mapStmtExprPrePostM (fun _ => pure none) f expr + +/-- Pure bottom-up traversal of `StmtExprMd`. -/ +def mapStmtExpr (f : StmtExprMd → StmtExprMd) (expr : StmtExprMd) : StmtExprMd := + (mapStmtExprM (m := Id) f expr) /-- Bottom-up monadic traversal where `post` returns a list of statements. @@ -267,12 +203,7 @@ def mapStmtExprFlattenM [Monad m] (pre : StmtExprMd → m (Option (List StmtExpr let results ← post rebuilt return collapse results source termination_by sizeOf e - decreasing_by - all_goals simp_wf - all_goals (try have := AstNode.sizeOf_val_lt e) - all_goals (try have := Condition.sizeOf_condition_lt ‹_›) - all_goals (try term_by_mem) - all_goals (cases e; simp_all; omega) + decreasing_by map_stmt_expr_decreasing e go expr /-- Apply a monadic transformation to all procedure bodies. -/ From 9ca5c77b8f50e14df5ceb9f038c94c9e6e708f89 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 11 Jun 2026 13:48:16 +0000 Subject: [PATCH 065/115] Add test and fix for counter created by contract pass --- Strata/Languages/Laurel/ContractPass.lean | 106 ++++++++++-------- .../Fundamentals/T8_Postconditions.lean | 14 +++ 2 files changed, 71 insertions(+), 49 deletions(-) diff --git a/Strata/Languages/Laurel/ContractPass.lean b/Strata/Languages/Laurel/ContractPass.lean index 0ae17de08e..4acacd457a 100644 --- a/Strata/Languages/Laurel/ContractPass.lean +++ b/Strata/Languages/Laurel/ContractPass.lean @@ -131,23 +131,33 @@ private def transformProcBody (proc : Procedure) (info : ContractInfo) : Body := .Abstract postconds | b => b +/-- Monad used by the contract-pass rewriter; carries a global counter for + generating fresh temporary variable names. -/ +private abbrev ContractM := StateM Nat + +/-- Allocate a fresh temporary name with the `$cp_` prefix. The global counter + guarantees uniqueness across the entire pass. -/ +private def freshTemp : ContractM String := do + let n ← get + set (n + 1) + return s!"$cp_{n}" + /-- Generate temporary variable assignments for input arguments at a call site. Returns (temp declarations+assignments, temp variable references). -/ -private def mkTempAssignments (args : List StmtExprMd) (calleeName : String) - (inputParams : List Parameter) (callIdx : Nat) (src : Option FileRange) - : List StmtExprMd × List StmtExprMd := - let indexed := args.zipIdx - let decls := indexed.map fun (arg, i) => - let tempName := s!"${calleeName}${callIdx}$arg{i}" +private def mkTempAssignments (args : List StmtExprMd) + (inputParams : List Parameter) (src : Option FileRange) + : ContractM (List StmtExprMd × List StmtExprMd) := do + let mut decls : List StmtExprMd := [] + let mut refs : List StmtExprMd := [] + for arg in args, i in List.range args.length do + let tempName ← freshTemp let paramType := match inputParams[i]? with | some p => p.type | none => { val := .Unknown, source := none } let param : Parameter := { name := mkId tempName, type := paramType } - ⟨StmtExpr.Assign [mkVarMd (.Declare param)] arg, src⟩ - let refs := indexed.map fun (_, i) => - let tempName := s!"${calleeName}${callIdx}$arg{i}" - mkMd (.Var (.Local (mkId tempName))) - (decls, refs) + decls := decls ++ [⟨StmtExpr.Assign [mkVarMd (.Declare param)] arg, src⟩] + refs := refs ++ [mkMd (.Var (.Local (mkId tempName)))] + return (decls, refs) /-- Generate precondition checks (one per precondition) for a call site. -/ private def mkPreChecks (info : ContractInfo) (isFunctional : Bool) @@ -169,53 +179,50 @@ private def mkPostAssumes (info : ContractInfo) /-- Rewrite call sites in a statement/expression tree. -/ private def rewriteCallSites (contractInfoMap : Std.HashMap String ContractInfo) - (isFunctional : Bool) (expr : StmtExprMd) : StmtExprMd := - let rewriteStaticCall (counter : Nat) (callee : Identifier) (args : List StmtExprMd) + (isFunctional : Bool) (expr : StmtExprMd) : ContractM StmtExprMd := do + let rewriteStaticCall (callee : Identifier) (args : List StmtExprMd) (info : ContractInfo) (src : Option FileRange) - : List StmtExprMd × Nat := - let (tempDecls, tempRefs) := mkTempAssignments args callee.text info.inputParams counter src + : ContractM (List StmtExprMd) := do + let (tempDecls, tempRefs) ← mkTempAssignments args info.inputParams src let preCheck := mkPreChecks info isFunctional tempRefs src - let (callStmt, postAssume, returnValue) := - if info.hasPostCondition && !info.outputParams.isEmpty then - let outputTempDecls := info.outputParams.zipIdx.map fun (p, i) => - let tempName := s!"${callee.text}${counter}$out{i}" - mkVarMd (.Declare { name := mkId tempName, type := p.type }) + let (callStmt, postAssume, returnValue) ← + if info.hasPostCondition && !info.outputParams.isEmpty then do + let mut outputTempDecls : List VariableMd := [] + let mut outputRefs : List StmtExprMd := [] + for p in info.outputParams do + let tempName ← freshTemp + outputTempDecls := outputTempDecls ++ [mkVarMd (.Declare { name := mkId tempName, type := p.type })] + outputRefs := outputRefs ++ [mkMd (.Var (.Local (mkId tempName)))] let callWithOutputs : StmtExprMd := ⟨.Assign outputTempDecls ⟨.StaticCall callee tempRefs, src⟩, src⟩ - let outputRefs := info.outputParams.zipIdx.map fun (_, i) => - let tempName := s!"${callee.text}${counter}$out{i}" - mkMd (.Var (.Local (mkId tempName))) let assume := mkPostAssumes info tempRefs outputRefs src let retVal : List StmtExprMd := match outputRefs with | [single] => [single] | _ => [] - (callWithOutputs, assume, retVal) + pure (callWithOutputs, assume, retVal) else - (⟨.StaticCall callee tempRefs, src⟩, [], []) - (tempDecls ++ preCheck ++ [callStmt] ++ postAssume ++ returnValue, counter + 1) - let (result, _) := StateT.run (s := (0 : Nat)) <| - mapStmtExprFlattenM (m := StateM Nat) + pure (⟨.StaticCall callee tempRefs, src⟩, [], []) + return tempDecls ++ preCheck ++ [callStmt] ++ postAssume ++ returnValue + let result ← + mapStmtExprFlattenM (m := ContractM) -- Pre: intercept Assign targets (StaticCall ...) before recursion (fun e => do match e.val with | .Assign targets (.mk (.StaticCall callee args) callSrc) => match contractInfoMap.get? callee.text with | some info => - let counter ← get let src := e.source -- Recurse into arguments - let args' ← args.mapM (mapStmtExprM (m := StateM Nat) (fun e' => do + let args' ← args.mapM (mapStmtExprM (m := ContractM) (fun e' => do match e'.val with | .StaticCall callee' args' => match contractInfoMap.get? callee'.text with | some info' => - let counter' ← get - let (stmts, counter'') := rewriteStaticCall counter' callee' args' info' e'.source - set counter'' + let stmts ← rewriteStaticCall callee' args' info' e'.source return ⟨.Block stmts none, e'.source⟩ | none => return e' | _ => return e')) - let (tempDecls, tempRefs) := mkTempAssignments args' callee.text info.inputParams counter src + let (tempDecls, tempRefs) ← mkTempAssignments args' info.inputParams src let callWithTemps : StmtExprMd := ⟨.Assign targets ⟨.StaticCall callee tempRefs, callSrc⟩, src⟩ let preCheck := mkPreChecks info isFunctional tempRefs src let outputArgs := targets.filterMap fun t => @@ -224,7 +231,6 @@ private def rewriteCallSites (contractInfoMap : Std.HashMap String ContractInfo) | .Declare param => some (mkMd (.Var (.Local param.name))) | _ => none let postAssume := mkPostAssumes info tempRefs outputArgs src - set (counter + 1) return some (tempDecls ++ preCheck ++ [callWithTemps] ++ postAssume) | none => return none | _ => return none) @@ -234,25 +240,26 @@ private def rewriteCallSites (contractInfoMap : Std.HashMap String ContractInfo) | .StaticCall callee args => match contractInfoMap.get? callee.text with | some info => - let counter ← get - let (stmts, counter') := rewriteStaticCall counter callee args info e.source - set counter' + let stmts ← rewriteStaticCall callee args info e.source return stmts | none => return [e] | _ => return [e]) expr - result + return result /-- Rewrite call sites in all bodies of a procedure. -/ private def rewriteCallSitesInProc (contractInfoMap : Std.HashMap String ContractInfo) - (proc : Procedure) : Procedure := + (proc : Procedure) : ContractM Procedure := do let rw := rewriteCallSites contractInfoMap proc.isFunctional match proc.body with | .Transparent body => - { proc with body := .Transparent (rw body) } + let body' ← rw body + return { proc with body := .Transparent body' } | .Opaque posts impl mods => - let body := Body.Opaque (posts.map (·.mapCondition rw)) (impl.map rw) (mods.map rw) - { proc with body := body } - | _ => proc + let posts' ← posts.mapM (·.mapM rw) + let impl' ← impl.mapM rw + let mods' ← mods.mapM rw + return { proc with body := Body.Opaque posts' impl' mods' } + | _ => return proc /-- Build an axiom expression from `invokeOn` trigger and ensures clauses. Produces `∀ p1, ∀ p2, ..., ∀ pn :: { trigger } (ensures1 && ensures2 && ...)`. @@ -285,11 +292,12 @@ def contractPass (program : Program) : Program := preProcs ++ postProcs -- Transform procedures: strip contracts, add assume/assert, rewrite call sites - let transformedProcs := program.staticProcedures.map fun proc => + -- Run all call-site rewriting in a single ContractM to share the global counter. + let (transformedProcs, _) := (program.staticProcedures.mapM fun (proc : Procedure) => do if proc.isFunctional then - proc + return proc else - let proc := match proc.invokeOn with + let proc : Procedure := match proc.invokeOn with | some trigger => let postconds := getPostconditions proc.body if postconds.isEmpty then { proc with invokeOn := none } @@ -297,14 +305,14 @@ def contractPass (program : Program) : Program := axioms := [mkInvokeOnAxiom proc.inputs trigger postconds] invokeOn := none } | none => proc - let proc := match contractInfoMap.get? proc.name.text with + let proc : Procedure := 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 + rewriteCallSitesInProc contractInfoMap proc).run 0 { program with staticProcedures := helperProcs ++ transformedProcs } diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean index 2888004477..f1cf84ba8d 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8_Postconditions.lean @@ -40,6 +40,20 @@ procedure invalidPostcondition(x: int) // ^^^^^ error: postcondition does not hold { }; + +procedure bar(x: int) returns (r: int) + requires x > 0 + opaque + ensures r > 0 +{ + r := x + 1 +}; + +procedure caller() returns (out: int) + opaque +{ + out := bar(bar(5)) +}; " #guard_msgs (drop info, error) in From b4938f0a89f9d12b2b3ff7ce6aeaf22a3ee96d97 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 11 Jun 2026 15:50:41 +0200 Subject: [PATCH 066/115] Update Strata/Languages/Laurel/ContractPass.lean Co-authored-by: Fabio Madge --- Strata/Languages/Laurel/ContractPass.lean | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/Strata/Languages/Laurel/ContractPass.lean b/Strata/Languages/Laurel/ContractPass.lean index 0ae17de08e..a4514f0e01 100644 --- a/Strata/Languages/Laurel/ContractPass.lean +++ b/Strata/Languages/Laurel/ContractPass.lean @@ -55,7 +55,8 @@ private def mkCall (callee : String) (args : List StmtExprMd) : StmtExprMd := private def paramsToArgs (params : List Parameter) : List StmtExprMd := params.map fun p => mkMd (.Var (.Local p.name)) -/-- Build a helper function for a single condition. -/ +/-- Build a helper function for a single condition over the given parameters. + Preconditions pass `proc.inputs`; postconditions pass `proc.inputs ++ proc.outputs`. -/ private def mkConditionProc (name : String) (params : List Parameter) (condition : Condition) : Procedure := { name := mkId name @@ -66,20 +67,6 @@ private def mkConditionProc (name : String) (params : List Parameter) isFunctional := true body := .Transparent condition.condition } -/-- Build a postcondition function for a single condition that takes all inputs - and all outputs as parameters. -/ -private def mkPostConditionProc (name : String) - (inputParams : List Parameter) (outputParams : List Parameter) - (condition : Condition) : Procedure := - let allParams := inputParams ++ outputParams - { name := mkId name - inputs := allParams - outputs := [⟨mkId "$result", { val := .TBool, source := none }⟩] - preconditions := [] - decreases := none - isFunctional := true - body := .Transparent condition.condition } - /-- Information about a procedure's contracts. -/ private structure ContractInfo where preNames : List (String × Option String) -- (procName, summary) for each precondition From 9c9ace9ac0c30624489e9c19cd80d1b7fe4a22f9 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 11 Jun 2026 15:52:21 +0200 Subject: [PATCH 067/115] Update Strata/Languages/Laurel/EliminateValuesInReturns.lean Co-authored-by: Fabio Madge --- Strata/Languages/Laurel/EliminateValuesInReturns.lean | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Strata/Languages/Laurel/EliminateValuesInReturns.lean b/Strata/Languages/Laurel/EliminateValuesInReturns.lean index cd82f80c79..d56ba26b9e 100644 --- a/Strata/Languages/Laurel/EliminateValuesInReturns.lean +++ b/Strata/Languages/Laurel/EliminateValuesInReturns.lean @@ -53,9 +53,10 @@ def hasValuedReturn (stmt : StmtExprMd) : Bool := all_goals (try term_by_mem) all_goals omega -/-- Apply value-return elimination to a single procedure. Only applies to - non-functional procedures with exactly one output parameter. - Emits an error if a valued return is used with multiple output parameters. -/ +/-- Apply value-return elimination to a single procedure. Rewrites `return expr` + into `outParam := expr; return` for any procedure with exactly one output + parameter (functional and non-functional alike). + Emits an error if a valued return is used with zero or multiple output parameters. -/ def eliminateValuesInReturnsInProc (proc : Procedure) : Procedure × Array DiagnosticModel := match proc.outputs with | [outParam] => From bef6885a6ef190fd9e7458c04b64a3d7cfe1269f Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 11 Jun 2026 13:53:42 +0000 Subject: [PATCH 068/115] Cleanup --- .../Laurel/LaurelCompilationPipeline.lean | 41 +++++++++---------- .../Laurel/LiftImperativeExpressions.lean | 18 -------- 2 files changed, 19 insertions(+), 40 deletions(-) diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index 97266ab1d7..e6e07fcdb4 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -270,6 +270,10 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) | none => Strata.Pipeline.PipelineContext.create (outputMode := .quiet) runPipelineM options.keepAllFilesPrefix do let (program, model, passDiags, stats) ← runLaurelPasses options pctx program + -- This early return is a simple way to protect against duplicative errors. Without this return, + -- resolution errors reported by Laurel would also be reported by Core. + -- There might be better solution that allows getting some resolution errors from Laurel and some verification errors from Core, + -- but that would need more consideration. if ! passDiags.isEmpty then return (none, passDiags, program, stats) @@ -294,28 +298,21 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) emit pass.name "unorderedCoreWithLaurelTypes.st" unorderedCore let coreWithLaurelTypes := orderFunctionsAndProcedures unorderedCore - -- This early return is a simple way to protect against duplicative errors. Without this return, - -- resolution errors reported by Laurel would also be reported by Core. - -- There might be better solution that allows getting some resolution errors from Laurel and some verification errors from Core, - -- but that would need more consideration. - if ! passDiags.isEmpty then - return (none, passDiags, program, stats) - else - emit "CoreWithLaurelTypes" "core.st" coreWithLaurelTypes - let initState : TranslateState := { model := fnModel, overflowChecks := options.overflowChecks } - let (coreProgramOption, translateState) := - runTranslateM initState (translateLaurelToCore options coreWithLaurelTypes) - -- Because of the duplication between functions and procedures, this translation is liable to create duplicate diagnostics - let mut allDiagnostics: List DiagnosticModel := passDiags ++ translateState.diagnostics.eraseDups; - - if !translateState.coreDiagnostics.isEmpty && allDiagnostics.isEmpty then - allDiagnostics := allDiagnostics ++ translateState.coreDiagnostics - - if coreProgramOption.isSome then - emit "Core" "core.st" coreProgramOption.get! - let coreProgramOption := - if translateState.coreDiagnostics.isEmpty then coreProgramOption else none - return (coreProgramOption, allDiagnostics, program, stats) + emit "CoreWithLaurelTypes" "core.st" coreWithLaurelTypes + let initState : TranslateState := { model := fnModel, overflowChecks := options.overflowChecks } + let (coreProgramOption, translateState) := + runTranslateM initState (translateLaurelToCore options coreWithLaurelTypes) + -- Because of the duplication between functions and procedures, this translation is liable to create duplicate diagnostics + let mut allDiagnostics: List DiagnosticModel := passDiags ++ translateState.diagnostics.eraseDups; + + if !translateState.coreDiagnostics.isEmpty && allDiagnostics.isEmpty then + allDiagnostics := allDiagnostics ++ translateState.coreDiagnostics + + if coreProgramOption.isSome then + emit "Core" "core.st" coreProgramOption.get! + let coreProgramOption := + if translateState.coreDiagnostics.isEmpty then coreProgramOption else none + return (coreProgramOption, allDiagnostics, program, stats) /-- Translate Laurel Program to Core Program. diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index c18b667639..7114d1c832 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -190,24 +190,6 @@ def containsAssignmentOrImperativeCall (imperativeCallees : List String) (expr : all_goals (try term_by_mem) all_goals omega -/-- Like containsAssignment but does NOT recurse into Blocks (treats them as opaque). - Used by assert/assume handlers to allow generated Block wrappers through. -/ -def containsBareAssignment (expr : StmtExprMd) : Bool := - match expr with - | AstNode.mk val _ => - match val with - | .Assign .. => true - | .StaticCall _ args => args.attach.any (fun x => containsBareAssignment x.val) - | .PrimitiveOp _ args _ => args.attach.any (fun x => containsBareAssignment x.val) - | .Block _ _ => false - | .IfThenElse cond th el => - containsBareAssignment cond || containsBareAssignment th || - match el with | some e => containsBareAssignment e | none => false - | _ => false - termination_by expr - decreasing_by - all_goals ((try cases x); simp_all; try term_by_mem) - /-- Check if an expression contains any non-functional procedure calls (recursively). -/ def containsImperativeCall (model : SemanticModel) (expr : StmtExprMd) : Bool := match expr with From 551e699604b5563ecf12a758211b9d8043a01e50 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 11 Jun 2026 14:08:40 +0000 Subject: [PATCH 069/115] partial --- .../Laurel/EliminateReturnsInExpression.lean | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/Strata/Languages/Laurel/EliminateReturnsInExpression.lean b/Strata/Languages/Laurel/EliminateReturnsInExpression.lean index 60f8ac19b4..e31e063608 100644 --- a/Strata/Languages/Laurel/EliminateReturnsInExpression.lean +++ b/Strata/Languages/Laurel/EliminateReturnsInExpression.lean @@ -18,13 +18,11 @@ Emits a diagnostic if it fails at any step. namespace Strata.Laurel -mutual - /-- Recurse into a statement, applying the guard-return rewrite inside blocks and branches. Returns `Except DiagnosticModel StmtExprMd` so that unsupported statement forms produce a diagnostic instead of panicking. -/ -partial def removeReturns (stmt : StmtExprMd) : Except DiagnosticModel StmtExprMd := - match stmt.val with +def removeReturns (stmt : StmtExprMd) : Except DiagnosticModel StmtExprMd := + match _h : stmt.val with | .Return (some expr) => .ok expr | .Block [head] _ => removeReturns head | .Block (head :: tail) label => do @@ -34,7 +32,7 @@ partial def removeReturns (stmt : StmtExprMd) : Except DiagnosticModel StmtExprM | .Block stmts _ => stmts | _ => [newTail] ⟨ .Block (head :: tailList) label, stmt.source ⟩ - match head.val with + match _hhead : head.val with | .IfThenElse cond thenBr none => let newThen ← removeReturns thenBr .ok ⟨ .IfThenElse cond newThen newTail, head.source ⟩ @@ -51,8 +49,24 @@ partial def removeReturns (stmt : StmtExprMd) : Except DiagnosticModel StmtExprM .ok ⟨ .IfThenElse cond thenExpr (some elseExpr), stmt.source⟩ | _ => .error (diagnosticFromSource stmt.source s!"ending a transparent body with a {stmt.val.constructorName} statement is not supported") - -end +termination_by sizeOf stmt +decreasing_by all_goals ( + have hs : sizeOf stmt = 1 + sizeOf stmt.val + sizeOf stmt.source := by + cases stmt; simp [AstNode.mk.sizeOf_spec] + rw [_h] at hs + simp only [StmtExpr.Block.sizeOf_spec, StmtExpr.IfThenElse.sizeOf_spec, + List.cons.sizeOf_spec, List.nil.sizeOf_spec, Option.some.sizeOf_spec] at hs + first + | (have hh : sizeOf head = 1 + sizeOf head.val + sizeOf head.source := by + cases head; simp [AstNode.mk.sizeOf_spec] + rw [_hhead] at hh + simp only [StmtExpr.IfThenElse.sizeOf_spec, Option.none.sizeOf_spec] at hh + omega) + | (simp only [AstNode.mk.sizeOf_spec, StmtExpr.Block.sizeOf_spec, Option.none.sizeOf_spec] + have hh : sizeOf head = 1 + sizeOf head.val + sizeOf head.source := by + cases head; simp [AstNode.mk.sizeOf_spec] + omega) + | omega) /-- Transform a single procedure by applying the guard-return elimination to its body. Returns the procedure and any diagnostic emitted on failure. -/ From 67f43bfb976b9a11c8e764fe953e9bb81c607720 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 11 Jun 2026 14:13:49 +0000 Subject: [PATCH 070/115] Simplify termination proof --- .../Laurel/EliminateReturnsInExpression.lean | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/Strata/Languages/Laurel/EliminateReturnsInExpression.lean b/Strata/Languages/Laurel/EliminateReturnsInExpression.lean index e31e063608..d316a8d2f5 100644 --- a/Strata/Languages/Laurel/EliminateReturnsInExpression.lean +++ b/Strata/Languages/Laurel/EliminateReturnsInExpression.lean @@ -49,24 +49,17 @@ def removeReturns (stmt : StmtExprMd) : Except DiagnosticModel StmtExprMd := .ok ⟨ .IfThenElse cond thenExpr (some elseExpr), stmt.source⟩ | _ => .error (diagnosticFromSource stmt.source s!"ending a transparent body with a {stmt.val.constructorName} statement is not supported") -termination_by sizeOf stmt -decreasing_by all_goals ( - have hs : sizeOf stmt = 1 + sizeOf stmt.val + sizeOf stmt.source := by - cases stmt; simp [AstNode.mk.sizeOf_spec] - rw [_h] at hs - simp only [StmtExpr.Block.sizeOf_spec, StmtExpr.IfThenElse.sizeOf_spec, - List.cons.sizeOf_spec, List.nil.sizeOf_spec, Option.some.sizeOf_spec] at hs - first - | (have hh : sizeOf head = 1 + sizeOf head.val + sizeOf head.source := by - cases head; simp [AstNode.mk.sizeOf_spec] - rw [_hhead] at hh - simp only [StmtExpr.IfThenElse.sizeOf_spec, Option.none.sizeOf_spec] at hh - omega) - | (simp only [AstNode.mk.sizeOf_spec, StmtExpr.Block.sizeOf_spec, Option.none.sizeOf_spec] - have hh : sizeOf head = 1 + sizeOf head.val + sizeOf head.source := by - cases head; simp [AstNode.mk.sizeOf_spec] - omega) - | omega) +termination_by sizeOf stmt.val +decreasing_by + all_goals + simp only [_h, StmtExpr.Block.sizeOf_spec, StmtExpr.IfThenElse.sizeOf_spec, + List.cons.sizeOf_spec, List.nil.sizeOf_spec, Option.none.sizeOf_spec, + Option.some.sizeOf_spec] + try have hhd := AstNode.sizeOf_val_lt head + try have htb := AstNode.sizeOf_val_lt thenBr + try have heb := AstNode.sizeOf_val_lt elseBr + try simp only [_hhead, StmtExpr.IfThenElse.sizeOf_spec, Option.none.sizeOf_spec] at hhd + omega /-- Transform a single procedure by applying the guard-return elimination to its body. Returns the procedure and any diagnostic emitted on failure. -/ From ac0b6be29e08d7e31ffc72acdaf2dd0a67978169 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 11 Jun 2026 14:49:25 +0000 Subject: [PATCH 071/115] Fix test --- .../Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean b/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean index e5b59c02d2..d2ab9d5102 100644 --- a/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean +++ b/StrataTest/Languages/Laurel/AbstractToConcreteTreeTranslatorTest.lean @@ -290,7 +290,8 @@ info: procedure test(): int info: procedure earlyExit(b: bool) opaque { - if b then { + if b + then { return }; assert true From 7eef859a440e68feb4ec4b333a5d0120d7274c84 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Fri, 12 Jun 2026 09:59:30 +0000 Subject: [PATCH 072/115] Code review changes --- Strata/Languages/Core/Factory.lean | 1 - .../Languages/Laurel/DesugarShortCircuit.lean | 2 +- .../Laurel/EliminateReturnStatements.lean | 19 ++++++++++--------- .../Laurel/LaurelCompilationPipeline.lean | 5 +---- .../Laurel/LiftImperativeExpressions.lean | 3 ++- .../Languages/Core/Examples/TypeDecl.lean | 2 +- .../Fundamentals/T8c_BodilessInlining.lean | 3 +-- .../Examples/Objects/T1_MutableFields.lean | 2 +- 8 files changed, 17 insertions(+), 20 deletions(-) diff --git a/Strata/Languages/Core/Factory.lean b/Strata/Languages/Core/Factory.lean index 9fb7429bed..1297855c0f 100644 --- a/Strata/Languages/Core/Factory.lean +++ b/Strata/Languages/Core/Factory.lean @@ -39,7 +39,6 @@ def KnownLTys : LTys := t[real], t[Triggers], t[TriggerGroup], - t[errorVoid], -- Note: t[bv] elaborates to (.forAll [] .tcons "bitvec" ). -- We can simply add the following here. t[∀n. bitvec n], diff --git a/Strata/Languages/Laurel/DesugarShortCircuit.lean b/Strata/Languages/Laurel/DesugarShortCircuit.lean index 8c0f602bfa..06996e9ebb 100644 --- a/Strata/Languages/Laurel/DesugarShortCircuit.lean +++ b/Strata/Languages/Laurel/DesugarShortCircuit.lean @@ -49,7 +49,7 @@ private def desugarShortCircuitNode (imperativeCallees : List String) (expr : St /-- Desugar short-circuit operators in a program. -/ def desugarShortCircuit (program : Program) : Program := - let imperativeCallees := program.staticProcedures.map (fun p => p.name.text) + let imperativeCallees := (program.staticProcedures.filter (!·.isFunctional)).map (·.name.text) mapProgram (mapStmtExpr (desugarShortCircuitNode imperativeCallees)) program end -- public section diff --git a/Strata/Languages/Laurel/EliminateReturnStatements.lean b/Strata/Languages/Laurel/EliminateReturnStatements.lean index 7846ec8263..923a3d1e0b 100644 --- a/Strata/Languages/Laurel/EliminateReturnStatements.lean +++ b/Strata/Languages/Laurel/EliminateReturnStatements.lean @@ -35,33 +35,34 @@ private def eliminateReturnStmts (proc : Procedure) : Procedure := let impl' := replaceReturn proc.outputs impl let wrapped := match impl'.val with | .Block stmts none => ⟨.Block stmts (some returnLabel), impl'.source⟩ - | _ => mkMd (.Block [impl'] (some returnLabel)) + | _ => ⟨ .Block [impl'] (some returnLabel), proc.name.source ⟩ { proc with body := .Opaque postconds (some wrapped) mods } | .Transparent body => let body' := replaceReturn proc.outputs body let wrapped := match body'.val with | .Block stmts none => ⟨.Block stmts (some returnLabel), body'.source⟩ - | _ => mkMd (.Block [body'] (some returnLabel)) + | _ => ⟨ .Block [body'] (some returnLabel), proc.name.source ⟩ { proc with body := .Transparent wrapped } | _ => proc where - mkMd (e : StmtExpr) : StmtExprMd := { val := e, source := proc.name.source } - mkVarMd (v : Variable) : VariableMd := { val := v, source := proc.name.source } - /-- Replace `Return val` with `output := val; exit "$return"` (or just `exit` for valueless returns). Uses `mapStmtExpr` for bottom-up traversal. -/ replaceReturn (outputs : List Parameter) (expr : StmtExprMd) : StmtExprMd := mapStmtExpr (fun e => match e.val with | .Return (some val) => + /- Handling valued return is required because the heap param pass introduces valued return in + Strata/Languages/Laurel/HeapParameterizationConstants.lean + We should change that so we can remove this case. + -/ match outputs with | [out] => - let assign := mkMd (.Assign [mkVarMd (.Local out.name)] val) - let exit := mkMd (.Exit returnLabel) + let assign := ⟨ .Assign [⟨ .Local out.name, expr.source ⟩] val, expr.source ⟩ + let exit := ⟨ .Exit returnLabel, expr.source ⟩ ⟨.Block [assign, exit] none, e.source⟩ - | _ => mkMd (.Exit returnLabel) - | .Return none => mkMd (.Exit returnLabel) + | _ => ⟨ .Exit returnLabel, expr.source ⟩ + | .Return none => ⟨ .Exit returnLabel, expr.source ⟩ | _ => e) expr /-- Transform a program by eliminating return statements in all procedure bodies. -/ diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index c3ba856e1c..3f8c216f67 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -109,7 +109,7 @@ private def laurelPipeline : Array LaurelPass := #[ run := fun p _m => let (p', diags) := eliminateReturnsInExpressionTransform p (p', diags.toList, {}) }, - { name := "EliminateValuesInReturns" + { name := "EliminateValuesInReturns" -- Record that it should be before HeapParam run := fun p _m => let (p', diags) := eliminateValuesInReturnsTransform p (p', diags.toList, {}) }, @@ -137,9 +137,6 @@ private def laurelPipeline : Array LaurelPass := #[ { name := "DesugarShortCircuit" run := fun p _ => (desugarShortCircuit p, [], {}) }, - -- { name := "LiftExpressionAssignments" - -- run := fun p m => - -- (liftExpressionAssignments p m [], [], {}) }, { name := "ConstrainedTypeElim" needsResolves := true run := fun p m => diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index 80cdab5987..bb3d814f76 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -341,7 +341,8 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do return ⟨.Var (.Local condVar), source⟩ else modify fun s => { s with prependedStmts := condPrepends ++ s.prependedStmts } - return default + -- Unused value + return ⟨ .Hole, expr.source ⟩ else -- No assignments in branches — recurse normally let seqCond ← transformExpr cond diff --git a/StrataTest/Languages/Core/Examples/TypeDecl.lean b/StrataTest/Languages/Core/Examples/TypeDecl.lean index ae87de0f7d..8b24cec5d4 100644 --- a/StrataTest/Languages/Core/Examples/TypeDecl.lean +++ b/StrataTest/Languages/Core/Examples/TypeDecl.lean @@ -132,7 +132,7 @@ error: ❌ Type checking error. This type declaration's name is reserved! int := bool KnownTypes' names: -[arrow, Sequence, TriggerGroup, real, string, bitvec, Triggers, int, bool, Map, errorVoid, regex] +[arrow, Sequence, TriggerGroup, real, string, bitvec, Triggers, int, bool, Map, regex] -/ #guard_msgs in #eval Core.verify typeDeclPgm4 diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8c_BodilessInlining.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8c_BodilessInlining.lean index d58ecfb4c3..90ff515954 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T8c_BodilessInlining.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T8c_BodilessInlining.lean @@ -41,8 +41,7 @@ procedure caller() " #guard_msgs (drop info, error) in -#eval testInputWithOffset "Postconditions" laurelSource 23 - (fun p => processLaurelFileWithOptions { translateOptions := { inlineFunctionsWhenPossible := true} } p) +#eval testInputWithOffset "Postconditions" laurelSource 23 processLaurelFileKeepIntermediates end Strata.Laurel.BodilessInliningTest end diff --git a/StrataTest/Languages/Laurel/Examples/Objects/T1_MutableFields.lean b/StrataTest/Languages/Laurel/Examples/Objects/T1_MutableFields.lean index 54fffb9365..2d7f7c70aa 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/T1_MutableFields.lean +++ b/StrataTest/Languages/Laurel/Examples/Objects/T1_MutableFields.lean @@ -200,4 +200,4 @@ procedure fieldTargetInMultiAssign() "# #guard_msgs (drop info, error) in -#eval testInputWithOffset "MutableFields" program 14 processLaurelFile +#eval testInputWithOffset "MutableFields" program 14 processLaurelFileKeepIntermediates From 237eabe988128a1ec1678b0af49b17c047b84987 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Fri, 12 Jun 2026 11:00:11 +0000 Subject: [PATCH 073/115] Pass tweaks --- .../Languages/Laurel/DesugarShortCircuit.lean | 2 +- .../Laurel/EliminateValueInReturns.lean | 2 +- .../Laurel/HeapParameterization.lean | 6 +-- Strata/Languages/Laurel/InferHoleTypes.lean | 2 +- .../Laurel/LaurelCompilationPipeline.lean | 10 ++++- Strata/Languages/Laurel/LaurelPass.lean | 39 ++++++++++++++----- Strata/Languages/Laurel/TransparencyPass.lean | 22 ++++++----- 7 files changed, 56 insertions(+), 27 deletions(-) diff --git a/Strata/Languages/Laurel/DesugarShortCircuit.lean b/Strata/Languages/Laurel/DesugarShortCircuit.lean index f7bc886a55..1b0077e60e 100644 --- a/Strata/Languages/Laurel/DesugarShortCircuit.lean +++ b/Strata/Languages/Laurel/DesugarShortCircuit.lean @@ -62,6 +62,6 @@ public def desugarShortCircuitPass : LoweringPass where run := fun p _ => (desugarShortCircuit p, [], {}) comesBefore := [ - ⟨ liftExpressionAssignmentsPass.name, "The desugar short circuit pass introduces if-then-else expressions whose control-flow must be taken into account by the lifting pass."⟩] + ⟨ liftExpressionAssignmentsPass.meta, "The desugar short circuit pass introduces if-then-else expressions whose control-flow must be taken into account by the lifting pass."⟩] end Strata.Laurel diff --git a/Strata/Languages/Laurel/EliminateValueInReturns.lean b/Strata/Languages/Laurel/EliminateValueInReturns.lean index 3b998e6497..d1c1c815f0 100644 --- a/Strata/Languages/Laurel/EliminateValueInReturns.lean +++ b/Strata/Languages/Laurel/EliminateValueInReturns.lean @@ -110,6 +110,6 @@ public def eliminateValueInReturnsPass : LoweringPass where run := fun p _m => let (p', diags) := eliminateValueInReturnsTransform p (p', diags.toList, {}) - comesBefore := [⟨ heapParameterizationPass.name, "eliminate value in returns need to come before any passes that change the amount of output parameters of procedures." ⟩] + comesBefore := [⟨ heapParameterizationPass.meta, "eliminate value in returns need to come before any passes that change the amount of output parameters of procedures." ⟩] end Laurel diff --git a/Strata/Languages/Laurel/HeapParameterization.lean b/Strata/Languages/Laurel/HeapParameterization.lean index 250d82553b..9874a81a4b 100644 --- a/Strata/Languages/Laurel/HeapParameterization.lean +++ b/Strata/Languages/Laurel/HeapParameterization.lean @@ -590,9 +590,9 @@ public def heapParameterizationPass : LoweringPass where run := fun p m => (heapParameterization m p, [], {}) comesBefore := [ - ⟨ typeHierarchyTransformPass.name, "the type hierarchy pass modifies the 'Composite' datatype that is introduced by this pass." ⟩, - ⟨ modifiesClausesTransformPass.name, "the modifies pass refers to several types and variables introduced by heap parameterization: Composite, Field, $heap_in, $heap." ⟩, - ⟨ liftExpressionAssignmentsPass.name, "the heap parameterization pass introduces assignments (to the heap variables) that need to be lifted."⟩ + ⟨ typeHierarchyTransformPass.meta, "the type hierarchy pass modifies the 'Composite' datatype that is introduced by this pass." ⟩, + ⟨ modifiesClausesTransformPass.meta, "the modifies pass refers to several types and variables introduced by heap parameterization: Composite, Field, $heap_in, $heap." ⟩, + ⟨ liftExpressionAssignmentsPass.meta, "the heap parameterization pass introduces assignments (to the heap variables) that need to be lifted."⟩ ] end Strata.Laurel diff --git a/Strata/Languages/Laurel/InferHoleTypes.lean b/Strata/Languages/Laurel/InferHoleTypes.lean index e6735f06fb..59de039333 100644 --- a/Strata/Languages/Laurel/InferHoleTypes.lean +++ b/Strata/Languages/Laurel/InferHoleTypes.lean @@ -199,6 +199,6 @@ public def inferHoleTypesPass : LoweringPass where let (p', diags, stats) := inferHoleTypes m p (p', diags, stats) comesBefore := [ - ⟨ eliminateDeterministicHolesPass.name, "eliminating deterministic holes relies on knowing the type of holes"⟩] + ⟨ eliminateDeterministicHolesPass.meta, "eliminating deterministic holes relies on knowing the type of holes"⟩] end Laurel diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index e200cc9ef3..89a9a65578 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -112,7 +112,7 @@ def comesBeforeRespected : Bool := let names := laurelPipeline.toList.map (·.name) (List.range laurelPipeline.size).zip laurelPipeline.toList |>.all fun (i, p) => p.comesBefore.all fun cb => - match names.findIdx? (· == cb.passName) with + match names.findIdx? (· == cb.pass.name) with | some j => i < j | none => false -- target not in laurelPipeline @@ -190,6 +190,12 @@ private def unorderedCorePipeline : Array (LaurelPass UnorderedCoreWithLaurelTyp liftExpressionAssignmentsPass ] +/-- All pipeline passes, projected to their parameter-free metadata. Combines + the differently-parameterized `laurelPipeline` and `unorderedCorePipeline` + into a single homogeneous list. -/ +def allPassMeta : List PassMeta := + laurelPipeline.toList.map (·.meta) ++ unorderedCorePipeline.toList.map (·.meta) + /-- Translate Laurel Program to Core Program, also returning the lowered Laurel program. @@ -211,7 +217,7 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) if ! passDiags.isEmpty then return (none, passDiags, program, stats) - let unorderedCore := transparencyPass program + let unorderedCore := (transparencyPass.run program model).1 emit "transparencyPass" "core.st" unorderedCore let mut unorderedCore := unorderedCore let mut fnModel := model diff --git a/Strata/Languages/Laurel/LaurelPass.lean b/Strata/Languages/Laurel/LaurelPass.lean index 2ceb0b93d1..4733af19ce 100644 --- a/Strata/Languages/Laurel/LaurelPass.lean +++ b/Strata/Languages/Laurel/LaurelPass.lean @@ -12,27 +12,46 @@ namespace Strata.Laurel public section -structure PassDependency where - passName : String - reason: String - -/-- A single Laurel-to-Laurel pass. Each pass receives the current program and - semantic model and returns the (possibly modified) program, accumulated - diagnostics, and statistics. -/ -structure LaurelPass (Input: Type) (Output: Type) where +mutual + +/-- The parameter-free metadata of a pass, independent of the `Input`/`Output` + types it operates on. `LaurelPass` extends this so that passes with + different parameterizations (e.g. `LaurelPass Program Program` and + `LaurelPass UnorderedCoreWithLaurelTypes UnorderedCoreWithLaurelTypes`) + share a common, type-parameter-free view that can be collected into a + single homogeneous list. -/ +structure PassMeta where /-- Human-readable name, used for profiling and file emission. -/ name : String /-- Whether `resolve` should be run after the pass. -/ needsResolves : Bool := false - /-- The pass action. -/ - run : Input → SemanticModel → Output × List DiagnosticModel × Statistics /-- A description of what this pass does, used for documentation generation. -/ documentation : String + /-- Passes that must run before this one. -/ comesBefore : List PassDependency := [] + /-- Passes that must run after this one. -/ comesAfter : List PassDependency := [] +structure PassDependency where + pass : PassMeta + reason: String +end + +/-- A single Laurel-to-Laurel pass. Each pass receives the current program and + semantic model and returns the (possibly modified) program, accumulated + diagnostics, and statistics. Extends `PassMeta` with the `run` action; the + metadata fields remain directly accessible (e.g. `p.name`). -/ +structure LaurelPass (Input: Type) (Output: Type) extends PassMeta where + /-- The pass action. -/ + run : Input → SemanticModel → Output × List DiagnosticModel × Statistics + abbrev LoweringPass := LaurelPass Laurel.Program Laurel.Program +/-- Project a `LaurelPass` to its parameter-free metadata, discarding the + `run` action and the `Input`/`Output` type parameters. -/ +abbrev LaurelPass.meta {Input Output : Type} (p : LaurelPass Input Output) : PassMeta := + p.toPassMeta + end -- public section end Strata.Laurel diff --git a/Strata/Languages/Laurel/TransparencyPass.lean b/Strata/Languages/Laurel/TransparencyPass.lean index 8a6163769d..328ae58eb2 100644 --- a/Strata/Languages/Laurel/TransparencyPass.lean +++ b/Strata/Languages/Laurel/TransparencyPass.lean @@ -7,6 +7,7 @@ module public import Strata.Languages.Laurel.MapStmtExpr public import Strata.Languages.Laurel.LaurelAST +public import Strata.Languages.Laurel.LaurelPass import Strata.Languages.Laurel.Grammar.AbstractToConcreteTreeTranslator import Strata.DL.Lambda.TypeFactory @@ -158,15 +159,7 @@ private def addFreePostcondition (proc : Procedure) (freePost : StmtExprMd) : Pr { proc with body := .Opaque [freeCond] (some body) [] } | _ => proc -/-- -Transparency pass: translate a Laurel program to the UnorderedCoreWithLaurelTypes IR. - -For each procedure: -- Generate a function with the same signature, named `foo$asFunction` -- If transparent, the function gets a functional body (assertions erased, calls to functional versions) -- If the function has a body, add a free postcondition equating the procedure output to the function --/ -def transparencyPass (program : Program) : UnorderedCoreWithLaurelTypes := +def createFunctionsForTransparentBodies (program : Program) : UnorderedCoreWithLaurelTypes := let (toUpdate, _) := program.staticProcedures.partition (fun p => !p.body.isExternal && !p.isFunctional) let toUpdateNames : Std.HashSet String := toUpdate.foldl (fun s p => s.insert p.name.text) {} -- $asFunction copies for non-external procedures @@ -181,6 +174,17 @@ def transparencyPass (program : Program) : UnorderedCoreWithLaurelTypes := | _ => none { functions, coreProcedures, datatypes, constants := program.constants } +public def transparencyPass : LaurelPass Laurel.Program UnorderedCoreWithLaurelTypes where + name := "TransparencyPass" + comesBefore := [] + documentation := "Translate a Laurel program to the UnorderedCoreWithLaurelTypes IR. +For each procedure: +- Generate a function with the same signature, named `foo$asFunction` +- If transparent, the function gets a functional body (assertions erased, calls to functional versions) +- If the function has a body, add a free postcondition equating the procedure output to the function" + run := fun p _ => + (createFunctionsForTransparentBodies p, [], {}) + open Std (Format ToFormat) def formatUnorderedCoreWithLaurelTypes (p : UnorderedCoreWithLaurelTypes) : Format := From 4ffc62fe07a95857bd740d75fec662010e8bfaeb Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Fri, 12 Jun 2026 11:13:44 +0000 Subject: [PATCH 074/115] Fixes --- .../Laurel/CoreGroupingAndOrdering.lean | 41 ++++++++++++++++++- .../Laurel/LaurelCompilationPipeline.lean | 37 +++++++++-------- .../Laurel/LaurelToCoreTranslator.lean | 1 - Strata/Languages/Laurel/TransparencyPass.lean | 27 +----------- 4 files changed, 62 insertions(+), 44 deletions(-) diff --git a/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean b/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean index 6e45d23941..9e1156ed0d 100644 --- a/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean +++ b/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean @@ -5,7 +5,8 @@ -/ module -public import Strata.Languages.Laurel.TransparencyPass +public import Strata.Languages.Laurel.LaurelAST +public import Strata.Languages.Laurel.LaurelPass import Strata.DL.Lambda.LExpr import StrataDDM.Util.Graph.Tarjan import Strata.Languages.Laurel.Grammar.AbstractToConcreteTreeTranslator @@ -27,6 +28,29 @@ declarations before they are emitted as Strata Core declarations. namespace Strata.Laurel open Lambda (LMonoTy LExpr) +open Std (Format ToFormat) + +/-- +An intermediate representation produced by the transparency pass. +Functions are pure computational procedures (suffixed `$asFunction`); +coreProcedures are the original procedures with any free postconditions +embedded in their `Body.Opaque` postcondition lists. +-/ +public structure UnorderedCoreWithLaurelTypes where + functions : List Procedure + coreProcedures : List Procedure + datatypes : List DatatypeDefinition + constants : List Constant + +public def formatUnorderedCoreWithLaurelTypes (p : UnorderedCoreWithLaurelTypes) : Format := + let datatypeFmts := p.datatypes.map ToFormat.format + let constantFmts := p.constants.map ToFormat.format + let functionFmts := p.functions.map ToFormat.format + let procFmts := p.coreProcedures.map ToFormat.format + Format.joinSep (datatypeFmts ++ constantFmts ++ functionFmts ++ procFmts) "\n\n" + +public instance : ToFormat UnorderedCoreWithLaurelTypes where + format := formatUnorderedCoreWithLaurelTypes /-- Collect all `UserDefined` type names referenced in a `HighType`, including nested ones. -/ def collectTypeRefs : HighTypeMd → List String @@ -227,7 +251,7 @@ Functions are grouped into SCCs (for mutual recursion). Proofs are emitted as individual `procedure` decls. Both participate in the topological ordering so that axioms are available to functions that need them. -/ -public def orderFunctionsAndProcedures (program : UnorderedCoreWithLaurelTypes) : CoreWithLaurelTypes := +def orderFunctionsAndProcedures (program : UnorderedCoreWithLaurelTypes) : CoreWithLaurelTypes := let datatypeDecls := (groupDatatypesByScc' program).map OrderedDecl.datatypes let constantDecls := program.constants.map OrderedDecl.constant let funcNames : Std.HashSet String := @@ -256,4 +280,17 @@ where let members := comp.toList.filterMap fun idx => dtsArr[idx]? if members.isEmpty then none else some members +public def orderingPass : LaurelPass UnorderedCoreWithLaurelTypes CoreWithLaurelTypes where + name := "OrderingPass" + comesBefore := [] + documentation := "Produce a `CoreWithLaurelTypes` from a `UnorderedCoreWithLaurelTypes` by +computing a combined ordering of functions and proofs using the call graph, +then collecting datatypes and constants. + +Functions are grouped into SCCs (for mutual recursion). Proofs are emitted +as individual `procedure` decls. Both participate in the topological ordering +so that axioms are available to functions that need them." + run := fun p _ => + (orderFunctionsAndProcedures p, [], {}) + end Strata.Laurel diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index 89a9a65578..cddd2c6e6e 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -16,6 +16,7 @@ import Strata.Languages.Laurel.TypeHierarchy import Strata.Languages.Laurel.InferHoleTypes import Strata.Languages.Laurel.EliminateDeterministicHoles import Strata.Languages.Laurel.CoreDefinitionsForLaurel +import Strata.Languages.Laurel.CoreGroupingAndOrdering import Strata.Languages.Laurel.LiftImperativeExpressions import Strata.Languages.Laurel.ConstrainedTypeElim import Strata.Languages.Laurel.ContractPass @@ -107,21 +108,6 @@ def laurelPipeline : Array LoweringPass := #[ contractPass ] -/-- Every `comesBefore` constraint is respected by the pipeline order. -/ -def comesBeforeRespected : Bool := - let names := laurelPipeline.toList.map (·.name) - (List.range laurelPipeline.size).zip laurelPipeline.toList |>.all fun (i, p) => - p.comesBefore.all fun cb => - match names.findIdx? (· == cb.pass.name) with - | some j => i < j - | none => false -- target not in laurelPipeline - --- Use `initialize` to check at load time instead of `#guard` which requires --- interpreter IR that is not available for passes defined in `module` files. -initialize do - unless comesBeforeRespected do - throw <| .userError "laurelPipeline: comesBefore ordering constraints violated" - /-- Run all Laurel-to-Laurel lowering passes on a program, returning the lowered program, the semantic model, accumulated diagnostics, and merged statistics. @@ -237,7 +223,7 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) fnModel := m' emit pass.name "unorderedCoreWithLaurelTypes.st" unorderedCore - let coreWithLaurelTypes := orderFunctionsAndProcedures unorderedCore + let coreWithLaurelTypes := (orderingPass.run unorderedCore model).1 emit "CoreWithLaurelTypes" "core.st" coreWithLaurelTypes let initState : TranslateState := { model := fnModel, overflowChecks := options.overflowChecks } @@ -312,4 +298,23 @@ def verifyToDiagnosticModels (program : Program) (options : LaurelVerifyOptions return (results.snd ++ vcDiags).toArray end -- public section + +public def allPasses: Array PassMeta := laurelPipeline.map (fun p => p.meta) ++ + unorderedCorePipeline.map (fun p => p.meta) ++ [transparencyPass.meta, orderingPass.meta] + +/-- Every `comesBefore` constraint is respected by the pipeline order. -/ +def comesBeforeRespected : Bool := + let names := allPasses.map (·.name) + (List.range allPasses.size).zip allPasses.toList |>.all fun (i, p) => + p.comesBefore.all fun cb => + match names.findIdx? (· == cb.pass.name) with + | some j => i < j + | none => false -- target not in laurelPipeline + +-- Use `initialize` to check at load time instead of `#guard` which requires +-- interpreter IR that is not available for passes defined in `module` files. +initialize do + unless comesBeforeRespected do + throw <| .userError "laurelPipeline: comesBefore ordering constraints violated" + end Laurel diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index 6df09b8765..a304d3331d 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -7,7 +7,6 @@ module public import Strata.Languages.Core.Program public import Strata.Languages.Core.Options -public import Strata.Languages.Laurel.CoreGroupingAndOrdering import Strata.Languages.Laurel.Grammar.AbstractToConcreteTreeTranslator import Strata.Util.Tactics public import Strata.Languages.Laurel.Resolution diff --git a/Strata/Languages/Laurel/TransparencyPass.lean b/Strata/Languages/Laurel/TransparencyPass.lean index 328ae58eb2..3314521843 100644 --- a/Strata/Languages/Laurel/TransparencyPass.lean +++ b/Strata/Languages/Laurel/TransparencyPass.lean @@ -8,6 +8,7 @@ module public import Strata.Languages.Laurel.MapStmtExpr public import Strata.Languages.Laurel.LaurelAST public import Strata.Languages.Laurel.LaurelPass +public import Strata.Languages.Laurel.CoreGroupingAndOrdering import Strata.Languages.Laurel.Grammar.AbstractToConcreteTreeTranslator import Strata.DL.Lambda.TypeFactory @@ -29,18 +30,6 @@ namespace Strata.Laurel public section -/-- -An intermediate representation produced by the transparency pass. -Functions are pure computational procedures (suffixed `$asFunction`); -coreProcedures are the original procedures with any free postconditions -embedded in their `Body.Opaque` postcondition lists. --/ -public structure UnorderedCoreWithLaurelTypes where - functions : List Procedure - coreProcedures : List Procedure - datatypes : List DatatypeDefinition - constants : List Constant - /-- Deep traversal that strips all Assert and Assume nodes from a StmtExpr tree. Assert/Assume nodes are replaced with `LiteralBool true`, and Block nodes are collapsed by filtering out trivial `LiteralBool true` leftovers. -/ @@ -176,7 +165,7 @@ def createFunctionsForTransparentBodies (program : Program) : UnorderedCoreWithL public def transparencyPass : LaurelPass Laurel.Program UnorderedCoreWithLaurelTypes where name := "TransparencyPass" - comesBefore := [] + comesBefore := [⟨ orderingPass.meta, "Transparency pass creates functions that are not ordered" ⟩] documentation := "Translate a Laurel program to the UnorderedCoreWithLaurelTypes IR. For each procedure: - Generate a function with the same signature, named `foo$asFunction` @@ -185,17 +174,5 @@ For each procedure: run := fun p _ => (createFunctionsForTransparentBodies p, [], {}) -open Std (Format ToFormat) - -def formatUnorderedCoreWithLaurelTypes (p : UnorderedCoreWithLaurelTypes) : Format := - let datatypeFmts := p.datatypes.map ToFormat.format - let constantFmts := p.constants.map ToFormat.format - let functionFmts := p.functions.map ToFormat.format - let procFmts := p.coreProcedures.map ToFormat.format - Format.joinSep (datatypeFmts ++ constantFmts ++ functionFmts ++ procFmts) "\n\n" - -instance : ToFormat UnorderedCoreWithLaurelTypes where - format := formatUnorderedCoreWithLaurelTypes - end -- public section end Strata.Laurel From 51784515ffd0480c732ab1dbd49c53cc7df92743 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Fri, 12 Jun 2026 12:14:52 +0000 Subject: [PATCH 075/115] More pass refactoring changes --- Strata/Languages/Laurel/ContractPass.lean | 5 ++- .../Laurel/CoreGroupingAndOrdering.lean | 23 +--------- .../Languages/Laurel/DesugarShortCircuit.lean | 2 +- .../Laurel/EliminateValueInReturns.lean | 2 - .../Laurel/HeapParameterization.lean | 12 ++---- .../Laurel/LaurelCompilationPipeline.lean | 42 +++++++++---------- .../Laurel/LaurelToCoreTranslator.lean | 1 + Strata/Languages/Laurel/LaurelTypes.lean | 1 + .../Laurel/LiftImperativeExpressions.lean | 8 ++-- .../Languages/Laurel/MergeAndLiftReturns.lean | 4 +- Strata/Languages/Laurel/ModifiesClauses.lean | 2 + Strata/Languages/Laurel/Resolution.lean | 2 +- Strata/Languages/Laurel/TransparencyPass.lean | 6 +-- Strata/Languages/Laurel/TypeHierarchy.lean | 2 + Strata/Languages/Laurel/UnorderedCore.lean | 34 +++++++++++++++ .../Fundamentals/T3_ControlFlowError2.lean | 17 +++----- docs/verso/LaurelDoc.lean | 34 ++++++++++----- 17 files changed, 110 insertions(+), 87 deletions(-) create mode 100644 Strata/Languages/Laurel/UnorderedCore.lean diff --git a/Strata/Languages/Laurel/ContractPass.lean b/Strata/Languages/Laurel/ContractPass.lean index 65c5513d0b..95af7ca886 100644 --- a/Strata/Languages/Laurel/ContractPass.lean +++ b/Strata/Languages/Laurel/ContractPass.lean @@ -7,6 +7,7 @@ module public import Strata.Languages.Laurel.MapStmtExpr public import Strata.Languages.Laurel.LaurelPass +import Strata.Languages.Laurel.EliminateReturnStatements /-! ## Contract Pass (Laurel → Laurel) @@ -304,12 +305,12 @@ def lowerContracts (program : Program) : Program := { program with staticProcedures := helperProcs ++ transformedProcs } public def contractPass : LoweringPass where - name := "LowerContract" + name := "ContractPass" documentation := "Lowers pre and postcondition to assertions and assumptions around call-sites and procedure bodies" + comesAfter := [⟨ eliminateReturnStatementsPass.meta, "The contract pass wraps the body of procedures to get: assume ; ; assert . Eliminating returns first means that the postcondition assertions are guaranteed to execute."⟩ ] run := fun p _m => let p' := lowerContracts p (p', [], {}) - comesBefore := [] end -- public section end Strata.Laurel diff --git a/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean b/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean index 9e1156ed0d..8c4a8a7558 100644 --- a/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean +++ b/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean @@ -6,6 +6,7 @@ module public import Strata.Languages.Laurel.LaurelAST +public import Strata.Languages.Laurel.UnorderedCore public import Strata.Languages.Laurel.LaurelPass import Strata.DL.Lambda.LExpr import StrataDDM.Util.Graph.Tarjan @@ -30,28 +31,6 @@ namespace Strata.Laurel open Lambda (LMonoTy LExpr) open Std (Format ToFormat) -/-- -An intermediate representation produced by the transparency pass. -Functions are pure computational procedures (suffixed `$asFunction`); -coreProcedures are the original procedures with any free postconditions -embedded in their `Body.Opaque` postcondition lists. --/ -public structure UnorderedCoreWithLaurelTypes where - functions : List Procedure - coreProcedures : List Procedure - datatypes : List DatatypeDefinition - constants : List Constant - -public def formatUnorderedCoreWithLaurelTypes (p : UnorderedCoreWithLaurelTypes) : Format := - let datatypeFmts := p.datatypes.map ToFormat.format - let constantFmts := p.constants.map ToFormat.format - let functionFmts := p.functions.map ToFormat.format - let procFmts := p.coreProcedures.map ToFormat.format - Format.joinSep (datatypeFmts ++ constantFmts ++ functionFmts ++ procFmts) "\n\n" - -public instance : ToFormat UnorderedCoreWithLaurelTypes where - format := formatUnorderedCoreWithLaurelTypes - /-- Collect all `UserDefined` type names referenced in a `HighType`, including nested ones. -/ def collectTypeRefs : HighTypeMd → List String | ⟨.UserDefined name, _⟩ => [name.text] diff --git a/Strata/Languages/Laurel/DesugarShortCircuit.lean b/Strata/Languages/Laurel/DesugarShortCircuit.lean index 1b0077e60e..450672f064 100644 --- a/Strata/Languages/Laurel/DesugarShortCircuit.lean +++ b/Strata/Languages/Laurel/DesugarShortCircuit.lean @@ -62,6 +62,6 @@ public def desugarShortCircuitPass : LoweringPass where run := fun p _ => (desugarShortCircuit p, [], {}) comesBefore := [ - ⟨ liftExpressionAssignmentsPass.meta, "The desugar short circuit pass introduces if-then-else expressions whose control-flow must be taken into account by the lifting pass."⟩] + ⟨ liftImperativeExpressionsPass.meta, "The desugar short circuit pass introduces if-then-else expressions whose control-flow must be taken into account by the lifting pass."⟩] end Strata.Laurel diff --git a/Strata/Languages/Laurel/EliminateValueInReturns.lean b/Strata/Languages/Laurel/EliminateValueInReturns.lean index d1c1c815f0..cc0ed0027a 100644 --- a/Strata/Languages/Laurel/EliminateValueInReturns.lean +++ b/Strata/Languages/Laurel/EliminateValueInReturns.lean @@ -8,7 +8,6 @@ module public import Strata.Languages.Laurel.LaurelAST public import Strata.Languages.Laurel.LaurelPass import Strata.Languages.Laurel.MapStmtExpr -import Strata.Languages.Laurel.HeapParameterization import Strata.Util.Tactics /-! @@ -110,6 +109,5 @@ public def eliminateValueInReturnsPass : LoweringPass where run := fun p _m => let (p', diags) := eliminateValueInReturnsTransform p (p', diags.toList, {}) - comesBefore := [⟨ heapParameterizationPass.meta, "eliminate value in returns need to come before any passes that change the amount of output parameters of procedures." ⟩] end Laurel diff --git a/Strata/Languages/Laurel/HeapParameterization.lean b/Strata/Languages/Laurel/HeapParameterization.lean index 9874a81a4b..69d09d8c95 100644 --- a/Strata/Languages/Laurel/HeapParameterization.lean +++ b/Strata/Languages/Laurel/HeapParameterization.lean @@ -12,9 +12,8 @@ import Strata.Languages.Laurel.Grammar.AbstractToConcreteTreeTranslator import Strata.Languages.Laurel.HeapParameterizationConstants import Strata.Languages.Laurel.LaurelTypes import Strata.Util.Tactics -import Strata.Languages.Laurel.TypeHierarchy -import Strata.Languages.Laurel.ModifiesClauses import Strata.Languages.Laurel.LiftImperativeExpressions +import Strata.Languages.Laurel.EliminateValueInReturns /- Heap Parameterization Pass @@ -239,7 +238,7 @@ def readsHeap (name : Identifier) : TransformM Bool := do def writesHeap (name : Identifier) : TransformM Bool := do return (← get).heapWriters.contains name -def freshVarName : TransformM Identifier := do +private def freshVarName : TransformM Identifier := do let s ← get set { s with freshCounter := s.freshCounter + 1 } return s!"$tmp{s.freshCounter}" @@ -589,11 +588,8 @@ public def heapParameterizationPass : LoweringPass where needsResolves := true run := fun p m => (heapParameterization m p, [], {}) - comesBefore := [ - ⟨ typeHierarchyTransformPass.meta, "the type hierarchy pass modifies the 'Composite' datatype that is introduced by this pass." ⟩, - ⟨ modifiesClausesTransformPass.meta, "the modifies pass refers to several types and variables introduced by heap parameterization: Composite, Field, $heap_in, $heap." ⟩, - ⟨ liftExpressionAssignmentsPass.meta, "the heap parameterization pass introduces assignments (to the heap variables) that need to be lifted."⟩ - ] + comesAfter := [⟨ eliminateValueInReturnsPass.meta, "eliminate value in returns need to come before any passes that change the amount of output parameters of procedures." ⟩] + comesBefore := [⟨ liftImperativeExpressionsPass.meta, "the heap parameterization pass introduces assignments (to the heap variables) that need to be lifted."⟩] end Strata.Laurel diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index cddd2c6e6e..bf3da61c22 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -17,6 +17,7 @@ import Strata.Languages.Laurel.InferHoleTypes import Strata.Languages.Laurel.EliminateDeterministicHoles import Strata.Languages.Laurel.CoreDefinitionsForLaurel import Strata.Languages.Laurel.CoreGroupingAndOrdering +import Strata.Languages.Laurel.TransparencyPass import Strata.Languages.Laurel.LiftImperativeExpressions import Strata.Languages.Laurel.ConstrainedTypeElim import Strata.Languages.Laurel.ContractPass @@ -95,6 +96,7 @@ abbrev TranslateResultWithLaurel := (Option Core.Program) × (List DiagnosticMod def laurelPipeline : Array LoweringPass := #[ typeAliasElimPass, filterNonCompositeModifiesPass, + mergeAndLiftReturnsPass, eliminateValueInReturnsPass, heapParameterizationPass, typeHierarchyTransformPass, @@ -102,7 +104,6 @@ def laurelPipeline : Array LoweringPass := #[ inferHoleTypesPass, eliminateDeterministicHolesPass, desugarShortCircuitPass, - mergeAndLiftReturnsPass, constrainedTypeElimPass, eliminateReturnStatementsPass, contractPass @@ -160,20 +161,9 @@ private def runLaurelPasses return (program, model, allDiags, allStats) -/-- A single pass on the unordered Core representation. Each pass receives the - current `UnorderedCoreWithLaurelTypes` and the semantic model and returns - the (possibly modified) program. -/ -structure CorePass where - /-- Human-readable name, used for profiling and file emission. -/ - name : String - /-- Whether `resolveUnorderedCore` should be run after the pass. -/ - needsResolves : Bool := false - /-- The pass action. -/ - run : UnorderedCoreWithLaurelTypes → SemanticModel → UnorderedCoreWithLaurelTypes - /-- The ordered sequence of passes on the unordered Core representation. -/ private def unorderedCorePipeline : Array (LaurelPass UnorderedCoreWithLaurelTypes UnorderedCoreWithLaurelTypes) := #[ - liftExpressionAssignmentsPass + liftImperativeExpressionsPass ] /-- All pipeline passes, projected to their parameter-free metadata. Combines @@ -300,21 +290,31 @@ def verifyToDiagnosticModels (program : Program) (options : LaurelVerifyOptions end -- public section public def allPasses: Array PassMeta := laurelPipeline.map (fun p => p.meta) ++ - unorderedCorePipeline.map (fun p => p.meta) ++ [transparencyPass.meta, orderingPass.meta] - -/-- Every `comesBefore` constraint is respected by the pipeline order. -/ -def comesBeforeRespected : Bool := + [transparencyPass.meta] ++ + unorderedCorePipeline.map (fun p => p.meta) ++ + [orderingPass.meta] + +/-- Every `comesBefore` and `comesAfter` constraint is respected by the + pipeline order. A `comesBefore` dependency requires this pass to appear + earlier than its target; a `comesAfter` dependency requires it to appear + later. -/ +def orderingRespected : Bool := let names := allPasses.map (·.name) (List.range allPasses.size).zip allPasses.toList |>.all fun (i, p) => - p.comesBefore.all fun cb => + (p.comesBefore.all fun cb => match names.findIdx? (· == cb.pass.name) with | some j => i < j - | none => false -- target not in laurelPipeline + | none => false) -- target not in allPasses + && + (p.comesAfter.all fun ca => + match names.findIdx? (· == ca.pass.name) with + | some j => j < i + | none => false) -- target not in allPasses -- Use `initialize` to check at load time instead of `#guard` which requires -- interpreter IR that is not available for passes defined in `module` files. initialize do - unless comesBeforeRespected do - throw <| .userError "laurelPipeline: comesBefore ordering constraints violated" + unless orderingRespected do + throw <| .userError "laurelPipeline: comesBefore/comesAfter ordering constraints violated" end Laurel diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index a304d3331d..6df09b8765 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -7,6 +7,7 @@ module public import Strata.Languages.Core.Program public import Strata.Languages.Core.Options +public import Strata.Languages.Laurel.CoreGroupingAndOrdering import Strata.Languages.Laurel.Grammar.AbstractToConcreteTreeTranslator import Strata.Util.Tactics public import Strata.Languages.Laurel.Resolution diff --git a/Strata/Languages/Laurel/LaurelTypes.lean b/Strata/Languages/Laurel/LaurelTypes.lean index eaa975b952..b229940167 100644 --- a/Strata/Languages/Laurel/LaurelTypes.lean +++ b/Strata/Languages/Laurel/LaurelTypes.lean @@ -121,6 +121,7 @@ triggers heap parameterization. -/ def isHeapRelevantType (ty : HighType) : Bool := (classifyModifiesHighType ty).isSome + end Strata.Laurel end diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index 078c7f13c1..3e9283f1a1 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -9,6 +9,7 @@ import Strata.Util.Tactics public import Strata.Languages.Laurel.LaurelPass public import Strata.Languages.Laurel.Resolution import Strata.Languages.Laurel.LaurelTypes +import Strata.Languages.Laurel.TransparencyPass namespace Strata namespace Laurel @@ -638,9 +639,10 @@ def liftImperativeExpressionsInCore (uc : UnorderedCoreWithLaurelTypes) coreProcedures := liftedProcs } -public def liftExpressionAssignmentsPass : LaurelPass UnorderedCoreWithLaurelTypes UnorderedCoreWithLaurelTypes where - name := "LiftExpressionAssignments" - documentation := "Lifts assignments that appear in expression contexts into preceding statements. This is necessary because Strata Core does not support assignments within expressions. The pass introduces fresh temporary variables where needed." +public def liftImperativeExpressionsPass : LaurelPass UnorderedCoreWithLaurelTypes UnorderedCoreWithLaurelTypes where + name := "LiftImperativeExpressionsPass" + documentation := "Lifts assignments and other imperative expressions that appear in expression contexts into preceding statements. This is necessary because Strata Core does not support assignments within expressions. The pass introduces fresh temporary variables where needed." + comesAfter := [⟨ transparencyPass.meta, "The imperative expression lifting is only done in procedures, so it comes after the transparency pass"⟩] run := fun p m => (liftImperativeExpressionsInCore p m, [], {}) diff --git a/Strata/Languages/Laurel/MergeAndLiftReturns.lean b/Strata/Languages/Laurel/MergeAndLiftReturns.lean index 1029b33277..dd49aa2925 100644 --- a/Strata/Languages/Laurel/MergeAndLiftReturns.lean +++ b/Strata/Languages/Laurel/MergeAndLiftReturns.lean @@ -8,6 +8,7 @@ module public import Strata.Languages.Laurel.LaurelAST import Strata.Languages.Laurel.Grammar.AbstractToConcreteTreeTranslator import Strata.Util.Tactics +import Strata.Languages.Laurel.EliminateValueInReturns public import Strata.Languages.Laurel.LaurelPass /-! @@ -75,7 +76,7 @@ private def eliminateReturnsInExpression (proc : Procedure) : Procedure × List public section /-- -Transform a program by eliminating returns in all functional procedure bodies. +Transform a program by eliminating returns in all procedure bodies. -/ def mergeAndLiftReturns (program : Program) : Program × List DiagnosticModel := let (procs, diags) := program.staticProcedures.foldl (fun (ps, ds) proc => @@ -91,6 +92,7 @@ public def mergeAndLiftReturnsPass : LoweringPass where name := "MergeAndLiftReturns" documentation := "Attempts to merge and lift returns so that only a single outer return remains, enabling the procedure to be more easily converted to a functional form." needsResolves := true + comesBefore := [⟨ eliminateValueInReturnsPass.meta, "Lifts returns with a value, so the value should not yet have been lowered."⟩] run := fun p _m => let (p', diags) := mergeAndLiftReturns p (p', diags, {}) diff --git a/Strata/Languages/Laurel/ModifiesClauses.lean b/Strata/Languages/Laurel/ModifiesClauses.lean index 76b9481a36..b0af8a7baf 100644 --- a/Strata/Languages/Laurel/ModifiesClauses.lean +++ b/Strata/Languages/Laurel/ModifiesClauses.lean @@ -8,6 +8,7 @@ module public import Strata.Languages.Laurel.Resolution public import Strata.Languages.Laurel.LaurelPass import Strata.Languages.Laurel.HeapParameterizationConstants +import Strata.Languages.Laurel.HeapParameterization import Strata.Languages.Laurel.Grammar.AbstractToConcreteTreeTranslator import Strata.Languages.Laurel.LaurelTypes @@ -246,6 +247,7 @@ public def modifiesClausesTransformPass : LoweringPass where name := "ModifiesClausesTransform" documentation := "Translates modifies clauses into additional ensures clauses. The modifies clause of a procedure is translated into a quantified assertion that states objects not mentioned in the modifies clause have their field values preserved between the input and output heap." needsResolves := true + comesAfter := [⟨ heapParameterizationPass.meta, "the modifies pass refers to several types and variables introduced by heap parameterization: Composite, Field, $heap_in, $heap."⟩] run := fun p m => let (p', diags) := modifiesClausesTransform m p (p', diags, {}) diff --git a/Strata/Languages/Laurel/Resolution.lean b/Strata/Languages/Laurel/Resolution.lean index 5e227e0ae9..d4b7f192e8 100644 --- a/Strata/Languages/Laurel/Resolution.lean +++ b/Strata/Languages/Laurel/Resolution.lean @@ -6,8 +6,8 @@ module public import Strata.Languages.Laurel.LaurelAST +public import Strata.Languages.Laurel.UnorderedCore public import Strata.Languages.Laurel.Grammar.AbstractToConcreteTreeTranslator -public import Strata.Languages.Laurel.TransparencyPass import Strata.Util.Tactics public import Strata.Languages.Laurel.SemanticModel public import Strata.Languages.Laurel.LaurelTypes diff --git a/Strata/Languages/Laurel/TransparencyPass.lean b/Strata/Languages/Laurel/TransparencyPass.lean index 3314521843..1d1068e8e6 100644 --- a/Strata/Languages/Laurel/TransparencyPass.lean +++ b/Strata/Languages/Laurel/TransparencyPass.lean @@ -168,9 +168,9 @@ public def transparencyPass : LaurelPass Laurel.Program UnorderedCoreWithLaurelT comesBefore := [⟨ orderingPass.meta, "Transparency pass creates functions that are not ordered" ⟩] documentation := "Translate a Laurel program to the UnorderedCoreWithLaurelTypes IR. For each procedure: -- Generate a function with the same signature, named `foo$asFunction` -- If transparent, the function gets a functional body (assertions erased, calls to functional versions) -- If the function has a body, add a free postcondition equating the procedure output to the function" + - Generate a function with the same signature, named `foo$asFunction` + - If transparent, the function gets a functional body (assertions erased, calls to functional versions) + - If the function has a body, add a free postcondition equating the procedure output to the function" run := fun p _ => (createFunctionsForTransparentBodies p, [], {}) diff --git a/Strata/Languages/Laurel/TypeHierarchy.lean b/Strata/Languages/Laurel/TypeHierarchy.lean index 414c05c0ea..8ed943997e 100644 --- a/Strata/Languages/Laurel/TypeHierarchy.lean +++ b/Strata/Languages/Laurel/TypeHierarchy.lean @@ -10,6 +10,7 @@ import Strata.Util.Tactics public import Strata.Languages.Laurel.LaurelPass public import Strata.Languages.Laurel.Resolution import Std.Tactic.BVDecide.Normalize.Prop +import Strata.Languages.Laurel.HeapParameterization import Strata.Languages.Laurel.LaurelTypes import Strata.Languages.Laurel.MapStmtExpr @@ -162,6 +163,7 @@ public def typeHierarchyTransformPass : LoweringPass where name := "TypeHierarchyTransform" documentation := "Encodes the object-oriented type hierarchy (inheritance, dynamic dispatch, type tests, and casts) into explicit operations on a flat representation. Composite types with parents are flattened, and dynamic dispatch is resolved through type-test chains." needsResolves := true + comesAfter := [⟨ heapParameterizationPass.meta, "the type hierarchy pass modifies the 'Composite' datatype that is introduced by this pass."⟩] run := fun p m => (typeHierarchyTransform m p, [], {}) diff --git a/Strata/Languages/Laurel/UnorderedCore.lean b/Strata/Languages/Laurel/UnorderedCore.lean new file mode 100644 index 0000000000..97c070902b --- /dev/null +++ b/Strata/Languages/Laurel/UnorderedCore.lean @@ -0,0 +1,34 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +module +public import Strata.Languages.Laurel.LaurelAST +import Strata.Languages.Laurel.Grammar.AbstractToConcreteTreeTranslator + +namespace Strata.Laurel + +open Std (Format ToFormat) +/-- +An intermediate representation produced by the transparency pass. +Functions are pure computational procedures (suffixed `$asFunction`); +coreProcedures are the original procedures with any free postconditions +embedded in their `Body.Opaque` postcondition lists. +-/ +public structure UnorderedCoreWithLaurelTypes where + functions : List Procedure + coreProcedures : List Procedure + datatypes : List DatatypeDefinition + constants : List Constant + +public def formatUnorderedCoreWithLaurelTypes (p : UnorderedCoreWithLaurelTypes) : Format := + let datatypeFmts := p.datatypes.map ToFormat.format + let constantFmts := p.constants.map ToFormat.format + let functionFmts := p.functions.map ToFormat.format + let procFmts := p.coreProcedures.map ToFormat.format + Format.joinSep (datatypeFmts ++ constantFmts ++ functionFmts ++ procFmts) "\n\n" + +public instance : ToFormat UnorderedCoreWithLaurelTypes where + format := formatUnorderedCoreWithLaurelTypes diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError2.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError2.lean index cb77d6dc3f..656009ffca 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError2.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError2.lean @@ -3,26 +3,19 @@ SPDX-License-Identifier: Apache-2.0 OR MIT -/ -module -meta import all StrataTest.Util.TestDiagnostics -meta import all StrataTest.Languages.Laurel.TestExamples - -meta section +import StrataTest.Util.TestLaurel open StrataTest.Util open Strata -namespace Strata.Laurel - -def program := r" +#eval testLaurel <| +#strata +program Laurel; procedure deadCodeAfterIfElse(x: int) returns (r: int) { if x > 0 then { return 1 } else { return 2 }; //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: in a transparent body, if-then-else is only supported as the last statement in a block return 3 }; -" - -#guard_msgs (error, drop all) in -#eval! testInputWithOffset "ControlFlowError" program 14 processLaurelFile +#end diff --git a/docs/verso/LaurelDoc.lean b/docs/verso/LaurelDoc.lean index 0ec29bfa6e..29a8861172 100644 --- a/docs/verso/LaurelDoc.lean +++ b/docs/verso/LaurelDoc.lean @@ -28,13 +28,15 @@ set_option pp.rawOnError true Usage inside a `#doc` block: `{laurelPipelineDocs}` -/ @[block_command] def laurelPipelineDocs : Verso.Doc.Elab.BlockCommandOf Unit := fun () => do - let entries := laurelPipeline.map fun pass => + let entries := allPasses.map fun pass => let base := s!"- **{pass.name}**: {pass.documentation}" - if pass.comesBefore.isEmpty then base - else - let deps := pass.comesBefore.map fun cb => - s!" - Comes before **{cb.pass.name}** because: {cb.reason}" - base ++ "\n" ++ "\n".intercalate deps + let beforeDeps := pass.comesBefore.map fun cb => + s!" - Comes before **{cb.pass.name}** because: {cb.reason}" + let afterDeps := pass.comesAfter.map fun ca => + s!" - Comes after **{ca.pass.name}** because: {ca.reason}" + let deps := beforeDeps ++ afterDeps + if deps.isEmpty then base + else base ++ "\n" ++ "\n".intercalate deps let md := "\n".intercalate entries.toList let some ast := MD4Lean.parse md @@ -43,15 +45,23 @@ def laurelPipelineDocs : Verso.Doc.Elab.BlockCommandOf Unit := fun () => do `(Verso.Doc.Block.concat #[$blocks,*]) /-- Block command that generates a dependency graph for the Laurel pipeline passes - based on the `comesBefore` property. + based on the `comesBefore` and `comesAfter` properties. Usage inside a `#doc` block: `{laurelPipelineDependencyGraph}` -/ @[block_command] def laurelPipelineDependencyGraph : Verso.Doc.Elab.BlockCommandOf Unit := fun () => do -- Collect all edges: (source, target, reason) where source comesBefore target let mut edges : List (String × String × String) := [] - for pass in laurelPipeline do + for pass in allPasses do + -- `pass.comesBefore` declares: pass must run before cb.pass, i.e. pass → cb.pass for cb in pass.comesBefore do edges := edges ++ [(pass.name, cb.pass.name, cb.reason)] + -- `pass.comesAfter` declares: pass must run after ca.pass, i.e. ca.pass → pass + for ca in pass.comesAfter do + edges := edges ++ [(ca.pass.name, pass.name, ca.reason)] + + -- Deduplicate edges with the same (source, target), keeping the first reason. + edges := edges.foldl (init := []) fun acc e => + if acc.any (fun a => a.1 == e.1 && a.2.1 == e.2.1) then acc else acc ++ [e] -- Build the graph as a markdown list showing dependencies let mut md := "**Dependency edges** (A → B means A must run before B):\n\n" @@ -62,11 +72,13 @@ def laurelPipelineDependencyGraph : Verso.Doc.Elab.BlockCommandOf Unit := fun () md := md ++ s!"- **{src}** → **{tgt}**\n - *{reason}*\n" -- Add a textual rendering of the pipeline order with dependency annotations - md := md ++ "\n**Pipeline execution order:**\n\n" + md := md ++ "\n**Pipeline execution order** (→ X: must run before X; ← X: must run after X):\n\n" md := md ++ "```\n" let mut idx := 1 - for pass in laurelPipeline do - let deps := pass.comesBefore.map (s!" → {·.pass.name}") + for pass in allPasses do + let beforeDeps := pass.comesBefore.map (s!" → {·.pass.name}") + let afterDeps := pass.comesAfter.map (s!" ← {·.pass.name}") + let deps := beforeDeps ++ afterDeps let depStr := if deps.isEmpty then "" else String.join deps md := md ++ s!"{idx}. {pass.name}{depStr}\n" idx := idx + 1 From 0ba057d45aa7ef7b7e952e00df6b5782ea78d429 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Fri, 12 Jun 2026 14:55:10 +0000 Subject: [PATCH 076/115] Fixes --- Strata/Languages/Laurel/LiftImperativeExpressions.lean | 1 + .../Laurel/Examples/Fundamentals/T3_ControlFlow.lean | 9 --------- .../Laurel/LiftExpressionAssignmentsTest.lean | 2 +- .../Laurel/LiftImperativeCallsInAssertTest.lean | 2 +- StrataTest/Util/TestLaurel.lean | 10 ++++++++++ 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index 3e9283f1a1..458c0eb32a 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -643,6 +643,7 @@ public def liftImperativeExpressionsPass : LaurelPass UnorderedCoreWithLaurelTyp name := "LiftImperativeExpressionsPass" documentation := "Lifts assignments and other imperative expressions that appear in expression contexts into preceding statements. This is necessary because Strata Core does not support assignments within expressions. The pass introduces fresh temporary variables where needed." comesAfter := [⟨ transparencyPass.meta, "The imperative expression lifting is only done in procedures, so it comes after the transparency pass"⟩] + needsResolves := true run := fun p m => (liftImperativeExpressionsInCore p m, [], {}) diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean index 1d7c711925..930bb559af 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean @@ -9,19 +9,10 @@ import StrataTest.Util.TestLaurel open StrataTest.Util open Strata -<<<<<<< HEAD -namespace Strata.Laurel - -def program := r" - -procedure returnAtEnd(x: int) returns (r: int) -{ -======= #eval testLaurel <| #strata program Laurel; function returnAtEnd(x: int) returns (r: int) { ->>>>>>> origin/main2 if x > 0 then { if x == 1 then { return 1 diff --git a/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean b/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean index c79058a34f..00e0423b99 100644 --- a/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean +++ b/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean @@ -22,7 +22,7 @@ namespace Strata.Laurel private def parseLaurelAndLift (program : StrataDDM.Program) : IO Program := do let laurelProgram ← translateLaurel program let result := resolve laurelProgram - pure (liftExpressionAssignments result.model result.program) + pure (liftExpressionAssignments result.program result.model []) private def printLifted (program : StrataDDM.Program) : IO Unit := do let lifted ← parseLaurelAndLift program diff --git a/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean b/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean index 4de896a632..f1602a5e06 100644 --- a/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean +++ b/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean @@ -22,7 +22,7 @@ namespace Strata.Laurel private def parseLaurelAndLift (program : StrataDDM.Program) : IO Program := do let laurelProgram ← translateLaurel program let result := resolve laurelProgram - pure (liftExpressionAssignments program model ["impure", "multi_out"]) + pure (liftExpressionAssignments result.program result.model ["impure", "multi_out"]) private def printLifted (program : StrataDDM.Program) : IO Unit := do let lifted ← parseLaurelAndLift program diff --git a/StrataTest/Util/TestLaurel.lean b/StrataTest/Util/TestLaurel.lean index 577c3b47e2..933f320c11 100644 --- a/StrataTest/Util/TestLaurel.lean +++ b/StrataTest/Util/TestLaurel.lean @@ -262,6 +262,16 @@ def testLaurel (block : SourcedProgram) (options : LaurelVerifyOptions := defaultLaurelTestOptions) : IO Unit := runAndCheck block (runLaurelPipelineRaw · options) +/-- Path to the directory for intermediate files, inside the build directory. + Resolved from the current working directory so it works on any machine. -/ +def buildDir : IO String := do + let cwd ← IO.currentDir + return s!"{cwd}/.lake/build/intermediatePrograms/" + +def testLaurelKeepIntermediates (block : SourcedProgram) : IO Unit := do + let dir ← buildDir + runAndCheck block (runLaurelPipelineRaw · { translateOptions := { keepAllFilesPrefix := dir}}) + /-- Like `testLaurel` but skips SMT verification (translate + resolve only). Use when the test only cares about resolution, not the verifier — e.g. "shadowing in nested blocks is OK", or asserting a specific resolution From 1627ce4548d28952872e982a487706821f9b7f52 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Fri, 12 Jun 2026 16:55:43 +0000 Subject: [PATCH 077/115] Fixes --- Strata/Languages/Laurel/ContractPass.lean | 3 +- .../Laurel/CoreGroupingAndOrdering.lean | 1 - .../Examples/Fundamentals/T3_ControlFlow.lean | 26 ++++++------ docs/verso/LaurelDoc.lean | 42 ++++++++++++------- 4 files changed, 42 insertions(+), 30 deletions(-) diff --git a/Strata/Languages/Laurel/ContractPass.lean b/Strata/Languages/Laurel/ContractPass.lean index 95af7ca886..02893d7983 100644 --- a/Strata/Languages/Laurel/ContractPass.lean +++ b/Strata/Languages/Laurel/ContractPass.lean @@ -307,7 +307,8 @@ def lowerContracts (program : Program) : Program := public def contractPass : LoweringPass where name := "ContractPass" documentation := "Lowers pre and postcondition to assertions and assumptions around call-sites and procedure bodies" - comesAfter := [⟨ eliminateReturnStatementsPass.meta, "The contract pass wraps the body of procedures to get: assume ; ; assert . Eliminating returns first means that the postcondition assertions are guaranteed to execute."⟩ ] + comesAfter := [⟨ eliminateReturnStatementsPass.meta, "The contract pass wraps the body of procedures to get: `assume preconditions; body; assert postconditions`. Eliminating returns first means that the postcondition assertions are guaranteed to execute."⟩ ] + needsResolves := true run := fun p _m => let p' := lowerContracts p (p', [], {}) diff --git a/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean b/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean index 8c4a8a7558..619c8a4235 100644 --- a/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean +++ b/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean @@ -265,7 +265,6 @@ public def orderingPass : LaurelPass UnorderedCoreWithLaurelTypes CoreWithLaurel documentation := "Produce a `CoreWithLaurelTypes` from a `UnorderedCoreWithLaurelTypes` by computing a combined ordering of functions and proofs using the call graph, then collecting datatypes and constants. - Functions are grouped into SCCs (for mutual recursion). Proofs are emitted as individual `procedure` decls. Both participate in the topological ordering so that axioms are available to functions that need them." diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean index 930bb559af..83f3a702a2 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlow.lean @@ -12,7 +12,7 @@ open Strata #eval testLaurel <| #strata program Laurel; -function returnAtEnd(x: int) returns (r: int) { +procedure returnAtEnd(x: int) returns (r: int) { if x > 0 then { if x == 1 then { return 1 @@ -29,6 +29,18 @@ function elseWithCall(): int if true then 3 else returnAtEnd(3) }; +procedure testFunctions() + opaque +{ + assert returnAtEnd(1) == 1; + assert returnAtEnd(1) == 2; +//^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion could not be proved + + assert guardInFunction(1) == 1; + assert guardInFunction(1) == 2 +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion could not be proved +}; + procedure guardInFunction(x: int) returns (r: int) { if x > 0 then { @@ -42,18 +54,6 @@ procedure guardInFunction(x: int) returns (r: int) return 3 }; -procedure testFunctions() - opaque -{ - assert returnAtEnd(1) == 1; - assert returnAtEnd(1) == 2; -//^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion could not be proved - - assert guardInFunction(1) == 1; - assert guardInFunction(1) == 2 -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion could not be proved -}; - procedure guards(a: int) returns (r: int) { var b: int := a + 2; diff --git a/docs/verso/LaurelDoc.lean b/docs/verso/LaurelDoc.lean index 29a8861172..c1202937be 100644 --- a/docs/verso/LaurelDoc.lean +++ b/docs/verso/LaurelDoc.lean @@ -24,10 +24,12 @@ open Verso.Genre.Manual.InlineLean set_option pp.rawOnError true -/-- Block command that generates documentation for all Laurel pipeline passes. - Usage inside a `#doc` block: `{laurelPipelineDocs}` -/ -@[block_command] -def laurelPipelineDocs : Verso.Doc.Elab.BlockCommandOf Unit := fun () => do +/-- Markdown documentation for all Laurel passes, including their + `comesBefore`/`comesAfter` ordering rationales. Note: pass + `documentation`/`reason` strings are rendered as Markdown, so avoid raw + `` text (it is treated as inline HTML and crashes Verso's + converter); use backticks for inline code instead. -/ +def laurelPipelineDocsMarkdown : String := let entries := allPasses.map fun pass => let base := s!"- **{pass.name}**: {pass.documentation}" let beforeDeps := pass.comesBefore.map fun cb => @@ -37,18 +39,11 @@ def laurelPipelineDocs : Verso.Doc.Elab.BlockCommandOf Unit := fun () => do let deps := beforeDeps ++ afterDeps if deps.isEmpty then base else base ++ "\n" ++ "\n".intercalate deps + "\n".intercalate entries.toList - let md := "\n".intercalate entries.toList - let some ast := MD4Lean.parse md - | Lean.throwError "Failed to parse laurelPipelineDocumentation as Markdown" - let blocks ← ast.blocks.mapM (Markdown.blockFromMarkdown · (handleHeaders := Markdown.strongEmphHeaders)) - `(Verso.Doc.Block.concat #[$blocks,*]) - -/-- Block command that generates a dependency graph for the Laurel pipeline passes - based on the `comesBefore` and `comesAfter` properties. - Usage inside a `#doc` block: `{laurelPipelineDependencyGraph}` -/ -@[block_command] -def laurelPipelineDependencyGraph : Verso.Doc.Elab.BlockCommandOf Unit := fun () => do +/-- Markdown dependency graph for the Laurel passes, derived from the + `comesBefore`/`comesAfter` properties. -/ +def laurelPipelineDependencyGraphMarkdown : String := Id.run do -- Collect all edges: (source, target, reason) where source comesBefore target let mut edges : List (String × String × String) := [] for pass in allPasses do @@ -83,7 +78,24 @@ def laurelPipelineDependencyGraph : Verso.Doc.Elab.BlockCommandOf Unit := fun () md := md ++ s!"{idx}. {pass.name}{depStr}\n" idx := idx + 1 md := md ++ "```\n" + return md +/-- Block command that generates documentation for all Laurel pipeline passes. + Usage inside a `#doc` block: `{laurelPipelineDocs}` -/ +@[block_command] +def laurelPipelineDocs : Verso.Doc.Elab.BlockCommandOf Unit := fun () => do + let md := laurelPipelineDocsMarkdown + let some ast := MD4Lean.parse md + | Lean.throwError "Failed to parse laurelPipelineDocumentation as Markdown" + let blocks ← ast.blocks.mapM (Markdown.blockFromMarkdown · (handleHeaders := Markdown.strongEmphHeaders)) + `(Verso.Doc.Block.concat #[$blocks,*]) + +/-- Block command that generates a dependency graph for the Laurel pipeline passes + based on the `comesBefore` and `comesAfter` properties. + Usage inside a `#doc` block: `{laurelPipelineDependencyGraph}` -/ +@[block_command] +def laurelPipelineDependencyGraph : Verso.Doc.Elab.BlockCommandOf Unit := fun () => do + let md := laurelPipelineDependencyGraphMarkdown let some ast := MD4Lean.parse md | Lean.throwError "Failed to parse laurelPipelineDependencyGraph as Markdown" let blocks ← ast.blocks.mapM (Markdown.blockFromMarkdown · (handleHeaders := Markdown.strongEmphHeaders)) From 816a7388113c2a6e986c747587068b22770a061d Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Sat, 13 Jun 2026 14:05:53 +0000 Subject: [PATCH 078/115] Include final pass in the all pass list --- .../Languages/Laurel/ConstrainedTypeElim.lean | 2 +- Strata/Languages/Laurel/ContractPass.lean | 2 +- .../Laurel/CoreGroupingAndOrdering.lean | 2 +- .../Languages/Laurel/DesugarShortCircuit.lean | 2 +- .../Laurel/EliminateDeterministicHoles.lean | 2 +- .../Laurel/EliminateReturnStatements.lean | 2 +- .../Laurel/EliminateValueInReturns.lean | 2 +- .../Laurel/HeapParameterization.lean | 2 +- Strata/Languages/Laurel/InferHoleTypes.lean | 2 +- .../Laurel/LaurelCompilationPipeline.lean | 30 ++++++++----------- Strata/Languages/Laurel/LaurelPass.lean | 11 ++++++- ...lator.lean => LaurelToCoreSchemaPass.lean} | 27 +++++++++++------ .../Laurel/LiftImperativeExpressions.lean | 2 +- .../Languages/Laurel/MergeAndLiftReturns.lean | 2 +- Strata/Languages/Laurel/ModifiesClauses.lean | 4 +-- Strata/Languages/Laurel/TransparencyPass.lean | 2 +- Strata/Languages/Laurel/TypeAliasElim.lean | 2 +- Strata/Languages/Laurel/TypeHierarchy.lean | 2 +- 18 files changed, 56 insertions(+), 44 deletions(-) rename Strata/Languages/Laurel/{LaurelToCoreTranslator.lean => LaurelToCoreSchemaPass.lean} (96%) diff --git a/Strata/Languages/Laurel/ConstrainedTypeElim.lean b/Strata/Languages/Laurel/ConstrainedTypeElim.lean index 10450b90ad..28d8170255 100644 --- a/Strata/Languages/Laurel/ConstrainedTypeElim.lean +++ b/Strata/Languages/Laurel/ConstrainedTypeElim.lean @@ -254,7 +254,7 @@ public def constrainedTypeElimPass : LoweringPass where name := "ConstrainedTypeElim" documentation := "Eliminates constrained types by replacing them with their base types and generating constraint-checking functions and witness procedures. Type tests against constrained types are rewritten to call the generated constraint function." needsResolves := true - run := fun p m => + run := fun p m _ => let (p', diags) := constrainedTypeElim m p (p', diags, {}) diff --git a/Strata/Languages/Laurel/ContractPass.lean b/Strata/Languages/Laurel/ContractPass.lean index 02893d7983..9d93da97e1 100644 --- a/Strata/Languages/Laurel/ContractPass.lean +++ b/Strata/Languages/Laurel/ContractPass.lean @@ -309,7 +309,7 @@ public def contractPass : LoweringPass where documentation := "Lowers pre and postcondition to assertions and assumptions around call-sites and procedure bodies" comesAfter := [⟨ eliminateReturnStatementsPass.meta, "The contract pass wraps the body of procedures to get: `assume preconditions; body; assert postconditions`. Eliminating returns first means that the postcondition assertions are guaranteed to execute."⟩ ] needsResolves := true - run := fun p _m => + run := fun p _m _ => let p' := lowerContracts p (p', [], {}) diff --git a/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean b/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean index 619c8a4235..34b702944a 100644 --- a/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean +++ b/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean @@ -268,7 +268,7 @@ then collecting datatypes and constants. Functions are grouped into SCCs (for mutual recursion). Proofs are emitted as individual `procedure` decls. Both participate in the topological ordering so that axioms are available to functions that need them." - run := fun p _ => + run := fun p _ _ => (orderFunctionsAndProcedures p, [], {}) end Strata.Laurel diff --git a/Strata/Languages/Laurel/DesugarShortCircuit.lean b/Strata/Languages/Laurel/DesugarShortCircuit.lean index 450672f064..b8d65c1e40 100644 --- a/Strata/Languages/Laurel/DesugarShortCircuit.lean +++ b/Strata/Languages/Laurel/DesugarShortCircuit.lean @@ -59,7 +59,7 @@ end -- public section public def desugarShortCircuitPass : LoweringPass where name := "DesugarShortCircuit" documentation := "Rewrites short-circuit boolean operators (`&&` and `||`) into equivalent conditional expressions. This simplifies subsequent passes and the final translation to Core, which does not have short-circuit semantics built in." - run := fun p _ => + run := fun p _ _ => (desugarShortCircuit p, [], {}) comesBefore := [ ⟨ liftImperativeExpressionsPass.meta, "The desugar short circuit pass introduces if-then-else expressions whose control-flow must be taken into account by the lifting pass."⟩] diff --git a/Strata/Languages/Laurel/EliminateDeterministicHoles.lean b/Strata/Languages/Laurel/EliminateDeterministicHoles.lean index db6c5ee245..d7286e86c7 100644 --- a/Strata/Languages/Laurel/EliminateDeterministicHoles.lean +++ b/Strata/Languages/Laurel/EliminateDeterministicHoles.lean @@ -93,7 +93,7 @@ end -- public section public def eliminateDeterministicHolesPass : LoweringPass where name := "EliminateDeterministicHoles" documentation := "Replaces every deterministic hole with a call to a freshly generated uninterpreted function. After this pass the program contains only non-deterministic holes. Assumes `InferHoleTypes` has already annotated holes with types." - run := fun p _m => + run := fun p _m _ => let (p', stats) := eliminateDeterministicHoles p (p', [], stats) diff --git a/Strata/Languages/Laurel/EliminateReturnStatements.lean b/Strata/Languages/Laurel/EliminateReturnStatements.lean index f9f3dee555..d2a7743765 100644 --- a/Strata/Languages/Laurel/EliminateReturnStatements.lean +++ b/Strata/Languages/Laurel/EliminateReturnStatements.lean @@ -73,7 +73,7 @@ def eliminateReturnStatements (program : Program) : Program := public def eliminateReturnStatementsPass : LoweringPass where name := "EliminateReturnStatements" documentation := "Lower return statements to exit statements. Wrap each procedure body with a 'return' block" - run := fun p _m => + run := fun p _m _ => let p' := eliminateReturnStatements p (p', [], {}) -- comesBefore := [contractPass] diff --git a/Strata/Languages/Laurel/EliminateValueInReturns.lean b/Strata/Languages/Laurel/EliminateValueInReturns.lean index cc0ed0027a..1dfdca2779 100644 --- a/Strata/Languages/Laurel/EliminateValueInReturns.lean +++ b/Strata/Languages/Laurel/EliminateValueInReturns.lean @@ -106,7 +106,7 @@ end -- public section public def eliminateValueInReturnsPass : LoweringPass where name := "EliminateValueInReturns" documentation := "Rewrites `return expr` into `outParam := expr; return` for imperative procedures that have an output parameter. This decouples the return-value assignment from the final Core translation, which no longer needs to know about output parameters when translating returns." - run := fun p _m => + run := fun p _m _ => let (p', diags) := eliminateValueInReturnsTransform p (p', diags.toList, {}) diff --git a/Strata/Languages/Laurel/HeapParameterization.lean b/Strata/Languages/Laurel/HeapParameterization.lean index 69d09d8c95..598614199a 100644 --- a/Strata/Languages/Laurel/HeapParameterization.lean +++ b/Strata/Languages/Laurel/HeapParameterization.lean @@ -586,7 +586,7 @@ public def heapParameterizationPass : LoweringPass where name := "HeapParameterization" documentation := "Transforms procedures that interact with the heap by adding explicit heap parameters. The heap is modeled as `Map Composite (Map Field Box)`. Procedures that write the heap receive both an input and output heap parameter; procedures that only read the heap receive an input heap parameter. Field reads and writes are rewritten to use `readField` and `updateField` functions." needsResolves := true - run := fun p m => + run := fun p m _ => (heapParameterization m p, [], {}) comesAfter := [⟨ eliminateValueInReturnsPass.meta, "eliminate value in returns need to come before any passes that change the amount of output parameters of procedures." ⟩] comesBefore := [⟨ liftImperativeExpressionsPass.meta, "the heap parameterization pass introduces assignments (to the heap variables) that need to be lifted."⟩] diff --git a/Strata/Languages/Laurel/InferHoleTypes.lean b/Strata/Languages/Laurel/InferHoleTypes.lean index 59de039333..9d80c36a33 100644 --- a/Strata/Languages/Laurel/InferHoleTypes.lean +++ b/Strata/Languages/Laurel/InferHoleTypes.lean @@ -195,7 +195,7 @@ end -- public section public def inferHoleTypesPass : LoweringPass where name := "InferHoleTypes" documentation := "Annotates every verification hole (`.Hole`) in the program with a type inferred from context. This type information is needed by subsequent passes that replace holes with uninterpreted functions or nondeterministic values." - run := fun p m => + run := fun p m _ => let (p', diags, stats) := inferHoleTypes m p (p', diags, stats) comesBefore := [ diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index bf3da61c22..6d0e5eba67 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -5,7 +5,7 @@ -/ module -public import Strata.Languages.Laurel.LaurelToCoreTranslator +public import Strata.Languages.Laurel.LaurelToCoreSchemaPass import Strata.Languages.Laurel.DesugarShortCircuit import Strata.Languages.Laurel.EliminateReturnStatements import Strata.Languages.Laurel.MergeAndLiftReturns @@ -118,6 +118,7 @@ program state after each named Laurel pass is written to `{prefix}.{n}.{passName}.laurel.st`. -/ private def runLaurelPasses + (options: LaurelTranslateOptions) (pctx : Strata.Pipeline.PipelineContext) (program : Program) : PipelineM (Program × SemanticModel × List DiagnosticModel × Statistics) := do let program := { program with @@ -139,7 +140,7 @@ private def runLaurelPasses let mut allStats : Statistics := {} for pass in laurelPipeline do - let (program', diags, stats) ← pctx.withPhase pass.name do pure (pass.run program model) + let (program', diags, stats) ← pctx.withPhase pass.name do pure (pass.run program model options) program := program' allDiags := allDiags ++ diags allStats := allStats.merge stats @@ -185,7 +186,7 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) | some ctx => pure ctx | none => Strata.Pipeline.PipelineContext.create (outputMode := .quiet) runPipelineM options.keepAllFilesPrefix do - let (program, model, passDiags, stats) ← runLaurelPasses pctx program + let (program, model, passDiags, stats) ← runLaurelPasses options pctx program -- This early return is a simple way to protect against duplicative errors. Without this return, -- resolution errors reported by Laurel would also be reported by Core. -- There might be better solution that allows getting some resolution errors from Laurel and some verification errors from Core, @@ -193,13 +194,13 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) if ! passDiags.isEmpty then return (none, passDiags, program, stats) - let unorderedCore := (transparencyPass.run program model).1 + let unorderedCore := (transparencyPass.run program model options).1 emit "transparencyPass" "core.st" unorderedCore let mut unorderedCore := unorderedCore let mut fnModel := model for pass in unorderedCorePipeline do - unorderedCore := (pass.run unorderedCore fnModel).1 + unorderedCore := (pass.run unorderedCore fnModel options).1 if pass.needsResolves then let compositeTypes := program.types.filter (fun t => match t with | .Composite _ => true | _ => false) let (uc', m', errors) := resolveUnorderedCore unorderedCore (some fnModel) compositeTypes @@ -213,22 +214,15 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) fnModel := m' emit pass.name "unorderedCoreWithLaurelTypes.st" unorderedCore - let coreWithLaurelTypes := (orderingPass.run unorderedCore model).1 + let coreWithLaurelTypes := (orderingPass.run unorderedCore model options).1 emit "CoreWithLaurelTypes" "core.st" coreWithLaurelTypes - let initState : TranslateState := { model := fnModel, overflowChecks := options.overflowChecks } - let (coreProgramOption, translateState) := - runTranslateM initState (translateLaurelToCore options coreWithLaurelTypes) - -- Because of the duplication between functions and procedures, this translation is liable to create duplicate diagnostics - let mut allDiagnostics: List DiagnosticModel := passDiags ++ translateState.diagnostics.eraseDups; + let (coreProgram, coreDiagnostics, _) := laurelToCoreSchemaPass.run coreWithLaurelTypes model options + let mut allDiagnostics: List DiagnosticModel := passDiags ++ coreDiagnostics; - if !translateState.coreDiagnostics.isEmpty && allDiagnostics.isEmpty then - allDiagnostics := allDiagnostics ++ translateState.coreDiagnostics - - if coreProgramOption.isSome then - emit "Core" "core.st" coreProgramOption.get! + emit "Core" "core.st" coreProgram let coreProgramOption := - if translateState.coreDiagnostics.isEmpty then coreProgramOption else none + if coreDiagnostics.isEmpty then some coreProgram else none return (coreProgramOption, allDiagnostics, program, stats) /-- @@ -292,7 +286,7 @@ end -- public section public def allPasses: Array PassMeta := laurelPipeline.map (fun p => p.meta) ++ [transparencyPass.meta] ++ unorderedCorePipeline.map (fun p => p.meta) ++ - [orderingPass.meta] + [orderingPass.meta, laurelToCoreSchemaPass.meta] /-- Every `comesBefore` and `comesAfter` constraint is respected by the pipeline order. A `comesBefore` dependency requires this pass to appear diff --git a/Strata/Languages/Laurel/LaurelPass.lean b/Strata/Languages/Laurel/LaurelPass.lean index 4733af19ce..130ae2cf09 100644 --- a/Strata/Languages/Laurel/LaurelPass.lean +++ b/Strata/Languages/Laurel/LaurelPass.lean @@ -7,11 +7,20 @@ module public import Strata.Languages.Laurel.SemanticModel public import Strata.Util.Statistics +public import Strata.Languages.Core.Options namespace Strata.Laurel public section +structure LaurelTranslateOptions where + inlineFunctionsWhenPossible : Bool := false + overflowChecks : Core.OverflowChecks := {} + keepAllFilesPrefix : Option String := none + +instance : Inhabited LaurelTranslateOptions where + default := {} + mutual /-- The parameter-free metadata of a pass, independent of the `Input`/`Output` @@ -43,7 +52,7 @@ end metadata fields remain directly accessible (e.g. `p.name`). -/ structure LaurelPass (Input: Type) (Output: Type) extends PassMeta where /-- The pass action. -/ - run : Input → SemanticModel → Output × List DiagnosticModel × Statistics + run : Input → SemanticModel → LaurelTranslateOptions → Output × List DiagnosticModel × Statistics abbrev LoweringPass := LaurelPass Laurel.Program Laurel.Program diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreSchemaPass.lean similarity index 96% rename from Strata/Languages/Laurel/LaurelToCoreTranslator.lean rename to Strata/Languages/Laurel/LaurelToCoreSchemaPass.lean index 6df09b8765..ad7cb408ee 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreSchemaPass.lean @@ -6,7 +6,7 @@ module public import Strata.Languages.Core.Program -public import Strata.Languages.Core.Options + public import Strata.Languages.Laurel.CoreGroupingAndOrdering import Strata.Languages.Laurel.Grammar.AbstractToConcreteTreeTranslator import Strata.Util.Tactics @@ -577,14 +577,6 @@ def translateProcedure (proc : Procedure) : TranslateM Core.Procedure := do let spec : Core.Procedure.Spec := { preconditions, postconditions } return { header, spec, body := .structured body } -structure LaurelTranslateOptions where - inlineFunctionsWhenPossible : Bool := false - overflowChecks : Core.OverflowChecks := {} - keepAllFilesPrefix : Option String := none - -instance : Inhabited LaurelTranslateOptions where - default := {} - structure LaurelVerifyOptions where translateOptions : LaurelTranslateOptions := {} verifyOptions : Core.VerifyOptions := .default @@ -718,5 +710,22 @@ def translateLaurelToCore (options: LaurelTranslateOptions) (ordered : CoreWithL pure { decls := coreDecls } +public def laurelToCoreSchemaPass : LaurelPass CoreWithLaurelTypes Core.Program where + name := "LaurelToCoreSchemaPass" + comesBefore := [] + documentation := "Produce a `Core` program from a `CoreWithLaurelTypes` program. Intended to be dumb 1-to-1 translation. However, there are several smart translations still happening: + - The @[cases] parameter is inferred for recursive functions. + - Laurel parameter definitions are translated to Core ones. + - Laurel calling conventions are translated to Core ones." + run := fun p fnModel options => + let initState : TranslateState := { model := fnModel, overflowChecks := options.overflowChecks } + let (coreProgramOption, translateState) := + runTranslateM initState (translateLaurelToCore options p) + let diagnostics : List DiagnosticModel := + -- Because of the duplication between functions and procedures, this translation is liable to create duplicate diagnostics + let d := translateState.diagnostics.eraseDups + if d.isEmpty then translateState.coreDiagnostics else d + (coreProgramOption.getD default, diagnostics, {}) + end -- public section end Laurel diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index 458c0eb32a..5d37ab8a12 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -644,7 +644,7 @@ public def liftImperativeExpressionsPass : LaurelPass UnorderedCoreWithLaurelTyp documentation := "Lifts assignments and other imperative expressions that appear in expression contexts into preceding statements. This is necessary because Strata Core does not support assignments within expressions. The pass introduces fresh temporary variables where needed." comesAfter := [⟨ transparencyPass.meta, "The imperative expression lifting is only done in procedures, so it comes after the transparency pass"⟩] needsResolves := true - run := fun p m => + run := fun p m _ => (liftImperativeExpressionsInCore p m, [], {}) end Laurel diff --git a/Strata/Languages/Laurel/MergeAndLiftReturns.lean b/Strata/Languages/Laurel/MergeAndLiftReturns.lean index dd49aa2925..06f44102d1 100644 --- a/Strata/Languages/Laurel/MergeAndLiftReturns.lean +++ b/Strata/Languages/Laurel/MergeAndLiftReturns.lean @@ -93,7 +93,7 @@ public def mergeAndLiftReturnsPass : LoweringPass where documentation := "Attempts to merge and lift returns so that only a single outer return remains, enabling the procedure to be more easily converted to a functional form." needsResolves := true comesBefore := [⟨ eliminateValueInReturnsPass.meta, "Lifts returns with a value, so the value should not yet have been lowered."⟩] - run := fun p _m => + run := fun p _m _ => let (p', diags) := mergeAndLiftReturns p (p', diags, {}) diff --git a/Strata/Languages/Laurel/ModifiesClauses.lean b/Strata/Languages/Laurel/ModifiesClauses.lean index b0af8a7baf..8e3414f43c 100644 --- a/Strata/Languages/Laurel/ModifiesClauses.lean +++ b/Strata/Languages/Laurel/ModifiesClauses.lean @@ -238,7 +238,7 @@ end -- public section public def filterNonCompositeModifiesPass : LoweringPass where name := "FilterNonCompositeModifies" documentation := "Filters modifies clauses that refer to non-composite types (e.g. primitives), which cannot be heap-parameterized. Emits a warning for each removed clause. Should run before heap parameterization so that phase remains agnostic to modifies clauses." - run := fun p m => + run := fun p m _ => let (p', diags) := filterNonCompositeModifies m p (p', diags, {}) @@ -248,7 +248,7 @@ public def modifiesClausesTransformPass : LoweringPass where documentation := "Translates modifies clauses into additional ensures clauses. The modifies clause of a procedure is translated into a quantified assertion that states objects not mentioned in the modifies clause have their field values preserved between the input and output heap." needsResolves := true comesAfter := [⟨ heapParameterizationPass.meta, "the modifies pass refers to several types and variables introduced by heap parameterization: Composite, Field, $heap_in, $heap."⟩] - run := fun p m => + run := fun p m _ => let (p', diags) := modifiesClausesTransform m p (p', diags, {}) diff --git a/Strata/Languages/Laurel/TransparencyPass.lean b/Strata/Languages/Laurel/TransparencyPass.lean index 1d1068e8e6..c44751ff96 100644 --- a/Strata/Languages/Laurel/TransparencyPass.lean +++ b/Strata/Languages/Laurel/TransparencyPass.lean @@ -171,7 +171,7 @@ For each procedure: - Generate a function with the same signature, named `foo$asFunction` - If transparent, the function gets a functional body (assertions erased, calls to functional versions) - If the function has a body, add a free postcondition equating the procedure output to the function" - run := fun p _ => + run := fun p _ _ => (createFunctionsForTransparentBodies p, [], {}) end -- public section diff --git a/Strata/Languages/Laurel/TypeAliasElim.lean b/Strata/Languages/Laurel/TypeAliasElim.lean index cc13e4ae67..5a61fcdbd3 100644 --- a/Strata/Languages/Laurel/TypeAliasElim.lean +++ b/Strata/Languages/Laurel/TypeAliasElim.lean @@ -121,6 +121,6 @@ public def typeAliasElimPass : LoweringPass where name := "TypeAliasElim" documentation := "Eliminates type aliases by replacing all UserDefined references to alias names with their resolved target types. Chained aliases are resolved transitively. Alias entries are removed from the type list." needsResolves := true - run := fun p m => (typeAliasElim m p, [], {}) + run := fun p m _ => (typeAliasElim m p, [], {}) end Strata.Laurel diff --git a/Strata/Languages/Laurel/TypeHierarchy.lean b/Strata/Languages/Laurel/TypeHierarchy.lean index 8ed943997e..5e7a569a29 100644 --- a/Strata/Languages/Laurel/TypeHierarchy.lean +++ b/Strata/Languages/Laurel/TypeHierarchy.lean @@ -164,7 +164,7 @@ public def typeHierarchyTransformPass : LoweringPass where documentation := "Encodes the object-oriented type hierarchy (inheritance, dynamic dispatch, type tests, and casts) into explicit operations on a flat representation. Composite types with parents are flattened, and dynamic dispatch is resolved through type-test chains." needsResolves := true comesAfter := [⟨ heapParameterizationPass.meta, "the type hierarchy pass modifies the 'Composite' datatype that is introduced by this pass."⟩] - run := fun p m => + run := fun p m _ => (typeHierarchyTransform m p, [], {}) end Strata.Laurel From 8470d93bd37c8175ae7305c99b59f4bc64f3b9b8 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Sat, 13 Jun 2026 14:09:53 +0000 Subject: [PATCH 079/115] Fixes --- Strata/Languages/Laurel/EliminateValueInReturns.lean | 4 +--- Strata/Languages/Laurel/LaurelCompilationPipeline.lean | 2 +- .../Languages/Laurel/LiftExpressionAssignmentsTest.lean | 2 +- .../Languages/Laurel/LiftImperativeCallsInAssertTest.lean | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Strata/Languages/Laurel/EliminateValueInReturns.lean b/Strata/Languages/Laurel/EliminateValueInReturns.lean index 1dfdca2779..fe0cc49372 100644 --- a/Strata/Languages/Laurel/EliminateValueInReturns.lean +++ b/Strata/Languages/Laurel/EliminateValueInReturns.lean @@ -14,9 +14,7 @@ import Strata.Util.Tactics # Eliminate Values In Returns Rewrites `return expr` into `outParam := expr; return` for imperative -(non-functional) procedures that have an output parameter. This decouples -the return-value assignment from the `LaurelToCoreTranslator`, which no -longer needs to know about output parameters when translating returns. +(non-functional) procedures that have an output parameter. The pass is a Laurel-to-Laurel rewrite that runs before Core translation. -/ diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index 6d0e5eba67..a8528cf988 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -217,7 +217,7 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) let coreWithLaurelTypes := (orderingPass.run unorderedCore model options).1 emit "CoreWithLaurelTypes" "core.st" coreWithLaurelTypes - let (coreProgram, coreDiagnostics, _) := laurelToCoreSchemaPass.run coreWithLaurelTypes model options + let (coreProgram, coreDiagnostics, _) := laurelToCoreSchemaPass.run coreWithLaurelTypes fnModel options let mut allDiagnostics: List DiagnosticModel := passDiags ++ coreDiagnostics; emit "Core" "core.st" coreProgram diff --git a/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean b/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean index 00e0423b99..807e59b557 100644 --- a/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean +++ b/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean @@ -11,7 +11,7 @@ by comparing the lifted Laurel against expected output. -/ import StrataTest.Util.TestLaurel -import Strata.Languages.Laurel.LaurelToCoreTranslator +import Strata.Languages.Laurel.LaurelToCoreSchemaPass import Strata.Languages.Laurel.Resolution open Strata diff --git a/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean b/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean index f1602a5e06..ba90bb447c 100644 --- a/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean +++ b/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean @@ -11,7 +11,7 @@ out of assert and assume conditions, while leaving assignments untouched -/ import StrataTest.Util.TestLaurel -import Strata.Languages.Laurel.LaurelToCoreTranslator +import Strata.Languages.Laurel.LaurelToCoreSchemaPass import Strata.Languages.Laurel.Resolution open Strata From b7508d738fbdc7094995c95663f5c65f309b5f08 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Sat, 13 Jun 2026 15:44:02 +0000 Subject: [PATCH 080/115] Fix test --- .../Languages/Laurel/Examples/Objects/T10_CompositeBvField.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/StrataTest/Languages/Laurel/Examples/Objects/T10_CompositeBvField.lean b/StrataTest/Languages/Laurel/Examples/Objects/T10_CompositeBvField.lean index 7c84f64a55..d60053efef 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/T10_CompositeBvField.lean +++ b/StrataTest/Languages/Laurel/Examples/Objects/T10_CompositeBvField.lean @@ -44,7 +44,7 @@ procedure writeLiteral(r: Register) procedure writeWrongLiteral(r: Register) opaque ensures r#value == 100 bv 16 -// ^^^^^^^^^^^^^^^^^^^^ error: assertion could not be proved +// ^^^^^^^^^^^^^^^^^^^^ error: postcondition could not be proved modifies r { r#value := 200 bv 16 From f1507d1b36c1f998cd236d9834cad94d0c5d7a91 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Sat, 13 Jun 2026 15:54:30 +0000 Subject: [PATCH 081/115] Update Laurel docs --- docs/verso/LaurelDoc.lean | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/verso/LaurelDoc.lean b/docs/verso/LaurelDoc.lean index c1202937be..f9f7ea6357 100644 --- a/docs/verso/LaurelDoc.lean +++ b/docs/verso/LaurelDoc.lean @@ -237,17 +237,20 @@ If new references or definitions are created during compilation, `resolve` must ## Translation Pipeline -Laurel programs are verified by translating them to Strata Core and then invoking the Core -verification pipeline. The Laurel compilation pipeline consists of three parts: -- Lowering, consisting of many phases. Maps Laurel to Laurel -- Ordering, consisting of a single pass. Maps Laurel to OrderedLaurel -- Translation, consisting of a single pass. Maps OrderedLaurel to Core. +The Laurel to Core translation pipeline relates to four IRs: +- Laurel +- UnorderedCoreWithLaurelTypes +- CoreWithLaurelTypes +- Core -Ideally the translation pass only translates between types but does not change the structure of the program. +Most of the passes are in the Laurel IR. +The transparency pass goes from `Laurel` to `UnorderedCoreWithLaurelTypes`. +The CoreGroupingAndOrdering goes from `UnorderedCoreWithLaurelTypes` to `CoreWithLaurelTypes` +And the LaurelToCoreSchemaPass goes from `CoreWithLaurelTypes` to `Core`. -## Lowering Passes +## Passes -The following passes are part of the lowering group: +The following passes making up the compilation of Laurel to Core: {laurelPipelineDocs} From 10779790c57eef2177dfd1382d871d665e783377 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Sat, 13 Jun 2026 15:55:03 +0000 Subject: [PATCH 082/115] Change text --- docs/verso/LaurelDoc.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/verso/LaurelDoc.lean b/docs/verso/LaurelDoc.lean index f9f7ea6357..e5453e393f 100644 --- a/docs/verso/LaurelDoc.lean +++ b/docs/verso/LaurelDoc.lean @@ -237,7 +237,7 @@ If new references or definitions are created during compilation, `resolve` must ## Translation Pipeline -The Laurel to Core translation pipeline relates to four IRs: +The Laurel to Core translation pipeline uses these IRs: - Laurel - UnorderedCoreWithLaurelTypes - CoreWithLaurelTypes From b30f4cbac34c2aa9c32271e388d66a98ef0a43cb Mon Sep 17 00:00:00 2001 From: keyboardDrummer-bot Date: Sat, 13 Jun 2026 20:17:13 +0000 Subject: [PATCH 083/115] Fix invokeOn axiom: guard with preconditions + reject output-referencing ensures - mkInvokeOnAxiom now produces instead of the unsound - New invokeOnOutputRefError emits a UserError diagnostic when an invokeOn procedure has postconditions that reference output parameters (which are not quantified in the axiom), preventing a 'Cannot find this fvar' crash - lowerContracts now returns diagnostics alongside the program --- Strata/Languages/Laurel/ContractPass.lean | 85 ++++++++++++++++++++--- 1 file changed, 74 insertions(+), 11 deletions(-) diff --git a/Strata/Languages/Laurel/ContractPass.lean b/Strata/Languages/Laurel/ContractPass.lean index 9d93da97e1..c810beda6c 100644 --- a/Strata/Languages/Laurel/ContractPass.lean +++ b/Strata/Languages/Laurel/ContractPass.lean @@ -8,6 +8,7 @@ module public import Strata.Languages.Laurel.MapStmtExpr public import Strata.Languages.Laurel.LaurelPass import Strata.Languages.Laurel.EliminateReturnStatements +import Strata.Util.Tactics /-! ## Contract Pass (Laurel → Laurel) @@ -250,26 +251,85 @@ private def rewriteCallSitesInProc (contractInfoMap : Std.HashMap String Contrac return { proc with body := Body.Opaque posts' impl' mods' } | _ => return proc +/-- Conjoin a list of conditions into a single expression with `&&`. -/ +private def conjoin (conds : List Condition) : Option StmtExprMd := + match conds.map (·.condition) with + | [] => none + | e :: rest => some (rest.foldl (fun acc x => mkMd (.PrimitiveOp .And [acc, x])) e) + /-- Build an axiom expression from `invokeOn` trigger and ensures clauses. - Produces `∀ p1, ∀ p2, ..., ∀ pn :: { trigger } (ensures1 && ensures2 && ...)`. + Produces `∀ p1, ∀ p2, ..., ∀ pn :: { trigger } (preconds => ensures)`. The trigger controls when the SMT solver instantiates the axiom. -/ private def mkInvokeOnAxiom (params : List Parameter) (trigger : StmtExprMd) - (postconds : List Condition) : StmtExprMd := - let body := match postconds.map (·.condition) with - | [] => mkMd (.LiteralBool true) - | [e] => e - | e :: rest => rest.foldl (fun acc x => mkMd (.PrimitiveOp .And [acc, x])) e + (preconds : List Condition) (postconds : List Condition) : StmtExprMd := + let ensures := (conjoin postconds).getD (mkMd (.LiteralBool true)) + let body := match conjoin preconds with + | some pre => mkMd (.PrimitiveOp .Implies [pre, ensures]) + | none => ensures -- 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 (.Quantifier .Forall p trig acc), false)) |>.1 +/-- Check whether a `StmtExprMd` tree mentions a local variable by name. -/ +private def exprMentions (name : String) (expr : StmtExprMd) : Bool := + match expr with + | AstNode.mk val _ => + match val with + | .Var (.Local id) => id.text == name + | .StaticCall _ args => args.attach.any (fun x => exprMentions name x.val) + | .PrimitiveOp _ args _ => args.attach.any (fun x => exprMentions name x.val) + | .IfThenElse c t e => exprMentions name c || exprMentions name t || + match e with | some el => exprMentions name el | none => false + | .Block stmts _ => stmts.attach.any (fun x => exprMentions name x.val) + | .Quantifier _ _ trigger body => exprMentions name body || + match trigger with | some t => exprMentions name t | none => false + | .ReferenceEquals l r => exprMentions name l || exprMentions name r + | .Assign _ v => exprMentions name v + | .Old v => exprMentions name v + | .Fresh v => exprMentions name v + | .Assume c => exprMentions name c + | .Assert c => exprMentions name c.condition + | .Return (some v) => exprMentions name v + | .InstanceCall t _ args => exprMentions name t || args.attach.any (fun x => exprMentions name x.val) + | .AsType t _ => exprMentions name t + | .IsType t _ => exprMentions name t + | .PureFieldUpdate t _ v => exprMentions name t || exprMentions name v + | .ProveBy v p => exprMentions name v || exprMentions name p + | .ContractOf _ f => exprMentions name f + | .Assigned n => exprMentions name n + | _ => false + termination_by expr + decreasing_by + all_goals (try cases x) + all_goals (try simp_all) + all_goals (try have := Condition.sizeOf_condition_lt ‹_›) + all_goals (try term_by_mem) + all_goals omega + +/-- Emit a diagnostic if an `invokeOn` procedure has postconditions referencing + output parameters (which are not quantified in the axiom). -/ +private def invokeOnOutputRefError (proc : Procedure) : Option DiagnosticModel := + if proc.invokeOn.isNone then none else + let postconds := getPostconditions proc.body + let referenced := proc.outputs.filterMap (fun out => + if postconds.any (fun c => exprMentions out.name.text c.condition) + then some out.name.text else none) + match referenced with + | [] => none + | names => some (diagnosticFromSource proc.name.source + s!"'invokeOn' procedure '{proc.name.text}' has an ensures referencing its output(s) ({String.intercalate ", " names}); the auto-invocation axiom is quantified over inputs only." + DiagnosticType.UserError) + /-- Run the contract pass on a Laurel program. All procedures with contracts are transformed. -/ -def lowerContracts (program : Program) : Program := +def lowerContracts (program : Program) : Program × List DiagnosticModel := let contractInfoMap := collectContractInfo program.staticProcedures + -- Check for output-referencing ensures in invokeOn procedures + let diagnostics := program.staticProcedures.filterMap invokeOnOutputRefError + -- Generate helper procedures for all procedures with contracts let helperProcs := (program.staticProcedures.filter (fun proc => !proc.isFunctional)).flatMap fun proc => let postconds := getPostconditions proc.body @@ -289,8 +349,11 @@ def lowerContracts (program : Program) : Program := | some trigger => let postconds := getPostconditions proc.body if postconds.isEmpty then { proc with invokeOn := none } + else if invokeOnOutputRefError proc |>.isSome then + -- Skip axiom generation; diagnostic already emitted + { proc with invokeOn := none } else { proc with - axioms := [mkInvokeOnAxiom proc.inputs trigger postconds] + axioms := [mkInvokeOnAxiom proc.inputs trigger proc.preconditions postconds] invokeOn := none } | none => proc let proc : Procedure := match contractInfoMap.get? proc.name.text with @@ -302,7 +365,7 @@ def lowerContracts (program : Program) : Program := -- Rewrite call sites in the procedure body rewriteCallSitesInProc contractInfoMap proc).run 0 - { program with staticProcedures := helperProcs ++ transformedProcs } + ({ program with staticProcedures := helperProcs ++ transformedProcs }, diagnostics) public def contractPass : LoweringPass where name := "ContractPass" @@ -310,8 +373,8 @@ public def contractPass : LoweringPass where comesAfter := [⟨ eliminateReturnStatementsPass.meta, "The contract pass wraps the body of procedures to get: `assume preconditions; body; assert postconditions`. Eliminating returns first means that the postcondition assertions are guaranteed to execute."⟩ ] needsResolves := true run := fun p _m _ => - let p' := lowerContracts p - (p', [], {}) + let (p', diags) := lowerContracts p + (p', diags, {}) end -- public section end Strata.Laurel From 6dd545ad6afdbb5fd4a07e2815d4e989665377aa Mon Sep 17 00:00:00 2001 From: keyboardDrummer-bot Date: Sat, 13 Jun 2026 21:01:25 +0000 Subject: [PATCH 084/115] Fix if-then-else prepend duplication in LiftImperativeExpressions Use takePrepends instead of reading prependedStmts to save+clear outer prepends before transforming the condition. This prevents condPrepends from including the saved outer prepends, which caused duplicate '' definitions when a call appeared in both the condition and branches. --- Strata/Languages/Laurel/LiftImperativeExpressions.lean | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index 5d37ab8a12..a91feee1b2 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -313,10 +313,10 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do let condVar ← freshCondVar -- Save outer state let savedSubst := (← get).subst - let savedPrepends := (← get).prependedStmts + let savedPrepends ← takePrepends let seqCond ← transformExpr cond - let condPrepends := (← get).prependedStmts + let condPrepends ← takePrepends -- Process then-branch from scratch modify fun s => { s with prependedStmts := [], subst := [] } let seqThen ← transformExpr thenBranch From 032279626dfcdd694f981a0a9cadf463df951c49 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 16 Jun 2026 10:41:40 +0000 Subject: [PATCH 085/115] Fix test --- StrataTest/Languages/Laurel/IncrDecrLiftTest.lean | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/StrataTest/Languages/Laurel/IncrDecrLiftTest.lean b/StrataTest/Languages/Laurel/IncrDecrLiftTest.lean index b9d00c16cd..44731ab35d 100644 --- a/StrataTest/Languages/Laurel/IncrDecrLiftTest.lean +++ b/StrataTest/Languages/Laurel/IncrDecrLiftTest.lean @@ -15,7 +15,6 @@ expected output. meta import StrataDDM.Elab meta import StrataDDM.BuiltinDialects.Init meta import Strata.Languages.Laurel.Grammar -meta import Strata.Languages.Laurel.LaurelToCoreTranslator meta import Strata.Languages.Laurel.EliminateIncrDecr meta import Strata.Languages.Laurel.LiftImperativeExpressions @@ -42,7 +41,7 @@ def parseLowerIncrDecr (input : String) : IO Program := do -- Step 2: resolve so liftExpressionAssignments has a valid SemanticModel let result := resolve program let (program, model) := (result.program, result.model) - pure (liftExpressionAssignments model program) + pure (liftExpressionAssignments program model []) /-- Statement form: `x++;` and `--x` as statements. Prefix (`--x`) produces a clean assignment. Postfix (`x++`) emits the same assignment-based form as From 878f325b5a37f5ad08ab949bab26c9dee6a0930e Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 16 Jun 2026 12:33:19 +0000 Subject: [PATCH 086/115] Code review --- Strata/Languages/Laurel/LiftImperativeExpressions.lean | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index fb389e4e6b..b36fc13d52 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -112,10 +112,6 @@ private def onlyKeepSideEffectStmtsAndLast (stmts : List StmtExprMd) : LiftM (Li match s.val with | .Var (.Declare ..) | .Assign ([⟨.Declare .., _⟩]) _ => do pure [s] - -- | .Assert _ => do - -- pure [s] - -- | .Assume _ => do - -- pure [s] /- Any other impure StmtExpr, like .Assign, .Exit or .Return, @@ -274,7 +270,7 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do else let startingPrepend ← takePrepends let seqArgs ← args.reverse.mapM transformExpr - let argsPepends ← takePrepends + let argsPrepends ← takePrepends let seqCall := ⟨.StaticCall callee seqArgs.reverse, source⟩ -- Imperative call in expression position: lift to an assignment. -- Only valid for single-output procedures (or unresolved ones where we @@ -293,7 +289,7 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do ⟨ (.Var (.Declare ⟨callResultVar, callResultType⟩)), source ⟩, ⟨.Assign [⟨ .Local callResultVar, source⟩] seqCall, source⟩ ] - modify fun s => { s with prependedStmts := argsPepends ++ liftedCall ++ startingPrepend} + modify fun s => { s with prependedStmts := argsPrepends ++ liftedCall ++ startingPrepend} return ⟨.Var (.Local callResultVar), source⟩ | .IfThenElse cond thenBranch elseBranch => From e0c8b68b8b7a51201e669a844c947499d8497890 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 16 Jun 2026 12:39:22 +0000 Subject: [PATCH 087/115] Fix early return --- Strata/Languages/Laurel/LaurelCompilationPipeline.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index ad721725f0..fb65ec7d03 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -193,7 +193,7 @@ def translateWithLaurel (options : LaurelTranslateOptions) (program : Program) -- resolution errors reported by Laurel would also be reported by Core. -- There might be better solution that allows getting some resolution errors from Laurel and some verification errors from Core, -- but that would need more consideration. - if ! passDiags.isEmpty then + if passDiags.any (·.type != .Warning) then return (none, passDiags, program, stats) let unorderedCore := (transparencyPass.run program model options).1 From a63384a5080b7a53f168bfc0394ce3447bc4043b Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 16 Jun 2026 12:45:57 +0000 Subject: [PATCH 088/115] Fix lifting bug --- Strata/Languages/Laurel/LiftImperativeExpressions.lean | 8 ++++---- .../Languages/Laurel/LiftExpressionAssignmentsTest.lean | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index b36fc13d52..a8fbad934e 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -391,16 +391,16 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do | .Assume cond => let prepends ← takePrepends - let seqCond ← transformExpr cond + _ ← transformExpr cond let argPrepends ← takePrepends - modify fun s => { s with prependedStmts := argPrepends ++ [⟨.Assume seqCond, source⟩] ++ prepends } + modify fun s => { s with prependedStmts := argPrepends ++ [expr] ++ prepends } default | .Assert cond => let prepends ← takePrepends - let seqCond ← transformExpr cond.condition + _ ← transformExpr cond.condition let argPrepends ← takePrepends - modify fun s => { s with prependedStmts := argPrepends ++ [⟨.Assert { cond with condition := seqCond }, source⟩] ++ prepends } + modify fun s => { s with prependedStmts := argPrepends ++ [expr] ++ prepends } default | .Return (some retExpr) => diff --git a/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean b/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean index 807e59b557..814fabe6cf 100644 --- a/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean +++ b/StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean @@ -34,7 +34,7 @@ info: procedure assertInBlockExpr() opaque { var x: int := 0; - assert $x_0 == 0; + assert x == 0; var $x_0: int := x; x := 1; var y: int := { From 83e793b0e1c9abfc4f7723c370d592b232fbf395 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 18 Jun 2026 08:06:38 +0000 Subject: [PATCH 089/115] Port fixes to lifting pass --- .../Laurel/LiftImperativeExpressions.lean | 159 ++++++++++-------- .../Fundamentals/T2_ImpureExpressions.lean | 42 ++++- 2 files changed, 128 insertions(+), 73 deletions(-) diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index a8fbad934e..69845bc198 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -86,15 +86,13 @@ structure LiftState where @[expose] abbrev LiftM := StateM LiftState -private def emptyMd : Option String := none - private def freshTempFor (varName : Identifier) : LiftM Identifier := do let counters := (← get).varCounters let counter := counters.find? (·.1 == varName) |>.map (·.2) |>.getD 0 modify fun s => { s with varCounters := (varName, counter + 1) :: s.varCounters.filter (·.1 != varName) } return s!"${varName.text}_{counter}" -private def freshCondVar : LiftM Identifier := do +private def freshTempVar : LiftM Identifier := do let n := (← get).condCounter modify fun s => { s with condCounter := n + 1 } return s!"$cndtn_{n}" @@ -102,6 +100,9 @@ private def freshCondVar : LiftM Identifier := do private def prepend (stmt : StmtExprMd) : LiftM Unit := modify fun s => { s with prependedStmts := stmt :: s.prependedStmts } +private def prependList (stmts : List StmtExprMd) : LiftM Unit := + modify fun s => { s with prependedStmts := stmts ++ s.prependedStmts } + private def onlyKeepSideEffectStmtsAndLast (stmts : List StmtExprMd) : LiftM (List StmtExprMd) := do match stmts with | [] => return [] @@ -140,8 +141,13 @@ private def computeType (expr : StmtExprMd) : LiftM HighTypeMd := do let s ← get return computeExprType s.model expr -/-- Check if an expression contains any assignments or imperative calls (recursively). -/ -def containsAssignmentOrImperativeCall (imperativeCallees : List String) (expr : StmtExprMd) : Bool := +/-- Check if an expression contains any assignments or imperative calls +(recursively). When `liftsAssertsAssumes` is set, asserts and assumes also +count — these are lifted into statement position by `transformExpr`, so an +if-then-else whose branch contains one must itself be lifted to keep the +statement guarded by the condition. -/ +def containsAssignmentOrImperativeCall (imperativeCallees : List String) (expr : StmtExprMd) + (liftsAssertsAssumes : Bool := false) : Bool := match expr with | AstNode.mk val _ => match val with @@ -149,37 +155,37 @@ def containsAssignmentOrImperativeCall (imperativeCallees : List String) (expr : | .IncrDecr .. => true | .StaticCall name args1 => imperativeCallees.contains name.text || - args1.attach.any (fun x => containsAssignmentOrImperativeCall imperativeCallees x.val) - | .PrimitiveOp _ args2 _ => args2.attach.any (fun x => containsAssignmentOrImperativeCall imperativeCallees x.val) - | .Block stmts _ => stmts.attach.any (fun x => containsAssignmentOrImperativeCall imperativeCallees x.val) + args1.attach.any (fun x => containsAssignmentOrImperativeCall imperativeCallees x.val liftsAssertsAssumes) + | .PrimitiveOp _ args2 _ => args2.attach.any (fun x => containsAssignmentOrImperativeCall imperativeCallees x.val liftsAssertsAssumes) + | .Block stmts _ => stmts.attach.any (fun x => containsAssignmentOrImperativeCall imperativeCallees x.val liftsAssertsAssumes) | .IfThenElse cond th el => - containsAssignmentOrImperativeCall imperativeCallees cond || - containsAssignmentOrImperativeCall imperativeCallees th || - match el with | some e => containsAssignmentOrImperativeCall imperativeCallees e | none => false - | .Assume cond => containsAssignmentOrImperativeCall imperativeCallees cond - | .Assert cond => containsAssignmentOrImperativeCall imperativeCallees cond.condition + containsAssignmentOrImperativeCall imperativeCallees cond liftsAssertsAssumes || + containsAssignmentOrImperativeCall imperativeCallees th liftsAssertsAssumes || + match el with | some e => containsAssignmentOrImperativeCall imperativeCallees e liftsAssertsAssumes | none => false + | .Assume cond => liftsAssertsAssumes || containsAssignmentOrImperativeCall imperativeCallees cond liftsAssertsAssumes + | .Assert cond => liftsAssertsAssumes || containsAssignmentOrImperativeCall imperativeCallees cond.condition liftsAssertsAssumes | .InstanceCall target _ args => - containsAssignmentOrImperativeCall imperativeCallees target || - args.attach.any (fun x => containsAssignmentOrImperativeCall imperativeCallees x.val) + containsAssignmentOrImperativeCall imperativeCallees target liftsAssertsAssumes || + args.attach.any (fun x => containsAssignmentOrImperativeCall imperativeCallees x.val liftsAssertsAssumes) | .Quantifier _ _ trigger body => - containsAssignmentOrImperativeCall imperativeCallees body || - match trigger with | some t => containsAssignmentOrImperativeCall imperativeCallees t | none => false - | .Old value => containsAssignmentOrImperativeCall imperativeCallees value - | .Fresh value => containsAssignmentOrImperativeCall imperativeCallees value + containsAssignmentOrImperativeCall imperativeCallees body liftsAssertsAssumes || + match trigger with | some t => containsAssignmentOrImperativeCall imperativeCallees t liftsAssertsAssumes | none => false + | .Old value => containsAssignmentOrImperativeCall imperativeCallees value liftsAssertsAssumes + | .Fresh value => containsAssignmentOrImperativeCall imperativeCallees value liftsAssertsAssumes | .ProveBy value proof => - containsAssignmentOrImperativeCall imperativeCallees value || - containsAssignmentOrImperativeCall imperativeCallees proof + containsAssignmentOrImperativeCall imperativeCallees value liftsAssertsAssumes || + containsAssignmentOrImperativeCall imperativeCallees proof liftsAssertsAssumes | .ReferenceEquals lhs rhs => - containsAssignmentOrImperativeCall imperativeCallees lhs || - containsAssignmentOrImperativeCall imperativeCallees rhs + containsAssignmentOrImperativeCall imperativeCallees lhs liftsAssertsAssumes || + containsAssignmentOrImperativeCall imperativeCallees rhs liftsAssertsAssumes | .PureFieldUpdate target _ newValue => - containsAssignmentOrImperativeCall imperativeCallees target || - containsAssignmentOrImperativeCall imperativeCallees newValue - | .AsType target _ => containsAssignmentOrImperativeCall imperativeCallees target - | .IsType target _ => containsAssignmentOrImperativeCall imperativeCallees target - | .Assigned name => containsAssignmentOrImperativeCall imperativeCallees name - | .ContractOf _ func => containsAssignmentOrImperativeCall imperativeCallees func - | .Return (some v) => containsAssignmentOrImperativeCall imperativeCallees v + containsAssignmentOrImperativeCall imperativeCallees target liftsAssertsAssumes || + containsAssignmentOrImperativeCall imperativeCallees newValue liftsAssertsAssumes + | .AsType target _ => containsAssignmentOrImperativeCall imperativeCallees target liftsAssertsAssumes + | .IsType target _ => containsAssignmentOrImperativeCall imperativeCallees target liftsAssertsAssumes + | .Assigned name => containsAssignmentOrImperativeCall imperativeCallees name liftsAssertsAssumes + | .ContractOf _ func => containsAssignmentOrImperativeCall imperativeCallees func liftsAssertsAssumes + | .Return (some v) => containsAssignmentOrImperativeCall imperativeCallees v liftsAssertsAssumes | _ => false termination_by expr decreasing_by @@ -189,27 +195,23 @@ def containsAssignmentOrImperativeCall (imperativeCallees : List String) (expr : all_goals (try term_by_mem) all_goals omega +mutual + + /-- -Shared logic for lifting an assignment in expression position: -prepends the assignment, creates before-snapshots for all targets, -and updates substitutions. The value should already be transformed by the caller. +Process an expression in expression context, traversing arguments right to left. +Assignments are lifted to prependedStmts and replaced with snapshot variable references. -/ -private def liftAssignExpr (targets : List VariableMd) (seqValue : StmtExprMd) - (source : Option FileRange) : LiftM Unit := do - -- Prepend the assignment itself - prepend (⟨.Assign targets seqValue, source⟩) - -- Create a before-snapshot for each target and update substitutions - for target in targets do - match target.val with - | .Local varName => - let snapshotName ← freshTempFor varName - let varType ← computeType ⟨ .Var (.Local varName), source ⟩ - -- Snapshot goes before the assignment (cons pushes to front) - prepend (⟨.Assign [⟨.Declare ⟨snapshotName, varType⟩, source⟩] (⟨.Var (.Local varName), source⟩), source⟩) - setSubst varName snapshotName - | _ => pure () +def transformLiftedExpr (expr : StmtExprMd) : LiftM (List StmtExprMd × StmtExprMd) := do + let savedSubst := (← get).subst + let savedPrepends ← takePrepends + modify fun s => { s with prependedStmts := [], subst := []} + let result ← transformExpr expr + let newPrepends ← takePrepends + modify fun s => { s with prependedStmts := savedPrepends, subst := savedSubst } + return (newPrepends, result) + termination_by (sizeOf expr, 1) -mutual /-- Process an expression in expression context, traversing arguments right to left. Assignments are lifted to prependedStmts and replaced with snapshot variable references. @@ -225,7 +227,7 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do | .Hole false (some holeType) => -- Nondeterministic typed hole: lift to a fresh variable with no initializer (havoc) - let holeVar ← freshCondVar + let holeVar ← freshTempVar prepend ⟨ .Var (.Declare ⟨holeVar, holeType⟩), source⟩ return ⟨ .Var (.Local holeVar), source ⟩ @@ -249,9 +251,22 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do dbg_trace "Strata bug: non-identifier targets should have been removed before the lift expression phase"; return expr - -- Use the original value (not seqValue) for the prepended assignment, - -- because prepended statements execute in program order and don't need substitutions. - liftAssignExpr targets value source + let (valuePrepends, newValue) ← transformLiftedExpr value + + prepend (⟨.Assign targets newValue, source⟩) + + -- Create a before-snapshot for each target and update substitutions + for target in targets do + match target.val with + | .Local varName => + let snapshotName ← freshTempFor varName + let varType ← computeType ⟨ .Var (.Local varName), source ⟩ + -- Snapshot goes before the assignment (cons pushes to front) + prepend (⟨.Assign [⟨.Declare ⟨snapshotName, varType⟩, source⟩] (⟨.Var (.Local varName), source⟩), source⟩) + setSubst varName snapshotName + | _ => pure () + + prependList valuePrepends return resultExpr @@ -268,10 +283,9 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do let seqCall := ⟨.StaticCall callee seqArgs.reverse, source⟩ return seqCall else - let startingPrepend ← takePrepends - let seqArgs ← args.reverse.mapM transformExpr - let argsPrepends ← takePrepends - let seqCall := ⟨.StaticCall callee seqArgs.reverse, source⟩ + let seqArgsAndPrepends ← args.reverse.mapM transformLiftedExpr + let seqArgs := seqArgsAndPrepends.map (fun t => t.2) + let seqCall: StmtExprMd := ⟨.StaticCall callee seqArgs.reverse, source⟩ -- Imperative call in expression position: lift to an assignment. -- Only valid for single-output procedures (or unresolved ones where we -- fall back to a single target). Multi-output procedures in expression @@ -281,22 +295,27 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do | .staticProcedure proc => proc.outputs | .instanceProcedure _ proc => proc.outputs | _ => [] - let callResultVar ← freshCondVar + let callResultVar ← freshTempVar let callResultType ← match outputs with | [single] => pure single.type | _ => computeType expr - let liftedCall := [ + let liftedCall: List StmtExprMd := [ ⟨ (.Var (.Declare ⟨callResultVar, callResultType⟩)), source ⟩, ⟨.Assign [⟨ .Local callResultVar, source⟩] seqCall, source⟩ ] - modify fun s => { s with prependedStmts := argsPrepends ++ liftedCall ++ startingPrepend} + prependList liftedCall + prependList (seqArgsAndPrepends.map (fun t => t.1)).flatten return ⟨.Var (.Local callResultVar), source⟩ | .IfThenElse cond thenBranch elseBranch => let imperativeCallees := (← get).imperativeCallees - let thenHasAssign := containsAssignmentOrImperativeCall imperativeCallees thenBranch + -- A branch must be lifted if it contains anything `transformExpr` would + -- hoist: assignments, imperative calls, asserts, or assumes. (Asserts and + -- assumes matter because hoisting them out of the branch would drop the + -- condition's guard — see `liftsAssertsAssumes`.) + let thenHasAssign := containsAssignmentOrImperativeCall imperativeCallees thenBranch (liftsAssertsAssumes := true) let elseHasAssign := match elseBranch with - | some e => containsAssignmentOrImperativeCall imperativeCallees e + | some e => containsAssignmentOrImperativeCall imperativeCallees e (liftsAssertsAssumes := true) | none => false if thenHasAssign || elseHasAssign then @@ -307,7 +326,7 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do let needsCondVar := condType.val != .TVoid -- Lift the entire if-then-else. Introduce a fresh variable for the result. - let condVar ← freshCondVar + let condVar ← freshTempVar -- Save outer state let savedSubst := (← get).subst let savedPrepends ← takePrepends @@ -343,7 +362,7 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do -- Unused value return ⟨ .Hole, expr.source ⟩ else - -- No assignments in branches — recurse normally + -- No liftable statements in branches — recurse normally. let seqCond ← transformExpr cond let seqThen ← transformExpr thenBranch let seqElse ← match elseBranch with @@ -390,17 +409,15 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do return expr | .Assume cond => - let prepends ← takePrepends - _ ← transformExpr cond - let argPrepends ← takePrepends - modify fun s => { s with prependedStmts := argPrepends ++ [expr] ++ prepends } + let (argPrepends, newCond) ← transformLiftedExpr cond + prepend ⟨ .Assume newCond, source⟩ + prependList argPrepends default | .Assert cond => - let prepends ← takePrepends - _ ← transformExpr cond.condition - let argPrepends ← takePrepends - modify fun s => { s with prependedStmts := argPrepends ++ [expr] ++ prepends } + let (argPrepends, newCond) ← transformLiftedExpr cond.condition + prepend ⟨ .Assert {cond with condition := newCond}, source⟩ + prependList argPrepends default | .Return (some retExpr) => diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean index 886a9692e9..30b98d7bec 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean @@ -9,6 +9,7 @@ import StrataTest.Util.TestLaurel open StrataTest.Util open Strata +#guard_msgs (drop info) in #eval testLaurel <| #strata program Laurel; @@ -111,9 +112,9 @@ procedure imperativeCallInConditionalExpression(b: bool) } }; -function add(x: int, y: int): int +procedure add(x: int, y: int): int { - x + y + return x + y }; procedure repeatedBlockExpressions() @@ -145,4 +146,41 @@ procedure addProcCaller(): int // var z: int := addProc({x := 1; x}, {x := x + 10; x}) + (x := 3); // assert z == 15 }; + +procedure assertInsideConditionalExpression(a: int): int + return if a > 2 + then 4 + else { + assert a <= 2; + assert a < 2; +// ^^^^^^^^^^^^ error: assertion does not hold + 5 + }; + +procedure assertInBlockExpr() +opaque { + var x: int := 0; + var y: int := { assert x == 0; x := 1; x }; + assert y == 1 +}; + +procedure transparentProc(x: int) returns (r: int) +{ + return x + 1 +}; + +procedure assignmentInExpressionAfterProcCall() +opaque { + var x: int := 0; + var y: int := transparentProc(x) + (x := 2); + assert y == 3 +}; + +procedure liftInsideAssignmentInExpression() +opaque { + var x: int := 0; + var y: int := ((x := 1) + transparentProc(x)); + assert y == 3 +}; + #end From dcfb0e90d77c25f4a3958fcb8ce03b461b4dac38 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 18 Jun 2026 11:31:00 +0000 Subject: [PATCH 090/115] Some fixes for resolution during phases --- Strata/Languages/Laurel/Resolution.lean | 80 +++++++++---------- .../Laurel/ResolutionTypeCheckTests.lean | 12 +++ 2 files changed, 51 insertions(+), 41 deletions(-) diff --git a/Strata/Languages/Laurel/Resolution.lean b/Strata/Languages/Laurel/Resolution.lean index ed7022c3a7..41206de217 100644 --- a/Strata/Languages/Laurel/Resolution.lean +++ b/Strata/Languages/Laurel/Resolution.lean @@ -1089,10 +1089,7 @@ def Check.return (exprMd : StmtExprMd) | _ => let (e', _) ← Synth.resolveStmtExpr a.val; pure e') match val, expectedReturn with | none, some [] => pure () - | none, some [singleOutput] => - -- `return;` synthesizes the missing payload as `TVoid`; require it to - -- be a consistent subtype of the declared output. - checkSubtype source singleOutput { val := .TVoid, source := source } + | none, some [singleOutput] => pure () | none, some _ => pure () | some _, some [] => let diag := diagnosticFromSource source @@ -1693,6 +1690,32 @@ def Synth.staticCall (exprMd : StmtExprMd) (callee : Identifier) (args : List StmtExprMd) (source : Option FileRange) (h : exprMd.val = .StaticCall callee args) : ResolveM (StmtExpr × HighTypeMd) := do + + -- Hack because we use these polymorphic map primitives but Laurel does not + -- support polymorphism yet, so they cannot be type-checked against their + -- placeholder `int` signatures. Instead we resolve the arguments and infer the + -- result type structurally from them, keeping a concrete `HighType` flowing into + -- Core translation: + -- * `select(map, key)` ⇒ the map's value type + -- * `update(map, key, val)` ⇒ the map type itself + -- * `const(val)` ⇒ `Map _ (typeof val)` (key type is not recoverable) + if callee == "select" || callee == "update" || callee == "const" then + let resolved ← args.attach.mapM (fun ⟨a, hMem⟩ => do + have := hMem + Synth.resolveStmtExpr a) + let args' := resolved.map (·.1) + let argTys := resolved.map (·.2) + let resultTy : HighTypeMd ← + match callee, argTys with + | "select", mapTy :: _ => + match mapTy.val with + | .TMap _ valueTy => pure valueTy + | _ => pure ⟨ .Unknown, source ⟩ + | "update", mapTy :: _ => pure mapTy + | "const", valTy :: _ => pure ⟨ .TMap ⟨.UserDefined "TypeTag", source⟩ valTy, source ⟩ + | _, _ => pure ⟨ .Unknown, source ⟩ + return (.StaticCall callee args', resultTy) + let callee' ← resolveRef callee source (expected := #[.parameter, .staticProcedure, .datatypeConstructor, .datatypeDestructor, .constant]) let (retTy, paramTypes) ← getCallInfo callee @@ -1719,12 +1742,13 @@ def Synth.staticCall (exprMd : StmtExprMd) pure (.StaticCall callee' args', retTy) termination_by (exprMd, 1) decreasing_by - apply Prod.Lex.left - have hsz := exprMd.sizeOf_val_lt - rw [h] at hsz - simp only [StmtExpr.StaticCall.sizeOf_spec] at hsz - have := List.sizeOf_lt_of_mem ‹_ ∈ args› - omega + all_goals + apply Prod.Lex.left + have hsz := exprMd.sizeOf_val_lt + rw [h] at hsz + simp only [StmtExpr.StaticCall.sizeOf_spec] at hsz + have := List.sizeOf_lt_of_mem ‹_ ∈ args› + omega /-- Cases on the arity of the callee's declared outputs. ``` @@ -2620,14 +2644,14 @@ def resolveParameter (param : Parameter) : ResolveM Parameter := do output: a single output `T` for single-output functional procedures, `Unknown` otherwise. Bodies without an impl block (`Abstract`, `External`) ignore `expected`. -/ -def resolveBody (body : Body) (expected : HighTypeMd) : ResolveM Body := do +def resolveBody (body : Body) : ResolveM Body := do match body with | .Transparent b => - let b' ← Check.resolveStmtExpr b expected + let b' ← Check.resolveStmtExpr b ⟨ HighType.Unknown, b.source ⟩ return .Transparent b' | .Opaque posts impl mods => let posts' ← posts.mapM (·.mapM resolveStmtExpr) - let impl' ← impl.mapM (Check.resolveStmtExpr · expected) + let impl' ← impl.mapM (Check.resolveStmtExpr · ⟨ HighType.Unknown, default ⟩) let mods' ← mods.mapM resolveStmtExpr return .Opaque posts' impl' mods' | .Abstract posts => @@ -2635,26 +2659,6 @@ def resolveBody (body : Body) (expected : HighTypeMd) : ResolveM Body := do return .Abstract posts' | .External => return .External -/-- Compute the expected *value type* `A` for a procedure body, i.e. - the `A` in `Γ ⊢ body ⇐ A`. Functional procedures with a single - output `T` expect `A = T`: the body's last statement is the result - and must produce a `T`. Non-functional procedures expect - `A = Unknown`: their body is run as a statement and the last - statement's value (if any) is discarded — outputs are observed via - `return e` (whose payload is matched against the procedure's - declared outputs by `Resolution.Check.return`) or via named-output - assignment. - - This computes only the body's value type. The procedure's declared - output list is bound separately by the procedure rule - (`resolveProcedure` / `resolveInstanceProcedure`) into - `ResolveState.answerType`. -/ -private def procedureBodyType (isFunctional : Bool) (outputs : List Parameter) - (source : Option FileRange) : HighTypeMd := - match isFunctional, outputs with - | true, [singleOutput] => singleOutput.type - | _, _ => { val := .Unknown, source := source } - /-- (Procedure) ``` T_o-bar = proc.outputs.types A = procedureBodyType proc @@ -2676,12 +2680,7 @@ def resolveProcedure (proc : Procedure) : ResolveM Procedure := do let dec' ← proc.decreases.mapM resolveStmtExpr let savedAnswer := (← get).answerType modify fun s => { s with answerType := some (outputs'.map (·.type)) } - let bodyExpected := procedureBodyType proc.isFunctional outputs' proc.name.source - -- Pre-register the implicit `bodyLabel` block that the LaurelToCore - -- translator wraps every body in (`Core.Statement.block bodyLabel …`), - -- so that frontends emitting `Exit bodyLabel` for early-return lowering - -- (e.g. PythonToLaurel) don't trip Check.exit's label-scope check. - let body' ← withLabel (some bodyLabel) <| resolveBody proc.body bodyExpected + let body' ← withLabel (some bodyLabel) <| resolveBody proc.body modify fun s => { s with answerType := savedAnswer } -- Transparent (static) procedure bodies are supported (#1215): the -- TransparencyPass derives a functional `$asFunction` copy, and the @@ -2722,9 +2721,8 @@ def resolveInstanceProcedure (typeName : Identifier) (proc : Procedure) : Resolv let dec' ← proc.decreases.mapM resolveStmtExpr let savedAnswer := (← get).answerType modify fun s => { s with answerType := some (outputs'.map (·.type)) } - let bodyExpected := procedureBodyType proc.isFunctional outputs' proc.name.source -- See `resolveProcedure` for the rationale on `bodyLabel`. - let body' ← withLabel (some bodyLabel) <| resolveBody proc.body bodyExpected + let body' ← withLabel (some bodyLabel) <| resolveBody proc.body modify fun s => { s with answerType := savedAnswer } let invokeOn' ← proc.invokeOn.mapM resolveStmtExpr modify fun s => { s with instanceTypeName := savedInstType } diff --git a/StrataTest/Languages/Laurel/ResolutionTypeCheckTests.lean b/StrataTest/Languages/Laurel/ResolutionTypeCheckTests.lean index dfb6e79568..7d0568f868 100644 --- a/StrataTest/Languages/Laurel/ResolutionTypeCheckTests.lean +++ b/StrataTest/Languages/Laurel/ResolutionTypeCheckTests.lean @@ -16,6 +16,18 @@ open Strata /-! ## Non-boolean conditions -/ +#eval testLaurelResolution <| +#strata +program Laurel; + +procedure voidReturn(x: int) + returns (r: int) +{ + r := 1; + return +}; +#end + #eval testLaurelResolution <| #strata program Laurel; From a0ec015d0d995cb5cc0b79de3e0287489b3ea186 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 18 Jun 2026 11:42:26 +0000 Subject: [PATCH 091/115] Fixes to resolution so it throws fewer errors during passes --- Strata/Languages/Laurel/Resolution.lean | 44 ++++++++++++++----- .../Laurel/ResolutionTypeCheckTests.lean | 12 +++++ 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/Strata/Languages/Laurel/Resolution.lean b/Strata/Languages/Laurel/Resolution.lean index 7f81346fd1..9c1be1a960 100644 --- a/Strata/Languages/Laurel/Resolution.lean +++ b/Strata/Languages/Laurel/Resolution.lean @@ -1089,10 +1089,7 @@ def Check.return (exprMd : StmtExprMd) | _ => let (e', _) ← Synth.resolveStmtExpr a.val; pure e') match val, expectedReturn with | none, some [] => pure () - | none, some [singleOutput] => - -- `return;` synthesizes the missing payload as `TVoid`; require it to - -- be a consistent subtype of the declared output. - checkSubtype source singleOutput { val := .TVoid, source := source } + | none, some [singleOutput] => pure () | none, some _ => pure () | some _, some [] => let diag := diagnosticFromSource source @@ -1693,6 +1690,32 @@ def Synth.staticCall (exprMd : StmtExprMd) (callee : Identifier) (args : List StmtExprMd) (source : Option FileRange) (h : exprMd.val = .StaticCall callee args) : ResolveM (StmtExpr × HighTypeMd) := do + + -- Hack because we use these polymorphic map primitives but Laurel does not + -- support polymorphism yet, so they cannot be type-checked against their + -- placeholder `int` signatures. Instead we resolve the arguments and infer the + -- result type structurally from them, keeping a concrete `HighType` flowing into + -- Core translation: + -- * `select(map, key)` ⇒ the map's value type + -- * `update(map, key, val)` ⇒ the map type itself + -- * `const(val)` ⇒ `Map _ (typeof val)` (key type is not recoverable) + if callee == "select" || callee == "update" || callee == "const" then + let resolved ← args.attach.mapM (fun ⟨a, hMem⟩ => do + have := hMem + Synth.resolveStmtExpr a) + let args' := resolved.map (·.1) + let argTys := resolved.map (·.2) + let resultTy : HighTypeMd ← + match callee, argTys with + | "select", mapTy :: _ => + match mapTy.val with + | .TMap _ valueTy => pure valueTy + | _ => pure ⟨ .Unknown, source ⟩ + | "update", mapTy :: _ => pure mapTy + | "const", valTy :: _ => pure ⟨ .TMap ⟨.UserDefined "TypeTag", source⟩ valTy, source ⟩ + | _, _ => pure ⟨ .Unknown, source ⟩ + return (.StaticCall callee args', resultTy) + let callee' ← resolveRef callee source (expected := #[.parameter, .staticProcedure, .datatypeConstructor, .datatypeDestructor, .constant]) let (retTy, paramTypes) ← getCallInfo callee @@ -1719,12 +1742,13 @@ def Synth.staticCall (exprMd : StmtExprMd) pure (.StaticCall callee' args', retTy) termination_by (exprMd, 1) decreasing_by - apply Prod.Lex.left - have hsz := exprMd.sizeOf_val_lt - rw [h] at hsz - simp only [StmtExpr.StaticCall.sizeOf_spec] at hsz - have := List.sizeOf_lt_of_mem ‹_ ∈ args› - omega + all_goals + apply Prod.Lex.left + have hsz := exprMd.sizeOf_val_lt + rw [h] at hsz + simp only [StmtExpr.StaticCall.sizeOf_spec] at hsz + have := List.sizeOf_lt_of_mem ‹_ ∈ args› + omega /-- Cases on the arity of the callee's declared outputs. ``` diff --git a/StrataTest/Languages/Laurel/ResolutionTypeCheckTests.lean b/StrataTest/Languages/Laurel/ResolutionTypeCheckTests.lean index dfb6e79568..7d0568f868 100644 --- a/StrataTest/Languages/Laurel/ResolutionTypeCheckTests.lean +++ b/StrataTest/Languages/Laurel/ResolutionTypeCheckTests.lean @@ -16,6 +16,18 @@ open Strata /-! ## Non-boolean conditions -/ +#eval testLaurelResolution <| +#strata +program Laurel; + +procedure voidReturn(x: int) + returns (r: int) +{ + r := 1; + return +}; +#end + #eval testLaurelResolution <| #strata program Laurel; From 25f1d5d3c922ddb6e37e4bc1b3072cdc6e1b0f5c Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 18 Jun 2026 11:38:14 +0000 Subject: [PATCH 092/115] Emit resolution errors during passes --- Strata/Languages/Laurel/LaurelCompilationPipeline.lean | 6 ++++++ StrataTest/Languages/Laurel/ResolutionTypeCheckTests.lean | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index e0f2c7137f..e0ea6b7e74 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -163,7 +163,13 @@ private def runLaurelPasses let result := resolve program (some model) let newErrors := result.errors.filter fun e => !resolutionErrors.contains e if !newErrors.isEmpty then + let newDiags := newErrors.toList.map fun d => + { d with + message := + s!"Internal error: resolution after '{pass.name}' introduced this diagnostic: {d.message}" + type := .StrataBug } emit pass.name "laurel.st" program + return (program, model, allDiags ++ newDiags, allStats) program := result.program model := result.model emit pass.name "laurel.st" program diff --git a/StrataTest/Languages/Laurel/ResolutionTypeCheckTests.lean b/StrataTest/Languages/Laurel/ResolutionTypeCheckTests.lean index 7d0568f868..ce3b24e885 100644 --- a/StrataTest/Languages/Laurel/ResolutionTypeCheckTests.lean +++ b/StrataTest/Languages/Laurel/ResolutionTypeCheckTests.lean @@ -94,7 +94,7 @@ function cmp(x: string, y: int): bool { #eval testLaurelResolution <| #strata program Laurel; -procedure foo() opaque { +procedure invalidAssignment() opaque { var x: int := true // ^^^^ error: expected 'int', got 'bool' }; From fa7f84480ba91ca8b058106dc7213589fefbd60e Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 18 Jun 2026 12:10:32 +0000 Subject: [PATCH 093/115] Source fix --- Strata/Languages/Laurel/LiftImperativeExpressions.lean | 4 ++-- StrataTest/Util/TestLaurel.lean | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index 20cd25d908..2d15f56c2f 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -439,7 +439,7 @@ def transformStmt (stmt : StmtExprMd) : LiftM (List StmtExprMd) := do -- If the RHS is a direct imperative StaticCall, don't lift it — -- translateStmt handles Assign + StaticCall directly as a call statement. match _: valueMd with - | AstNode.mk value _ => + | AstNode.mk value callSource => match _: value with | .StaticCall callee args => let model := (← get).model @@ -452,7 +452,7 @@ def transformStmt (stmt : StmtExprMd) : LiftM (List StmtExprMd) := do let seqArgs ← args.mapM transformExpr let argPrepends ← takePrepends modify fun s => { s with subst := [] } - return argPrepends ++ [⟨.Assign targets ⟨.StaticCall callee seqArgs, source⟩, source⟩] + return argPrepends ++ [⟨.Assign targets ⟨.StaticCall callee seqArgs, callSource⟩, source⟩] | _ => let seqValue ← transformExpr valueMd let prepends ← takePrepends diff --git a/StrataTest/Util/TestLaurel.lean b/StrataTest/Util/TestLaurel.lean index 577c3b47e2..933f320c11 100644 --- a/StrataTest/Util/TestLaurel.lean +++ b/StrataTest/Util/TestLaurel.lean @@ -262,6 +262,16 @@ def testLaurel (block : SourcedProgram) (options : LaurelVerifyOptions := defaultLaurelTestOptions) : IO Unit := runAndCheck block (runLaurelPipelineRaw · options) +/-- Path to the directory for intermediate files, inside the build directory. + Resolved from the current working directory so it works on any machine. -/ +def buildDir : IO String := do + let cwd ← IO.currentDir + return s!"{cwd}/.lake/build/intermediatePrograms/" + +def testLaurelKeepIntermediates (block : SourcedProgram) : IO Unit := do + let dir ← buildDir + runAndCheck block (runLaurelPipelineRaw · { translateOptions := { keepAllFilesPrefix := dir}}) + /-- Like `testLaurel` but skips SMT verification (translate + resolve only). Use when the test only cares about resolution, not the verifier — e.g. "shadowing in nested blocks is OK", or asserting a specific resolution From 441ec1eae679d176c05ce783ab5d3ec77026c49e Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 18 Jun 2026 12:41:36 +0000 Subject: [PATCH 094/115] Improve well-formedness of the 3 heap passes --- .../Laurel/CoreGroupingAndOrdering.lean | 1 - Strata/Languages/Laurel/FilterPrelude.lean | 3 +-- .../AbstractToConcreteTreeTranslator.lean | 4 +--- .../Laurel/HeapParameterization.lean | 21 ++++++++++++------- Strata/Languages/Laurel/LaurelAST.lean | 11 ++-------- .../Laurel/LaurelToCoreTranslator.lean | 2 -- Strata/Languages/Laurel/ModifiesClauses.lean | 2 +- Strata/Languages/Laurel/Resolution.lean | 4 ---- Strata/Languages/Laurel/TypeAliasElim.lean | 1 - Strata/Languages/Laurel/TypeHierarchy.lean | 2 +- StrataPython/StrataPython/PythonToLaurel.lean | 2 -- .../StrataPythonTest/ToLaurelTest.lean | 2 -- docs/verso/LaurelDoc.lean | 2 +- 13 files changed, 20 insertions(+), 37 deletions(-) diff --git a/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean b/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean index 8a84b0c545..2cb6b89d46 100644 --- a/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean +++ b/Strata/Languages/Laurel/CoreGroupingAndOrdering.lean @@ -33,7 +33,6 @@ def collectTypeRefs : HighTypeMd → List String | ⟨.UserDefined name, _⟩ => [name.text] | ⟨.TSet elem, _⟩ => collectTypeRefs elem | ⟨.TMap k v, _⟩ => collectTypeRefs k ++ collectTypeRefs v - | ⟨.TTypedField vt, _⟩ => collectTypeRefs vt | ⟨.Applied base args, _⟩ => collectTypeRefs base ++ args.flatMap collectTypeRefs | ⟨.Pure base, _⟩ => collectTypeRefs base diff --git a/Strata/Languages/Laurel/FilterPrelude.lean b/Strata/Languages/Laurel/FilterPrelude.lean index 8eb64060b3..18bbca462b 100644 --- a/Strata/Languages/Laurel/FilterPrelude.lean +++ b/Strata/Languages/Laurel/FilterPrelude.lean @@ -71,14 +71,13 @@ private partial def collectHighTypeNames (ty : HighTypeMd) : CollectM Unit := do match ty.val with | .UserDefined name => addTypeName name.text | .TCore _ => pure () - | .TTypedField vt => collectHighTypeNames vt | .TSet et => collectHighTypeNames et | .TMap kt vt => collectHighTypeNames kt; collectHighTypeNames vt | .Applied base args => collectHighTypeNames base; args.forM collectHighTypeNames | .Pure base => collectHighTypeNames base | .Intersection types => types.forM collectHighTypeNames - | .TVoid | .TBool | .TInt | .TFloat64 | .TReal | .TString | .THeap + | .TVoid | .TBool | .TInt | .TFloat64 | .TReal | .TString | .TBv _ | .Unknown | .MultiValuedExpr _ => pure () /-- Collect all referenced names (procedure calls, type references) from a StmtExpr tree. -/ diff --git a/Strata/Languages/Laurel/Grammar/AbstractToConcreteTreeTranslator.lean b/Strata/Languages/Laurel/Grammar/AbstractToConcreteTreeTranslator.lean index 414bf1451f..2595594f48 100644 --- a/Strata/Languages/Laurel/Grammar/AbstractToConcreteTreeTranslator.lean +++ b/Strata/Languages/Laurel/Grammar/AbstractToConcreteTreeTranslator.lean @@ -49,9 +49,7 @@ partial def highTypeValToArg : HighType → Arg | .UserDefined name => laurelOp "compositeType" #[ident name.text] | .TCore s => laurelOp "coreType" #[ident s] | .TVoid => laurelOp "compositeType" #[ident "void"] - | .THeap => laurelOp "compositeType" #[ident "Heap"] - -- Type parameters discarded; the grammar cannot represent Field[T] or Set[T] - | .TTypedField _vt => laurelOp "compositeType" #[ident "Field"] + -- Type parameters discarded; the grammar cannot represent Set[T] | .TSet _et => laurelOp "compositeType" #[ident "Set"] | .Applied base _args => -- Applied types are not directly representable in the grammar; diff --git a/Strata/Languages/Laurel/HeapParameterization.lean b/Strata/Languages/Laurel/HeapParameterization.lean index 3b266a9fff..8e5eeaec8a 100644 --- a/Strata/Languages/Laurel/HeapParameterization.lean +++ b/Strata/Languages/Laurel/HeapParameterization.lean @@ -25,12 +25,12 @@ and a `nextReference: int` for allocating new objects. Box is a sum type with co primitive type (BoxInt, BoxBool, BoxFloat64, BoxComposite). Composite is a type synonym for int. 1. Procedures that write the heap get an inout heap parameter - - Input: `heap : THeap` - - Output: `heap : THeap` + - Input: `heap : Heap` + - Output: `heap : Heap` - Field writes become: `heap := updateField(heap, obj, field, BoxT(value))` 2. Procedures that only read the heap get an in heap parameter - - Input: `heap : THeap` + - Input: `heap : Heap` - Field reads become: `Box..tVal(readField(heap, obj, field))` 3. Procedure calls are transformed: @@ -480,8 +480,11 @@ def heapTransformProcedure (model: SemanticModel) (proc : Procedure) : Transform if writesHeap then -- This procedure writes the heap - add $heap_in as input and $heap as output -- At the start, assign $heap_in to $heap, then use $heap throughout - let heapInParam : Parameter := { name := heapInName, type := ⟨.THeap, none⟩ } - let heapOutParam : Parameter := { name := heapName, type := ⟨.THeap, none⟩ } + -- Type the heap parameters as the prelude `Heap` datatype so they stay + -- consistent with the generated heap functions (`readField`, `updateField`, + -- `increment`, `Heap..nextReference!`), all of which are declared over `Heap`. + let heapInParam : Parameter := { name := heapInName, type := ⟨.UserDefined "Heap", none⟩ } + let heapOutParam : Parameter := { name := heapName, type := ⟨.UserDefined "Heap", none⟩ } let inputs' := heapInParam :: proc.inputs let outputs' := heapOutParam :: proc.outputs @@ -519,8 +522,10 @@ def heapTransformProcedure (model: SemanticModel) (proc : Procedure) : Transform body := body' } else if readsHeap then - -- This procedure only reads the heap - add $heap as input only - let heapParam : Parameter := { name := heapName, type := ⟨.THeap, none⟩ } + -- This procedure only reads the heap - add $heap as input only. + -- Use the prelude `Heap` datatype for the parameter type (see the + -- writes-heap branch above for rationale). + let heapParam : Parameter := { name := heapName, type := ⟨.UserDefined "Heap", none⟩ } let inputs' := heapParam :: proc.inputs let preconditions' ← proc.preconditions.mapM (·.mapM (heapTransformExpr heapName model)) @@ -578,7 +583,7 @@ def heapParameterization (model: SemanticModel) (program : Program) : Program := public def heapParameterizationPass : LaurelPass where name := "HeapParameterization" documentation := "Transforms procedures that interact with the heap by adding explicit heap parameters. The heap is modeled as `Map Composite (Map Field Box)`. Procedures that write the heap receive both an input and output heap parameter; procedures that only read the heap receive an input heap parameter. Field reads and writes are rewritten to use `readField` and `updateField` functions." - needsResolves := true + needsResolves := false -- Only resolve again after completing HeapParam, ModifiesClauses and TypeHierarchy. These are logically one pass. run := fun p m => (heapParameterization m p, [], {}) comesBefore := [ diff --git a/Strata/Languages/Laurel/LaurelAST.lean b/Strata/Languages/Laurel/LaurelAST.lean index bbaec0fdfa..1574b210cc 100644 --- a/Strata/Languages/Laurel/LaurelAST.lean +++ b/Strata/Languages/Laurel/LaurelAST.lean @@ -135,8 +135,7 @@ structure AstNode (t : Type) : Type where The type system for Laurel programs. `HighType` covers primitive types (`TVoid`, `TBool`, `TInt`, `TReal`, `TFloat64`, -`TString`), internal types used by the heap parameterization pass (`THeap`, -`TTypedField`), collection types (`TSet`), user-defined types (`UserDefined`), +`TString`), collection types (`TSet`), user-defined types (`UserDefined`), generic applications (`Applied`), value types (`Pure`), and intersection types (`Intersection`). -/ @@ -153,10 +152,6 @@ inductive HighType : Type where | TReal /-- String type for text data. -/ | TString - /-- Internal type representing the heap. Introduced by the heap parameterization pass; not accessible via grammar. -/ - | THeap - /-- Internal type for a field constant with a known value type. Introduced by the heap parameterization pass; not accessible via grammar. -/ - | TTypedField (valueType : AstNode HighType) /-- Set type, e.g. `Set int`. -/ | TSet (elementType : AstNode HighType) /-- Map type. -/ @@ -527,9 +522,7 @@ def highEq (a : HighTypeMd) (b : HighTypeMd) : Bool := match _a: a.val, _b: b.va | HighType.TFloat64, HighType.TFloat64 => true | HighType.TReal, HighType.TReal => true | HighType.TString, HighType.TString => true - | HighType.THeap, HighType.THeap => true | HighType.TBv n1, HighType.TBv n2 => n1 == n2 - | HighType.TTypedField t1, HighType.TTypedField t2 => highEq t1 t2 | HighType.TSet t1, HighType.TSet t2 => highEq t1 t2 | HighType.TMap k1 v1, HighType.TMap k2 v2 => highEq k1 k2 && highEq v1 v2 | HighType.UserDefined r1, HighType.UserDefined r2 => r1.text == r2.text @@ -630,7 +623,7 @@ def isSubtype (ctx : TypeLattice) (sub sup : HighTypeMd) : Bool := /- ### Variance policy (covers `isSubtype` and `isConsistent`) All child-carrying constructors are INVARIANT by design: `isConsistent` bottoms out in `highEq` (structural equality) for `TSet`, `TMap`, - `TTypedField`, `Applied`, `Pure`, and `Intersection`. So `TSet Unknown ~ + `Applied`, `Pure`, and `Intersection`. So `TSet Unknown ~ TSet TInt` is FALSE — `Unknown` is a wildcard only at the TOP of a type, never under a constructor. This is intentional: `TSet` / `TMap` are MUTABLE collections, where covariance would be unsound; if you don't know the diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index 5948aa1d9e..592685c623 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -79,8 +79,6 @@ def translateType (ty : HighTypeMd) : TranslateM LMonoTy := do | .TString => return LMonoTy.string | .TBv n => return LMonoTy.bitvec n | .TVoid => return LMonoTy.bool -- Using bool as placeholder for void - | .THeap => return .tcons "Heap" [] - | .TTypedField _ => return .tcons "Field" [] | .TSet elementType => return Core.mapTy (← translateType elementType) LMonoTy.bool | .TMap keyType valueType => return Core.mapTy (← translateType keyType) (← translateType valueType) | .UserDefined name => diff --git a/Strata/Languages/Laurel/ModifiesClauses.lean b/Strata/Languages/Laurel/ModifiesClauses.lean index f2fcc0a791..d162e764ff 100644 --- a/Strata/Languages/Laurel/ModifiesClauses.lean +++ b/Strata/Languages/Laurel/ModifiesClauses.lean @@ -126,7 +126,7 @@ def buildModifiesEnsures (proc: Procedure) (model: SemanticModel) (modifiesExprs -- Build: antecedent ==> heapUnchanged let implBody := mkMd <| .PrimitiveOp .Implies [antecedent, heapUnchanged] -- Build: forall $obj: Composite, $fld: Field => ... - let innerForall := mkMd <| .Quantifier .Forall ⟨ fldName, { val := .TTypedField { val := .TInt, source := none }, source := none } ⟩ none implBody + let innerForall := mkMd <| .Quantifier .Forall ⟨ fldName, { val := .UserDefined "Field", source := none } ⟩ none implBody let outerForall : StmtExprMd := { val := .Quantifier .Forall ⟨ objName, { val := .UserDefined "Composite", source := none } ⟩ none innerForall, source := proc.name.source } some outerForall diff --git a/Strata/Languages/Laurel/Resolution.lean b/Strata/Languages/Laurel/Resolution.lean index 9c1be1a960..225d643403 100644 --- a/Strata/Languages/Laurel/Resolution.lean +++ b/Strata/Languages/Laurel/Resolution.lean @@ -338,9 +338,6 @@ def resolveHighType (ty : HighTypeMd) : ResolveM HighTypeMd := do | none => false -- name not defined: resolveRef already reported it if kindOk then pure (HighType.UserDefined ref') else pure HighType.Unknown - | .TTypedField vt => - let vt' ← resolveHighType vt - pure (.TTypedField vt') | .TSet et => let et' ← resolveHighType et pure (.TSet et') @@ -2839,7 +2836,6 @@ private def collectHighType (map : Std.HashMap Nat ResolvedNode) (ty : HighTypeM match ty with | AstNode.mk val _ => match val with - | .TTypedField vt => collectHighType map vt | .TSet et => collectHighType map et | .TMap kt vt => let map := collectHighType map kt diff --git a/Strata/Languages/Laurel/TypeAliasElim.lean b/Strata/Languages/Laurel/TypeAliasElim.lean index 1aa069e5f0..d96fc61a18 100644 --- a/Strata/Languages/Laurel/TypeAliasElim.lean +++ b/Strata/Languages/Laurel/TypeAliasElim.lean @@ -39,7 +39,6 @@ partial def resolveAliasType (amap : AliasMap) (ty : HighTypeMd) else match amap.get? name.text with | some target => resolveAliasType amap target (visited.insert name.text) | none => ty - | .TTypedField vt => { val := .TTypedField (resolveAliasType amap vt visited), source := ty.source } | .TSet et => { val := .TSet (resolveAliasType amap et visited), source := ty.source } | .TMap kt vt => { val := .TMap (resolveAliasType amap kt visited) (resolveAliasType amap vt visited), source := ty.source } diff --git a/Strata/Languages/Laurel/TypeHierarchy.lean b/Strata/Languages/Laurel/TypeHierarchy.lean index 599fc40492..46fd63022a 100644 --- a/Strata/Languages/Laurel/TypeHierarchy.lean +++ b/Strata/Languages/Laurel/TypeHierarchy.lean @@ -161,7 +161,7 @@ def typeHierarchyTransform (model: SemanticModel) (program : Program) : Program public def typeHierarchyTransformPass : LaurelPass where name := "TypeHierarchyTransform" documentation := "Encodes the object-oriented type hierarchy (inheritance, dynamic dispatch, type tests, and casts) into explicit operations on a flat representation. Composite types with parents are flattened, and dynamic dispatch is resolved through type-test chains." - needsResolves := true + needsResolves := false -- Only resolve again after completing HeapParam, ModifiesClauses and TypeHierarchy run := fun p m => (typeHierarchyTransform m p, [], {}) diff --git a/StrataPython/StrataPython/PythonToLaurel.lean b/StrataPython/StrataPython/PythonToLaurel.lean index a8cdbc64aa..199963fa40 100644 --- a/StrataPython/StrataPython/PythonToLaurel.lean +++ b/StrataPython/StrataPython/PythonToLaurel.lean @@ -2731,8 +2731,6 @@ def getHighTypeName : Laurel.HighType → String | .TString => "string" | .TVoid => "void" | .TFloat64 => "real" - | .THeap => "Heap" - | .TTypedField _ => "Field" | .TCore s => s | .UserDefined name => name.text | .TSet _ => "Map" diff --git a/StrataPython/StrataPythonTest/ToLaurelTest.lean b/StrataPython/StrataPythonTest/ToLaurelTest.lean index cb693b450b..f44e423ba5 100644 --- a/StrataPython/StrataPythonTest/ToLaurelTest.lean +++ b/StrataPython/StrataPythonTest/ToLaurelTest.lean @@ -64,8 +64,6 @@ private def fmtHighType : HighType → String | .TReal => "TReal" | .TFloat64 => "TFloat64" | .TString => "TString" - | .THeap => "THeap" - | .TTypedField _ => "TTypedField" | .TSet _ => "TSet" | .TMap _ _ => "TMap" | .UserDefined name => s!"UserDefined({name})" diff --git a/docs/verso/LaurelDoc.lean b/docs/verso/LaurelDoc.lean index 891eef33a7..698db8dc04 100644 --- a/docs/verso/LaurelDoc.lean +++ b/docs/verso/LaurelDoc.lean @@ -228,7 +228,7 @@ implementation introduces that have no surface syntax. The {name Strata.Laurel.HighType}`HighType` type enumerates every type Laurel tracks. Alongside the user-writable types it also includes internal constructors -(such as `THeap`, `Unknown`, and `MultiValuedExpr`) that the compiler introduces +(such as `Unknown` and `MultiValuedExpr`) that the compiler introduces during resolution and later passes; these have no surface syntax. {docstring Strata.Laurel.HighType} From cf706b240b2a1c2a68a483634f1ee34cbfa99841 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 18 Jun 2026 12:50:38 +0000 Subject: [PATCH 095/115] Fix for type hierarchy --- Strata/Languages/Laurel/TypeHierarchy.lean | 87 +++++++++++++++++++++- 1 file changed, 83 insertions(+), 4 deletions(-) diff --git a/Strata/Languages/Laurel/TypeHierarchy.lean b/Strata/Languages/Laurel/TypeHierarchy.lean index 46fd63022a..865e1dc311 100644 --- a/Strata/Languages/Laurel/TypeHierarchy.lean +++ b/Strata/Languages/Laurel/TypeHierarchy.lean @@ -123,6 +123,79 @@ private def rewriteTypeHierarchyNode (exprMd : StmtExprMd) : THM StmtExprMd := d | .IsType target ty => return lowerIsType target ty exprMd.source | _ => return exprMd +/-- +Rewrite a type so that every reference to a composite type (a name in +`composites`) becomes the flattened `Composite` datatype. After the type +hierarchy pass all composite values are represented by `Composite` references, +so their *static* types must follow suit; otherwise re-resolution sees a +`Pixel`-typed value flowing into a `Composite`-typed slot (`readField`, +`Composite..ref!`, an allocation `new C`, …). Recurses through compound types. -/ +partial def compositeRefToComposite (composites : Std.HashSet String) (ty : HighTypeMd) : HighTypeMd := + match ty.val with + | .UserDefined name => + if composites.contains name.text then { ty with val := .UserDefined "Composite" } else ty + | .TSet et => { ty with val := .TSet (compositeRefToComposite composites et) } + | .TMap kt vt => + { ty with val := .TMap (compositeRefToComposite composites kt) (compositeRefToComposite composites vt) } + | .Applied base args => + { ty with val := .Applied (compositeRefToComposite composites base) (args.map (compositeRefToComposite composites ·)) } + | .Pure base => { ty with val := .Pure (compositeRefToComposite composites base) } + | .Intersection tys => { ty with val := .Intersection (tys.map (compositeRefToComposite composites ·)) } + | _ => ty + +/-- Rewrite composite references in an assignment target's declared type. -/ +private def rewriteCompositeVar (composites : Std.HashSet String) (v : VariableMd) : VariableMd := + match v.val with + | .Declare param => ⟨.Declare { param with type := compositeRefToComposite composites param.type }, v.source⟩ + | _ => v + +/-- Rewrite composite references in the type positions carried by a single + `StmtExpr` node (local declarations and quantifier binders). `IsType`/`AsType` + are intentionally left untouched: they have already been lowered to type-tag + lookups by `rewriteTypeHierarchyNode`, and their type argument is a type-test + target rather than a value type. -/ +private def rewriteCompositeExprNode (composites : Std.HashSet String) (expr : StmtExprMd) : StmtExprMd := + match expr.val with + | .Assign targets value => + ⟨.Assign (targets.map (rewriteCompositeVar composites)) value, expr.source⟩ + | .Var (.Declare param) => + ⟨.Var (.Declare { param with type := compositeRefToComposite composites param.type }), expr.source⟩ + | .Quantifier mode param trigger body => + ⟨.Quantifier mode { param with type := compositeRefToComposite composites param.type } trigger body, expr.source⟩ + | _ => expr + +/-- Rewrite every composite reference type in a procedure (parameters, body, + contracts) to `Composite`. -/ +def rewriteCompositeInProc (composites : Std.HashSet String) (proc : Procedure) : Procedure := + let f := mapStmtExpr (rewriteCompositeExprNode composites) + let rewriteBody : Body → Body := fun body => match body with + | .Transparent b => .Transparent (f b) + | .Opaque ps impl modif => .Opaque (ps.map (·.mapCondition f)) (impl.map f) (modif.map f) + | .Abstract ps => .Abstract (ps.map (·.mapCondition f)) + | .External => .External + { proc with + body := rewriteBody proc.body + inputs := proc.inputs.map fun p => { p with type := compositeRefToComposite composites p.type } + outputs := proc.outputs.map fun p => { p with type := compositeRefToComposite composites p.type } + preconditions := proc.preconditions.map (·.mapCondition f) + decreases := proc.decreases.map f + invokeOn := proc.invokeOn.map f } + +/-- Rewrite composite reference types appearing inside a type definition + (datatype constructor arguments, constrained-type base/constraint/witness). -/ +def rewriteCompositeInType (composites : Std.HashSet String) (td : TypeDefinition) : TypeDefinition := + match td with + | .Datatype dt => + .Datatype { dt with constructors := dt.constructors.map fun ctor => + { ctor with args := ctor.args.map fun p => { p with type := compositeRefToComposite composites p.type } } } + | .Constrained ct => + let f := mapStmtExpr (rewriteCompositeExprNode composites) + .Constrained { ct with + base := compositeRefToComposite composites ct.base + constraint := f ct.constraint + witness := f ct.witness } + | _ => td + /-- Type hierarchy transformation pass (Laurel → Laurel). @@ -140,18 +213,24 @@ def typeHierarchyTransform (model: SemanticModel) (program : Program) : Program .Datatype { name := "TypeTag", typeArgs := [], constructors := compositeNames.map fun n => { name := (mkId $ n ++ "_TypeTag"), args := [] } } let typeHierarchyConstants := generateTypeHierarchyDecls model program let (procs', _) := (program.staticProcedures.mapM (mapProcedureM (mapStmtExprM rewriteTypeHierarchyNode))).run {} + -- Now that `New`/`IsType` have been lowered (they needed the original + -- composite names), flatten every remaining composite reference type to the + -- `Composite` datatype so the program re-resolves consistently. + let compositeSet : Std.HashSet String := + compositeNames.foldl (init := {}) (·.insert ·) + let procs' := procs'.map (rewriteCompositeInProc compositeSet) -- Update the Composite datatype to include the typeTag field (introduced in this phase) let typeTagTy : HighTypeMd := ⟨.UserDefined "TypeTag", none⟩ let remainingTypes := program.types.map fun td => - match td with + match (rewriteCompositeInType compositeSet td) with | .Datatype dt => if dt.name.text == "Composite" then .Datatype { dt with constructors := dt.constructors.map fun c => if c.name.text == "MkComposite" then { c with args := c.args ++ [{ name := ("typeTag" : Identifier), type := typeTagTy }] } else c } - else td - | _ => td + else .Datatype dt + | other => other { program with staticProcedures := procs', types := [typeTagDatatype] ++ remainingTypes, @@ -161,7 +240,7 @@ def typeHierarchyTransform (model: SemanticModel) (program : Program) : Program public def typeHierarchyTransformPass : LaurelPass where name := "TypeHierarchyTransform" documentation := "Encodes the object-oriented type hierarchy (inheritance, dynamic dispatch, type tests, and casts) into explicit operations on a flat representation. Composite types with parents are flattened, and dynamic dispatch is resolved through type-test chains." - needsResolves := false -- Only resolve again after completing HeapParam, ModifiesClauses and TypeHierarchy + needsResolves := false -- Only resolve again after completing HeapParam, ModifiesClauses and TypeHierarchy. These are logically one pass. run := fun p m => (typeHierarchyTransform m p, [], {}) From 66589c5584bf5f5f575d58553dd0af7020ffbc3e Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 18 Jun 2026 13:00:03 +0000 Subject: [PATCH 096/115] Refactoring --- .../Laurel/LaurelToCoreTranslator.lean | 2 +- Strata/Languages/Laurel/MapStmtExpr.lean | 102 ++++++++++++++++++ Strata/Languages/Laurel/TypeAliasElim.lean | 65 +---------- Strata/Languages/Laurel/TypeHierarchy.lean | 82 +++----------- 4 files changed, 124 insertions(+), 127 deletions(-) diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index 592685c623..59a1557aed 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -83,7 +83,7 @@ def translateType (ty : HighTypeMd) : TranslateM LMonoTy := do | .TMap keyType valueType => return Core.mapTy (← translateType keyType) (← translateType valueType) | .UserDefined name => match model.get? name with - | some (.compositeType _) => return .tcons "Composite" [] + | some (.compositeType _) => return .tcons "Composite" [] -- TODO no longer needed? | some (.datatypeDefinition dt) => return .tcons dt.name.text [] | some (.datatypeConstructor typeName _) => return .tcons typeName.text [] | _ => do -- resolution should have already emitted a diagnostic diff --git a/Strata/Languages/Laurel/MapStmtExpr.lean b/Strata/Languages/Laurel/MapStmtExpr.lean index f39cbfb621..47da17b591 100644 --- a/Strata/Languages/Laurel/MapStmtExpr.lean +++ b/Strata/Languages/Laurel/MapStmtExpr.lean @@ -137,5 +137,107 @@ def mapProgramM [Monad m] (f : StmtExprMd → m StmtExprMd) (program : Program) def mapProgram (f : StmtExprMd → StmtExprMd) (program : Program) : Program := mapProgramM (m := Id) f program +/-! ## Type-annotation traversals + +`mapStmtExprHighTypesM` and friends apply a `HighType → HighType` rewrite to +*every* type annotation reachable from a node / procedure / program, reusing +`mapStmtExprM` for the structural recursion. This is the single source of truth +for "rewrite all type references", so passes don't have to enumerate the +type-carrying constructors by hand (and silently miss one). The supplied `f` is +responsible for recursing into compound types (`TSet`/`TMap`/`Applied`/…). -/ + +/-- Rewrite the declared type carried by a `Variable` (only `Declare` carries one). -/ +def mapVariableHighTypesM [Monad m] (f : HighTypeMd → m HighTypeMd) (v : Variable) : m Variable := do + match v with + | .Declare param => pure (.Declare { param with type := ← f param.type }) + | .Local _ | .Field _ _ => pure v + +/-- +Apply `f` to every `HighType` annotation carried *directly* by a single +`StmtExpr` node: local declarations (in `Var`, `Assign` targets, and `IncrDecr` +targets), quantifier binders, `AsType`/`IsType` type arguments, and typed +`Hole`s. Does **not** recurse into child expressions — compose with +`mapStmtExprM` (see `mapStmtExprHighTypesM`) for a whole-tree traversal. +-/ +def mapNodeHighTypesM [Monad m] (f : HighTypeMd → m HighTypeMd) (expr : StmtExprMd) : m StmtExprMd := do + let source := expr.source + match expr.val with + | .Var v => pure ⟨.Var (← mapVariableHighTypesM f v), source⟩ + | .Assign targets value => + let targets' ← targets.mapM (fun t => do pure (⟨← mapVariableHighTypesM f t.val, t.source⟩ : VariableMd)) + pure ⟨.Assign targets' value, source⟩ + | .IncrDecr mode op target => + pure ⟨.IncrDecr mode op ⟨← mapVariableHighTypesM f target.val, target.source⟩, source⟩ + | .Quantifier mode param trigger body => + pure ⟨.Quantifier mode { param with type := ← f param.type } trigger body, source⟩ + | .AsType target ty => pure ⟨.AsType target (← f ty), source⟩ + | .IsType target ty => pure ⟨.IsType target (← f ty), source⟩ + | .Hole det (some ty) => pure ⟨.Hole det (some (← f ty)), source⟩ + | _ => pure expr + +/-- Apply `f` to every `HighType` annotation appearing anywhere in a `StmtExprMd`. -/ +def mapStmtExprHighTypesM [Monad m] (f : HighTypeMd → m HighTypeMd) (expr : StmtExprMd) : m StmtExprMd := + mapStmtExprM (mapNodeHighTypesM f) expr + +/-- Pure version of `mapStmtExprHighTypesM`. -/ +def mapStmtExprHighTypes (f : HighTypeMd → HighTypeMd) (expr : StmtExprMd) : StmtExprMd := + mapStmtExprHighTypesM (m := Id) f expr + +/-- Apply `f` to every `HighType` annotation in a procedure: parameter types, + body, preconditions, decreases measure, and auto-invocation trigger. -/ +def mapProcedureHighTypesM [Monad m] (f : HighTypeMd → m HighTypeMd) (proc : Procedure) : m Procedure := do + let mapExpr := mapStmtExprHighTypesM f + let mapParam (p : Parameter) : m Parameter := do pure { p with type := ← f p.type } + let body' ← match proc.body with + | .Transparent b => pure (.Transparent (← mapExpr b)) + | .Opaque ps impl mods => + pure (.Opaque (← ps.mapM (·.mapM mapExpr)) (← impl.mapM mapExpr) (← mods.mapM mapExpr)) + | .Abstract ps => pure (.Abstract (← ps.mapM (·.mapM mapExpr))) + | .External => pure .External + return { proc with + inputs := ← proc.inputs.mapM mapParam + outputs := ← proc.outputs.mapM mapParam + body := body' + preconditions := ← proc.preconditions.mapM (·.mapM mapExpr) + decreases := ← proc.decreases.mapM mapExpr + invokeOn := ← proc.invokeOn.mapM mapExpr } + +/-- Apply `f` to every `HighType` annotation in a type definition: composite + fields and instance procedures, constrained base/constraint/witness, + datatype constructor argument types, and alias targets. -/ +def mapTypeDefinitionHighTypesM [Monad m] (f : HighTypeMd → m HighTypeMd) (td : TypeDefinition) : m TypeDefinition := do + match td with + | .Composite ct => + pure (.Composite { ct with + fields := ← ct.fields.mapM (fun fld => do pure { fld with type := ← f fld.type }) + instanceProcedures := ← ct.instanceProcedures.mapM (mapProcedureHighTypesM f) }) + | .Constrained ct => + pure (.Constrained { ct with + base := ← f ct.base + constraint := ← mapStmtExprHighTypesM f ct.constraint + witness := ← mapStmtExprHighTypesM f ct.witness }) + | .Datatype dt => + pure (.Datatype { dt with + constructors := ← dt.constructors.mapM (fun ctor => do + pure { ctor with args := ← ctor.args.mapM (fun p => do pure { p with type := ← f p.type }) }) }) + | .Alias ta => pure (.Alias { ta with target := ← f ta.target }) + +/-- Apply `f` to a constant's declared type and (optional) initializer. -/ +def mapConstantHighTypesM [Monad m] (f : HighTypeMd → m HighTypeMd) (c : Constant) : m Constant := do + pure { c with type := ← f c.type, initializer := ← c.initializer.mapM (mapStmtExprHighTypesM f) } + +/-- Apply `f` to every `HighType` annotation anywhere in a program: procedures, + static fields, type definitions, and constants. -/ +def mapProgramHighTypesM [Monad m] (f : HighTypeMd → m HighTypeMd) (program : Program) : m Program := do + return { program with + staticProcedures := ← program.staticProcedures.mapM (mapProcedureHighTypesM f) + staticFields := ← program.staticFields.mapM (fun fld => do pure { fld with type := ← f fld.type }) + types := ← program.types.mapM (mapTypeDefinitionHighTypesM f) + constants := ← program.constants.mapM (mapConstantHighTypesM f) } + +/-- Pure version of `mapProgramHighTypesM`. -/ +def mapProgramHighTypes (f : HighTypeMd → HighTypeMd) (program : Program) : Program := + mapProgramHighTypesM (m := Id) f program + end -- public section end Strata.Laurel diff --git a/Strata/Languages/Laurel/TypeAliasElim.lean b/Strata/Languages/Laurel/TypeAliasElim.lean index d96fc61a18..36c5426613 100644 --- a/Strata/Languages/Laurel/TypeAliasElim.lean +++ b/Strata/Languages/Laurel/TypeAliasElim.lean @@ -51,69 +51,14 @@ partial def resolveAliasType (amap : AliasMap) (ty : HighTypeMd) { val := .Intersection (tys.map (resolveAliasType amap · visited)), source := ty.source } | _ => ty -def resolveAliasVariable (amap : AliasMap) (v : VariableMd) : VariableMd := - match v.val with - | .Declare param => ⟨.Declare { param with type := resolveAliasType amap param.type }, v.source⟩ - | _ => v - -/-- Resolve aliases in expression type positions. -/ -def resolveAliasExprNode (amap : AliasMap) (expr : StmtExprMd) : StmtExprMd := - match expr.val with - | .Assign targets value => - ⟨.Assign (targets.map (resolveAliasVariable amap)) value, expr.source⟩ - | .Var (.Declare param) => - ⟨.Var (.Declare { param with type := resolveAliasType amap param.type }), expr.source⟩ - | .Quantifier mode param trigger body => - { val := .Quantifier mode { param with type := resolveAliasType amap param.type } trigger body, source := expr.source } - | .AsType t ty => { val := .AsType t (resolveAliasType amap ty), source := expr.source } - | .IsType t ty => { val := .IsType t (resolveAliasType amap ty), source := expr.source } - | _ => expr - -def resolveAliasInProc (amap : AliasMap) (proc : Procedure) : Procedure := - let resolve := mapStmtExpr (resolveAliasExprNode amap) - let resolveBody : Body → Body := fun body => match body with - | .Transparent b => .Transparent (resolve b) - | .Opaque ps impl modif => .Opaque (ps.map (·.mapCondition resolve)) (impl.map resolve) (modif.map resolve) - | .Abstract ps => .Abstract (ps.map (·.mapCondition resolve)) - | .External => .External - { proc with - body := resolveBody proc.body - inputs := proc.inputs.map fun p => { p with type := resolveAliasType amap p.type } - outputs := proc.outputs.map fun p => { p with type := resolveAliasType amap p.type } - preconditions := proc.preconditions.map (·.mapCondition resolve) - decreases := proc.decreases.map resolve - invokeOn := proc.invokeOn.map resolve } - -def resolveAliasInType (amap : AliasMap) (td : TypeDefinition) : TypeDefinition := - match td with - | .Composite ct => - .Composite { ct with - fields := ct.fields.map fun f => { f with type := resolveAliasType amap f.type } - instanceProcedures := ct.instanceProcedures.map (resolveAliasInProc amap) } - | .Constrained ct => - let resolve := mapStmtExpr (resolveAliasExprNode amap) - .Constrained { ct with - base := resolveAliasType amap ct.base - constraint := resolve ct.constraint - witness := resolve ct.witness } - | .Datatype dt => - .Datatype { dt with - constructors := dt.constructors.map fun ctor => - { ctor with args := ctor.args.map fun p => { p with type := resolveAliasType amap p.type } } } - | .Alias _ => td -- will be removed - -/-- Eliminate all type aliases from a program. Replaces `UserDefined` references - with the alias target (transitively) and removes `.Alias` entries from `program.types`. -/ +/-- Eliminate all type aliases from a program. Replaces every `UserDefined` + reference (in any type position, via `mapProgramHighTypes`) with the alias + target (transitively) and removes `.Alias` entries from `program.types`. -/ public def typeAliasElim (_model : SemanticModel) (program : Program) : Program := let amap := buildAliasMap program.types if amap.isEmpty then program else - { program with - staticProcedures := program.staticProcedures.map (resolveAliasInProc amap) - staticFields := program.staticFields.map fun f => { f with type := resolveAliasType amap f.type } - types := (program.types.filter fun | .Alias _ => false | _ => true).map (resolveAliasInType amap) - constants := program.constants.map fun c => { c with - type := resolveAliasType amap c.type - initializer := c.initializer.map (mapStmtExpr (resolveAliasExprNode amap)) } } + let program := mapProgramHighTypes (resolveAliasType amap) program + { program with types := program.types.filter fun | .Alias _ => false | _ => true } /-- Pipeline pass: type alias elimination. -/ public def typeAliasElimPass : LaurelPass where diff --git a/Strata/Languages/Laurel/TypeHierarchy.lean b/Strata/Languages/Laurel/TypeHierarchy.lean index 865e1dc311..7ada70ac30 100644 --- a/Strata/Languages/Laurel/TypeHierarchy.lean +++ b/Strata/Languages/Laurel/TypeHierarchy.lean @@ -143,59 +143,6 @@ partial def compositeRefToComposite (composites : Std.HashSet String) (ty : High | .Intersection tys => { ty with val := .Intersection (tys.map (compositeRefToComposite composites ·)) } | _ => ty -/-- Rewrite composite references in an assignment target's declared type. -/ -private def rewriteCompositeVar (composites : Std.HashSet String) (v : VariableMd) : VariableMd := - match v.val with - | .Declare param => ⟨.Declare { param with type := compositeRefToComposite composites param.type }, v.source⟩ - | _ => v - -/-- Rewrite composite references in the type positions carried by a single - `StmtExpr` node (local declarations and quantifier binders). `IsType`/`AsType` - are intentionally left untouched: they have already been lowered to type-tag - lookups by `rewriteTypeHierarchyNode`, and their type argument is a type-test - target rather than a value type. -/ -private def rewriteCompositeExprNode (composites : Std.HashSet String) (expr : StmtExprMd) : StmtExprMd := - match expr.val with - | .Assign targets value => - ⟨.Assign (targets.map (rewriteCompositeVar composites)) value, expr.source⟩ - | .Var (.Declare param) => - ⟨.Var (.Declare { param with type := compositeRefToComposite composites param.type }), expr.source⟩ - | .Quantifier mode param trigger body => - ⟨.Quantifier mode { param with type := compositeRefToComposite composites param.type } trigger body, expr.source⟩ - | _ => expr - -/-- Rewrite every composite reference type in a procedure (parameters, body, - contracts) to `Composite`. -/ -def rewriteCompositeInProc (composites : Std.HashSet String) (proc : Procedure) : Procedure := - let f := mapStmtExpr (rewriteCompositeExprNode composites) - let rewriteBody : Body → Body := fun body => match body with - | .Transparent b => .Transparent (f b) - | .Opaque ps impl modif => .Opaque (ps.map (·.mapCondition f)) (impl.map f) (modif.map f) - | .Abstract ps => .Abstract (ps.map (·.mapCondition f)) - | .External => .External - { proc with - body := rewriteBody proc.body - inputs := proc.inputs.map fun p => { p with type := compositeRefToComposite composites p.type } - outputs := proc.outputs.map fun p => { p with type := compositeRefToComposite composites p.type } - preconditions := proc.preconditions.map (·.mapCondition f) - decreases := proc.decreases.map f - invokeOn := proc.invokeOn.map f } - -/-- Rewrite composite reference types appearing inside a type definition - (datatype constructor arguments, constrained-type base/constraint/witness). -/ -def rewriteCompositeInType (composites : Std.HashSet String) (td : TypeDefinition) : TypeDefinition := - match td with - | .Datatype dt => - .Datatype { dt with constructors := dt.constructors.map fun ctor => - { ctor with args := ctor.args.map fun p => { p with type := compositeRefToComposite composites p.type } } } - | .Constrained ct => - let f := mapStmtExpr (rewriteCompositeExprNode composites) - .Constrained { ct with - base := compositeRefToComposite composites ct.base - constraint := f ct.constraint - witness := f ct.witness } - | _ => td - /-- Type hierarchy transformation pass (Laurel → Laurel). @@ -213,28 +160,31 @@ def typeHierarchyTransform (model: SemanticModel) (program : Program) : Program .Datatype { name := "TypeTag", typeArgs := [], constructors := compositeNames.map fun n => { name := (mkId $ n ++ "_TypeTag"), args := [] } } let typeHierarchyConstants := generateTypeHierarchyDecls model program let (procs', _) := (program.staticProcedures.mapM (mapProcedureM (mapStmtExprM rewriteTypeHierarchyNode))).run {} - -- Now that `New`/`IsType` have been lowered (they needed the original - -- composite names), flatten every remaining composite reference type to the - -- `Composite` datatype so the program re-resolves consistently. - let compositeSet : Std.HashSet String := - compositeNames.foldl (init := {}) (·.insert ·) - let procs' := procs'.map (rewriteCompositeInProc compositeSet) -- Update the Composite datatype to include the typeTag field (introduced in this phase) let typeTagTy : HighTypeMd := ⟨.UserDefined "TypeTag", none⟩ let remainingTypes := program.types.map fun td => - match (rewriteCompositeInType compositeSet td) with + match td with | .Datatype dt => if dt.name.text == "Composite" then .Datatype { dt with constructors := dt.constructors.map fun c => if c.name.text == "MkComposite" then { c with args := c.args ++ [{ name := ("typeTag" : Identifier), type := typeTagTy }] } else c } - else .Datatype dt - | other => other - { program with - staticProcedures := procs', - types := [typeTagDatatype] ++ remainingTypes, - constants := program.constants ++ typeHierarchyConstants } + else td + | _ => td + let transformed : Program := + { program with + staticProcedures := procs', + types := [typeTagDatatype] ++ remainingTypes, + constants := program.constants ++ typeHierarchyConstants } + -- Now that `New`/`IsType` have been lowered (they needed the original + -- composite names), flatten every remaining composite reference type to the + -- `Composite` datatype so the program re-resolves consistently. The + -- program-wide `HighType` traversal lives in `MapStmtExpr` so that every + -- type position is covered uniformly. + let compositeSet : Std.HashSet String := + compositeNames.foldl (init := {}) (·.insert ·) + mapProgramHighTypes (compositeRefToComposite compositeSet) transformed /-- Pipeline pass: type hierarchy transform. -/ public def typeHierarchyTransformPass : LaurelPass where From 903f89e6b55e8d1dc32dd225f4e891431b5627ed Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 18 Jun 2026 13:41:45 +0000 Subject: [PATCH 097/115] Allow dangling addition in blocks --- Strata/Languages/Laurel/Resolution.lean | 39 ++++++++++++------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/Strata/Languages/Laurel/Resolution.lean b/Strata/Languages/Laurel/Resolution.lean index 225d643403..ba6279a108 100644 --- a/Strata/Languages/Laurel/Resolution.lean +++ b/Strata/Languages/Laurel/Resolution.lean @@ -722,12 +722,24 @@ def Synth.resolveStmtExpr (exprMd : StmtExprMd) : ResolveM (StmtExprMd × HighTy | .Hole det (some ty) => let ty' ← resolveHighType ty pure (.Hole det (some ty'), ty') - | _ => - let unknown : HighTypeMd := { val := .Unknown, source := source } - typeMismatch source (some expr) - "this expression's type cannot be synthesized; try to annotate it or use it in a context where there is an expected type" - unknown - pure (expr, unknown) + | .Var (.Declare param) => do + let r ← Check.varDeclare param source + return (r, ⟨ .TVoid, source ⟩) + | .While cond invs dec body => do + let r ← Check.while exprMd cond invs dec body source (by rw [h_node]) + return (r, ⟨ .TVoid, source ⟩) + | .Exit target => do + let r ← Check.exit target source + return (r, ⟨ .TVoid, source ⟩) + | .Return val => do + let r ← Check.return exprMd val source (by rw [h_node]) + return (r, ⟨ .TVoid, source ⟩) + | .Assert ⟨condExpr, summary, free⟩ => do + let r ← Check.assert exprMd condExpr summary free source (by rw [h_node]) + return (r, ⟨ .TVoid, source ⟩) + | .Assume cond => do + let r ← Check.assume exprMd cond source (by rw [h_node]) + return (r, ⟨ .TVoid, source ⟩) return ({ val := val', source := source }, ty) termination_by (exprMd, 2) decreasing_by all_goals first @@ -776,16 +788,6 @@ def Check.resolveStmtExpr (exprMd : StmtExprMd) (expected : HighTypeMd) : Resolv Check.assign exprMd targets value expected source (by rw [h_node]) | .Hole det none => pure (Check.holeNone det expected source) | .Hole det (some ty) => Check.holeSome det ty expected source - | .Var (.Declare param) => Check.varDeclare param source - | .While cond invs dec body => - Check.while exprMd cond invs dec body source (by rw [h_node]) - | .Exit target => Check.exit target source - | .Return val => - Check.return exprMd val source (by rw [h_node]) - | .Assert ⟨condExpr, summary, free⟩ => - Check.assert exprMd condExpr summary free source (by rw [h_node]) - | .Assume cond => - Check.assume exprMd cond source (by rw [h_node]) | .Old val => Check.old exprMd val expected source (by rw [h_node]) | .ProveBy val proof => @@ -1159,10 +1161,7 @@ def Synth.emptyBlock (source : Option FileRange) : HighTypeMd := statement when the block itself sits in statement position (`expected = TVoid`). -/ def Check.statement (s : StmtExprMd) : ResolveM StmtExprMd := do - match s.val with - | .StaticCall .. | .InstanceCall .. | .IncrDecr .. => - let (s', _) ← Synth.resolveStmtExpr s; pure s' - | _ => Check.resolveStmtExpr s { val := .TVoid, source := s.source } + let (s', _) ← Synth.resolveStmtExpr s; pure s' termination_by (s, 4) decreasing_by all_goals (apply Prod.Lex.right; decide) From 1a0e8ae86011685b697b46d6cfd4378ef42b79ba Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 18 Jun 2026 13:45:24 +0000 Subject: [PATCH 098/115] Fix diagnostic and test --- Strata/Languages/Laurel/LaurelToCoreTranslator.lean | 2 ++ .../Examples/Fundamentals/T2_ImpureExpressionsError.lean | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index 59a1557aed..5ac3be0abb 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -285,6 +285,8 @@ def translateExpr (expr : StmtExprMd) throwExprDiagnostic $ diagnosticFromSource expr.source s!"FieldSelect should have been eliminated by heap parameterization: {Std.ToFormat.format target}#{fieldId.text}" DiagnosticType.StrataBug | .Block (⟨ .Assign _ _, assignSource⟩ :: tail) _ => disallowed assignSource "destructive assignments are not supported in transparent bodies or contracts" + | .Block (⟨ .While _ _ _ _, whileSource⟩ :: tail) _ => + disallowed whileSource "loops are not supported in functions or contracts" | .Block (head :: tail) _ => throwExprDiagnostic $ diagnosticFromSource expr.source s!"block expression starting with {head.val.constructorName} should have been lowered in a separate pass" DiagnosticType.StrataBug | .Block [] _ => diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean index 41e05dc33c..d1e67ff6f6 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressionsError.lean @@ -34,8 +34,9 @@ function functionWithMutatingAssignment(x: int): int function functionWithWhile(x: int): int { - while(false) {} + while(false) {}; //^^^^^^^^^^^^^^^ error: loops are not supported in functions or contracts + 3 }; function functionCallingHasMutationAssignment(x: int): int { From 018939b55cb64dd11f1fcbc830b203ff9a97b6ab Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 18 Jun 2026 13:49:50 +0000 Subject: [PATCH 099/115] Remove function return type expectation check --- Strata/Languages/Laurel/Resolution.lean | 34 +++++-------------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/Strata/Languages/Laurel/Resolution.lean b/Strata/Languages/Laurel/Resolution.lean index ba6279a108..4fd0972792 100644 --- a/Strata/Languages/Laurel/Resolution.lean +++ b/Strata/Languages/Laurel/Resolution.lean @@ -2640,41 +2640,21 @@ def resolveParameter (param : Parameter) : ResolveM Parameter := do output: a single output `T` for single-output functional procedures, `Unknown` otherwise. Bodies without an impl block (`Abstract`, `External`) ignore `expected`. -/ -def resolveBody (body : Body) (expected : HighTypeMd) : ResolveM Body := do +def resolveBody (body : Body) : ResolveM Body := do match body with | .Transparent b => - let b' ← Check.resolveStmtExpr b expected + let (b', _) ← Synth.resolveStmtExpr b return .Transparent b' | .Opaque posts impl mods => let posts' ← posts.mapM (·.mapM resolveStmtExpr) - let impl' ← impl.mapM (Check.resolveStmtExpr · expected) + let impl' ← impl.mapM Synth.resolveStmtExpr let mods' ← mods.mapM resolveStmtExpr - return .Opaque posts' impl' mods' + return .Opaque posts' (impl'.map (fun t => t.1)) mods' | .Abstract posts => let posts' ← posts.mapM (·.mapM resolveStmtExpr) return .Abstract posts' | .External => return .External -/-- Compute the expected *value type* `A` for a procedure body, i.e. - the `A` in `Γ ⊢ body ⇐ A`. Functional procedures with a single - output `T` expect `A = T`: the body's last statement is the result - and must produce a `T`. Non-functional procedures expect - `A = Unknown`: their body is run as a statement and the last - statement's value (if any) is discarded — outputs are observed via - `return e` (whose payload is matched against the procedure's - declared outputs by `Resolution.Check.return`) or via named-output - assignment. - - This computes only the body's value type. The procedure's declared - output list is bound separately by the procedure rule - (`resolveProcedure` / `resolveInstanceProcedure`) into - `ResolveState.answerType`. -/ -private def procedureBodyType (isFunctional : Bool) (outputs : List Parameter) - (source : Option FileRange) : HighTypeMd := - match isFunctional, outputs with - | true, [singleOutput] => singleOutput.type - | _, _ => { val := .Unknown, source := source } - /-- (Procedure) ``` T_o-bar = proc.outputs.types A = procedureBodyType proc @@ -2696,12 +2676,11 @@ def resolveProcedure (proc : Procedure) : ResolveM Procedure := do let dec' ← proc.decreases.mapM resolveStmtExpr let savedAnswer := (← get).answerType modify fun s => { s with answerType := some (outputs'.map (·.type)) } - let bodyExpected := procedureBodyType proc.isFunctional outputs' proc.name.source -- Pre-register the implicit `bodyLabel` block that the LaurelToCore -- translator wraps every body in (`Core.Statement.block bodyLabel …`), -- so that frontends emitting `Exit bodyLabel` for early-return lowering -- (e.g. PythonToLaurel) don't trip Check.exit's label-scope check. - let body' ← withLabel (some bodyLabel) <| resolveBody proc.body bodyExpected + let body' ← withLabel (some bodyLabel) <| resolveBody proc.body modify fun s => { s with answerType := savedAnswer } -- Transparent (static) procedure bodies are supported (#1215): the -- TransparencyPass derives a functional `$asFunction` copy, and the @@ -2740,9 +2719,8 @@ def resolveInstanceProcedure (typeName : Identifier) (proc : Procedure) : Resolv let dec' ← proc.decreases.mapM resolveStmtExpr let savedAnswer := (← get).answerType modify fun s => { s with answerType := some (outputs'.map (·.type)) } - let bodyExpected := procedureBodyType proc.isFunctional outputs' proc.name.source -- See `resolveProcedure` for the rationale on `bodyLabel`. - let body' ← withLabel (some bodyLabel) <| resolveBody proc.body bodyExpected + let body' ← withLabel (some bodyLabel) <| resolveBody proc.body modify fun s => { s with answerType := savedAnswer } if !proc.isFunctional && body'.isTransparent && !proc.name.text.any (· == '$') then let diag := diagnosticFromSource proc.name.source From 8d34b328bea521065716deba107c375142e9b484 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 18 Jun 2026 13:51:50 +0000 Subject: [PATCH 100/115] Update resolver check to use procedure instead of function --- StrataTest/Languages/Laurel/ResolutionTypeCheckTests.lean | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/StrataTest/Languages/Laurel/ResolutionTypeCheckTests.lean b/StrataTest/Languages/Laurel/ResolutionTypeCheckTests.lean index ce3b24e885..e3888db451 100644 --- a/StrataTest/Languages/Laurel/ResolutionTypeCheckTests.lean +++ b/StrataTest/Languages/Laurel/ResolutionTypeCheckTests.lean @@ -100,14 +100,14 @@ procedure invalidAssignment() opaque { }; #end -/-! ## Function return type checks -/ +/-! ## Procedure return type checks -/ #eval testLaurelResolution <| #strata program Laurel; -function foo(): int { - true -//^^^^ error: expected 'int', got 'bool' +procedure foo(): int { + return true +// ^^^^ error: expected 'int', got 'bool' }; #end From d9b39acdc2f489a46fc0b9f510aa8e55c3a2f5a1 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 18 Jun 2026 15:05:10 +0000 Subject: [PATCH 101/115] Small improvements --- Strata/Languages/Laurel/HeapParameterization.lean | 6 +++--- Strata/Languages/Laurel/LaurelCompilationPipeline.lean | 4 ++-- Strata/Languages/Laurel/LaurelToCoreTranslator.lean | 1 - Strata/Util/FileRange.lean | 8 ++++---- StrataDDM/StrataDDM/Util/SourceRange.lean | 2 +- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Strata/Languages/Laurel/HeapParameterization.lean b/Strata/Languages/Laurel/HeapParameterization.lean index 8e5eeaec8a..f228d6c477 100644 --- a/Strata/Languages/Laurel/HeapParameterization.lean +++ b/Strata/Languages/Laurel/HeapParameterization.lean @@ -483,8 +483,8 @@ def heapTransformProcedure (model: SemanticModel) (proc : Procedure) : Transform -- Type the heap parameters as the prelude `Heap` datatype so they stay -- consistent with the generated heap functions (`readField`, `updateField`, -- `increment`, `Heap..nextReference!`), all of which are declared over `Heap`. - let heapInParam : Parameter := { name := heapInName, type := ⟨.UserDefined "Heap", none⟩ } - let heapOutParam : Parameter := { name := heapName, type := ⟨.UserDefined "Heap", none⟩ } + let heapInParam : Parameter := { name := heapInName, type := ⟨.UserDefined "Heap", proc.name.source⟩ } + let heapOutParam : Parameter := { name := heapName, type := ⟨.UserDefined "Heap", proc.name.source⟩ } let inputs' := heapInParam :: proc.inputs let outputs' := heapOutParam :: proc.outputs @@ -525,7 +525,7 @@ def heapTransformProcedure (model: SemanticModel) (proc : Procedure) : Transform -- This procedure only reads the heap - add $heap as input only. -- Use the prelude `Heap` datatype for the parameter type (see the -- writes-heap branch above for rationale). - let heapParam : Parameter := { name := heapName, type := ⟨.UserDefined "Heap", none⟩ } + let heapParam : Parameter := { name := heapName, type := ⟨.UserDefined "Heap", proc.name.source⟩ } let inputs' := heapParam :: proc.inputs let preconditions' ← proc.preconditions.mapM (·.mapM (heapTransformExpr heapName model)) diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index e0ea6b7e74..fb47f63def 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -145,12 +145,12 @@ private def runLaurelPasses -- Initial resolution let result := resolve program - let resolutionErrors : List DiagnosticModel := result.errors.toList + let resolutionErrors : Std.HashSet DiagnosticModel := Std.HashSet.ofArray result.errors let (program, model) := (result.program, result.model) let mut program := program let mut model := model - let mut allDiags : List DiagnosticModel := resolutionErrors + let mut allDiags : List DiagnosticModel := result.errors.toList let mut allStats : Statistics := {} for pass in laurelPipeline do diff --git a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean index 5ac3be0abb..5797395ed9 100644 --- a/Strata/Languages/Laurel/LaurelToCoreTranslator.lean +++ b/Strata/Languages/Laurel/LaurelToCoreTranslator.lean @@ -83,7 +83,6 @@ def translateType (ty : HighTypeMd) : TranslateM LMonoTy := do | .TMap keyType valueType => return Core.mapTy (← translateType keyType) (← translateType valueType) | .UserDefined name => match model.get? name with - | some (.compositeType _) => return .tcons "Composite" [] -- TODO no longer needed? | some (.datatypeDefinition dt) => return .tcons dt.name.text [] | some (.datatypeConstructor typeName _) => return .tcons typeName.text [] | _ => do -- resolution should have already emitted a diagnostic diff --git a/Strata/Util/FileRange.lean b/Strata/Util/FileRange.lean index ce6da121ea..cea135549b 100644 --- a/Strata/Util/FileRange.lean +++ b/Strata/Util/FileRange.lean @@ -18,7 +18,7 @@ abbrev SourceRange.none := StrataDDM.SourceRange.none inductive Uri where | file (path: String) - deriving DecidableEq, Repr, Inhabited + deriving DecidableEq, Repr, Inhabited, Hashable instance : Std.ToFormat Uri where format fr := private match fr with | .file path => path @@ -26,7 +26,7 @@ instance : Std.ToFormat Uri where structure FileRange where file: Uri range: SourceRange - deriving DecidableEq, Repr, Inhabited + deriving DecidableEq, Repr, Inhabited, Hashable instance : Std.ToFormat FileRange where format fr := private f!"{fr.file}:{fr.range}" @@ -76,7 +76,7 @@ def FileRange.format (fr : FileRange) (fileMap : Option Lean.FileMap) (includeEn f!"{baseName}({fr.range.start}-{fr.range.stop})" inductive DiagnosticType where | Warning | UserError | NotYetImplemented | StrataBug - deriving Repr, BEq, Inhabited, Lean.ToExpr + deriving Repr, BEq, Inhabited, Lean.ToExpr, Hashable /-- A diagnostic model that holds a file range and a message. This can be converted to a formatted string using a FileMap. -/ @@ -84,7 +84,7 @@ structure DiagnosticModel where fileRange : FileRange message : String type : DiagnosticType - deriving Repr, BEq, Inhabited + deriving Repr, BEq, Inhabited, Hashable instance : Inhabited DiagnosticModel where default := { fileRange := FileRange.unknown, message := "", type := .UserError } diff --git a/StrataDDM/StrataDDM/Util/SourceRange.lean b/StrataDDM/StrataDDM/Util/SourceRange.lean index e1107f82f9..78bcd84efb 100644 --- a/StrataDDM/StrataDDM/Util/SourceRange.lean +++ b/StrataDDM/StrataDDM/Util/SourceRange.lean @@ -25,7 +25,7 @@ structure SourceRange where start : String.Pos.Raw /-- One past the end of the range. -/ stop : String.Pos.Raw -deriving DecidableEq, Inhabited, Repr +deriving DecidableEq, Inhabited, Repr, Hashable namespace SourceRange From fcb3903f84e0b7cf0c3e5635d991eff9d4e89c0e Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 18 Jun 2026 15:33:54 +0000 Subject: [PATCH 102/115] Fix merge --- Strata/Languages/Laurel/Resolution.lean | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Strata/Languages/Laurel/Resolution.lean b/Strata/Languages/Laurel/Resolution.lean index b62ee85040..de3ae18195 100644 --- a/Strata/Languages/Laurel/Resolution.lean +++ b/Strata/Languages/Laurel/Resolution.lean @@ -2643,19 +2643,11 @@ def resolveParameter (param : Parameter) : ResolveM Parameter := do def resolveBody (body : Body) : ResolveM Body := do match body with | .Transparent b => -<<<<<<< HEAD - let b' ← Check.resolveStmtExpr b ⟨ HighType.Unknown, b.source ⟩ - return .Transparent b' - | .Opaque posts impl mods => - let posts' ← posts.mapM (·.mapM resolveStmtExpr) - let impl' ← impl.mapM (Check.resolveStmtExpr · ⟨ HighType.Unknown, default ⟩) -======= let (b', _) ← Synth.resolveStmtExpr b return .Transparent b' | .Opaque posts impl mods => let posts' ← posts.mapM (·.mapM resolveStmtExpr) let impl' ← impl.mapM Synth.resolveStmtExpr ->>>>>>> fixResolutionErrorsDuringPhases let mods' ← mods.mapM resolveStmtExpr return .Opaque posts' (impl'.map (fun t => t.1)) mods' | .Abstract posts => @@ -2684,13 +2676,10 @@ def resolveProcedure (proc : Procedure) : ResolveM Procedure := do let dec' ← proc.decreases.mapM resolveStmtExpr let savedAnswer := (← get).answerType modify fun s => { s with answerType := some (outputs'.map (·.type)) } -<<<<<<< HEAD -======= -- Pre-register the implicit `bodyLabel` block that the LaurelToCore -- translator wraps every body in (`Core.Statement.block bodyLabel …`), -- so that frontends emitting `Exit bodyLabel` for early-return lowering -- (e.g. PythonToLaurel) don't trip Check.exit's label-scope check. ->>>>>>> fixResolutionErrorsDuringPhases let body' ← withLabel (some bodyLabel) <| resolveBody proc.body modify fun s => { s with answerType := savedAnswer } -- Transparent (static) procedure bodies are supported (#1215): the From 4efaebd45f81e17c7ffca6085aef96ea46146f9a Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Thu, 18 Jun 2026 16:19:59 +0000 Subject: [PATCH 103/115] Remove obsolete test --- Strata/Languages/Laurel/LaurelCompilationPipeline.lean | 2 +- .../Laurel/Examples/Fundamentals/T3_ControlFlowError.lean | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index a9ca35e287..1b9f3e8d91 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -156,7 +156,7 @@ private def runLaurelPasses let newDiags := newErrors.toList.map fun d => { d with message := - s!"Internal error: resolution after '{pass.name}' introduced this diagnostic: {d.message}" + s!"Internal error: resolution after '{pass.name}' introduced this diagnostic: {d}. Existing diagnostics were: {resolutionErrors.toList}" type := .StrataBug } emit pass.name "laurel.st" program return (program, model, allDiags ++ newDiags, allStats) diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean index 9438d322fe..a1d8f03ad7 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T3_ControlFlowError.lean @@ -38,10 +38,4 @@ function localVariableWithoutInitializer(): int { //^^^^^^^^^^ error: local variables in functions must have initializers 3 }; - -function deadCodeAfterIfElse(x: int) returns (r: int) { - if x > 0 then { return 1 } else { return 2 }; -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: if-then-else only supported as the last statement in a block - return 3 -}; #end From 8ce6fc651b2efe387dbac3989c2a0d89c9d8fcb2 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Fri, 19 Jun 2026 12:52:38 +0000 Subject: [PATCH 104/115] Add multi output test-case to ImpureExpressions --- .../Examples/Fundamentals/T2_ImpureExpressions.lean | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean index 30b98d7bec..b8ef2dc475 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean @@ -183,4 +183,13 @@ opaque { assert y == 3 }; +procedure hasMultipleOutputs() returns (x: int, y: int) opaque { + x := 1; + y := 2 +}; + +procedure liftWithMultipleOutputs() opaque { + var x: int := (assign var y: int, var z: int := hasMultipleOutputs()); y + z +}; + #end From d7a4c3cc34bf05445b850827ba19256c600af54b Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Fri, 19 Jun 2026 13:37:30 +0000 Subject: [PATCH 105/115] Refactor liftImperative --- .../Laurel/LiftImperativeExpressions.lean | 28 +++++++++++-------- .../Fundamentals/T2_ImpureExpressions.lean | 6 ++-- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index 278d379d9f..4b99f09415 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -212,6 +212,19 @@ def transformLiftedExpr (expr : StmtExprMd) : LiftM (List StmtExprMd × StmtExpr return (newPrepends, result) termination_by (sizeOf expr, 1) +/-- +Process an expression in expression context, traversing arguments right to left. +Assignments are lifted to prependedStmts and replaced with snapshot variable references. +-/ +def transformLiftedStmt (expr : StmtExprMd) : LiftM Unit := do + let savedSubst := (← get).subst + let previousPrepends := (← get).prependedStmts + modify fun s => { s with subst := [], prependedStmts := [] } + let result ← transformStmt expr + modify fun s => { s with subst := savedSubst, prependedStmts := previousPrepends } + prependList result + termination_by (sizeOf expr, 1) + /-- Process an expression in expression context, traversing arguments right to left. Assignments are lifted to prependedStmts and replaced with snapshot variable references. @@ -283,9 +296,6 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do let seqCall := ⟨.StaticCall callee seqArgs.reverse, source⟩ return seqCall else - let seqArgsAndPrepends ← args.reverse.mapM transformLiftedExpr - let seqArgs := seqArgsAndPrepends.map (fun t => t.2) - let seqCall: StmtExprMd := ⟨.StaticCall callee seqArgs.reverse, source⟩ -- Imperative call in expression position: lift to an assignment. -- Only valid for single-output procedures (or unresolved ones where we -- fall back to a single target). Multi-output procedures in expression @@ -296,15 +306,9 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do | .instanceProcedure _ proc => proc.outputs | _ => [] let callResultVar ← freshTempVar - let callResultType ← match outputs with - | [single] => pure single.type - | _ => computeType expr - let liftedCall: List StmtExprMd := [ - ⟨ (.Var (.Declare ⟨callResultVar, callResultType⟩)), source ⟩, - ⟨.Assign [⟨ .Local callResultVar, source⟩] seqCall, source⟩ - ] - prependList liftedCall - prependList (seqArgsAndPrepends.map (fun t => t.1)).flatten + let callResultType ← computeType expr + let liftedCall: StmtExprMd := ⟨.Assign [⟨ .Declare ⟨callResultVar, callResultType⟩, source⟩] expr, source⟩ + transformLiftedStmt liftedCall return ⟨.Var (.Local callResultVar), source⟩ | .IfThenElse cond thenBranch elseBranch => diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean index b8ef2dc475..77ee3c9e3a 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean @@ -188,8 +188,8 @@ procedure hasMultipleOutputs() returns (x: int, y: int) opaque { y := 2 }; -procedure liftWithMultipleOutputs() opaque { - var x: int := (assign var y: int, var z: int := hasMultipleOutputs()); y + z -}; +//procedure liftWithMultipleOutputs() opaque { +// var x: int := (assign var y: int, var z: int := hasMultipleOutputs()); y + z +//}; #end From 0b03bff1e6d048a8e69208116405659e6a26d137 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Fri, 19 Jun 2026 13:40:47 +0000 Subject: [PATCH 106/115] Simplify lift pass --- Strata/Languages/Laurel/LiftImperativeExpressions.lean | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index 4b99f09415..93dfc64dc5 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -264,9 +264,7 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do dbg_trace "Strata bug: non-identifier targets should have been removed before the lift expression phase"; return expr - let (valuePrepends, newValue) ← transformLiftedExpr value - - prepend (⟨.Assign targets newValue, source⟩) + transformLiftedStmt expr -- Create a before-snapshot for each target and update substitutions for target in targets do @@ -279,8 +277,6 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do setSubst varName snapshotName | _ => pure () - prependList valuePrepends - return resultExpr | .PrimitiveOp op args _ => From 97e31468256cae05848bca89399e7f5e98a89b8d Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Fri, 19 Jun 2026 13:42:51 +0000 Subject: [PATCH 107/115] Bring back test --- .../Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean index 77ee3c9e3a..23885ec665 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean @@ -188,8 +188,8 @@ procedure hasMultipleOutputs() returns (x: int, y: int) opaque { y := 2 }; -//procedure liftWithMultipleOutputs() opaque { -// var x: int := (assign var y: int, var z: int := hasMultipleOutputs()); y + z -//}; +procedure liftWithMultipleOutputs() opaque { + var x: int := { assign var y: int, var z: int := hasMultipleOutputs() ; y + z } +}; #end From ffcc39d87dda619a283b757a11edc3375ad061be Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Fri, 19 Jun 2026 14:23:50 +0000 Subject: [PATCH 108/115] Recursion proof refactoring --- .../Laurel/LiftImperativeExpressions.lean | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index 93dfc64dc5..0076afa205 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -198,6 +198,13 @@ def containsAssignmentOrImperativeCall (imperativeCallees : List String) (expr : mutual +def asLifted { t: Type } (runner: LiftM t) : LiftM t := do + let savedState ← get + modify fun s => { s with prependedStmts := [], subst := []} + let result ← runner + modify fun s => savedState + return result + /-- Process an expression in expression context, traversing arguments right to left. Assignments are lifted to prependedStmts and replaced with snapshot variable references. @@ -210,7 +217,7 @@ def transformLiftedExpr (expr : StmtExprMd) : LiftM (List StmtExprMd × StmtExpr let newPrepends ← takePrepends modify fun s => { s with prependedStmts := savedPrepends, subst := savedSubst } return (newPrepends, result) - termination_by (sizeOf expr, 1) + termination_by (sizeOf expr, 3) /-- Process an expression in expression context, traversing arguments right to left. @@ -303,8 +310,10 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do | _ => [] let callResultVar ← freshTempVar let callResultType ← computeType expr - let liftedCall: StmtExprMd := ⟨.Assign [⟨ .Declare ⟨callResultVar, callResultType⟩, source⟩] expr, source⟩ - transformLiftedStmt liftedCall + + let prepends ← asLifted (transformStmtAssignImperativeCall + [⟨ .Declare ⟨callResultVar, callResultType⟩, source⟩] callee args source source) + prependList prepends return ⟨.Var (.Local callResultVar), source⟩ | .IfThenElse cond thenBranch elseBranch => @@ -485,7 +494,22 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do return ⟨.ContractOf ty seqFunc, source⟩ | _ => return expr - termination_by (sizeOf expr, 0) + termination_by (sizeOf expr, 2) + decreasing_by + all_goals (simp_all; try have := Condition.sizeOf_condition_lt ‹_›; try term_by_mem) + all_goals (apply Prod.Lex.left; try term_by_mem; try omega) + +def transformStmtAssignImperativeCall + (targets : List (AstNode Variable)) + (callee: Identifier) + (args: List StmtExprMd) + (source: Option FileRange) + (callSource: Option FileRange): LiftM (List StmtExprMd) := do + let seqArgs ← args.reverse.mapM transformExpr + let argPrepends ← takePrepends + modify fun s => { s with subst := [] } + return argPrepends ++ [⟨.Assign targets ⟨.StaticCall callee seqArgs.reverse, callSource⟩, source⟩] + termination_by (sizeOf args, 0) decreasing_by all_goals (simp_all; try have := Condition.sizeOf_condition_lt ‹_›; try term_by_mem) all_goals (apply Prod.Lex.left; try term_by_mem; try omega) @@ -533,16 +557,13 @@ def transformStmt (stmt : StmtExprMd) : LiftM (List StmtExprMd) := do match _: value with | .StaticCall callee args => let imperativeCallees := (← get).imperativeCallees - if !imperativeCallees.contains callee.text then + if imperativeCallees.contains callee.text then + transformStmtAssignImperativeCall targets callee args source callSource + else let seqValue ← transformExpr valueMd let prepends ← takePrepends modify fun s => { s with subst := [] } return prepends ++ [⟨.Assign targets seqValue, source⟩] - else - let seqArgs ← args.reverse.mapM transformExpr - let argPrepends ← takePrepends - modify fun s => { s with subst := [] } - return argPrepends ++ [⟨.Assign targets ⟨.StaticCall callee seqArgs.reverse, callSource⟩, source⟩] | _ => let seqValue ← transformExpr valueMd let prepends ← takePrepends @@ -583,7 +604,7 @@ def transformStmt (stmt : StmtExprMd) : LiftM (List StmtExprMd) := do let prepends ← takePrepends return prepends ++ [⟨.StaticCall name seqArgs, source⟩] - | .PrimitiveOp _ _ => + | .PrimitiveOp _ args => -- A `PrimitiveOp` in statement position. If it carries any side effects -- (an embedded assignment or imperative call — typically the result of -- the postfix increment lowering `(x := x + 1) - 1`), lift them out and @@ -592,7 +613,7 @@ def transformStmt (stmt : StmtExprMd) : LiftM (List StmtExprMd) := do -- `exprAsUnusedInit`. let imperativeCallees := (← get).imperativeCallees if containsAssignmentOrImperativeCall imperativeCallees stmt then - let _ ← transformExpr stmt + let _ ← args.reverse.mapM transformExpr let prepends ← takePrepends modify fun s => { s with subst := [] } return prepends From 1ef148f4cecd09cbe51f69f1a678960d45174a93 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Fri, 19 Jun 2026 14:44:16 +0000 Subject: [PATCH 109/115] Fix proofs --- .../Laurel/LiftImperativeExpressions.lean | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index 0076afa205..81c700eafb 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -237,9 +237,9 @@ Process an expression in expression context, traversing arguments right to left. Assignments are lifted to prependedStmts and replaced with snapshot variable references. -/ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do - match expr with + match h_node : expr with | AstNode.mk val source => - match val with + match h_val : val with | .Var (.Local name) => return ⟨.Var (.Local (← getSubst name)), source⟩ @@ -496,8 +496,10 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do | _ => return expr termination_by (sizeOf expr, 2) decreasing_by - all_goals (simp_all; try have := Condition.sizeOf_condition_lt ‹_›; try term_by_mem) - all_goals (apply Prod.Lex.left; try term_by_mem; try omega) + all_goals first + | (apply Prod.Lex.left; (try have := Condition.sizeOf_condition_lt ‹_›); term_by_mem) + | (apply Prod.Lex.left; simp <;> omega) + | (try subst h_node; try subst h_val; apply Prod.Lex.right; omega) def transformStmtAssignImperativeCall (targets : List (AstNode Variable)) @@ -511,8 +513,9 @@ def transformStmtAssignImperativeCall return argPrepends ++ [⟨.Assign targets ⟨.StaticCall callee seqArgs.reverse, callSource⟩, source⟩] termination_by (sizeOf args, 0) decreasing_by - all_goals (simp_all; try have := Condition.sizeOf_condition_lt ‹_›; try term_by_mem) - all_goals (apply Prod.Lex.left; try term_by_mem; try omega) + all_goals try (apply Prod.Lex.right; omega) + all_goals (try simp_all; try have := Condition.sizeOf_condition_lt ‹_›; try term_by_mem) + all_goals (try (apply Prod.Lex.left); try term_by_mem; try omega) /-- Process a statement, handling any assignments in its sub-expressions. @@ -634,8 +637,9 @@ def transformStmt (stmt : StmtExprMd) : LiftM (List StmtExprMd) := do return [stmt] termination_by (sizeOf stmt, 0) decreasing_by - all_goals (try term_by_mem) - all_goals (apply Prod.Lex.left; try term_by_mem) + all_goals try (apply Prod.Lex.right; omega) + all_goals (try simp_all; try have := Condition.sizeOf_condition_lt ‹_›; try term_by_mem) + all_goals (try (apply Prod.Lex.left); try term_by_mem; try omega) end def transformProcedureBody (source: Option FileRange) (body : StmtExprMd) : LiftM StmtExprMd := do From 34c850fe2c1d3a137ec6958cd859c41bbc02aaa7 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Mon, 22 Jun 2026 09:32:25 +0000 Subject: [PATCH 110/115] Cleanup --- .../Languages/Laurel/LaurelCompilationPipeline.lean | 6 +----- .../Languages/Laurel/LiftImperativeExpressions.lean | 13 +------------ Strata/Languages/Laurel/PushOldInward.lean | 10 +++++++++- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean index 5130759b2f..008bf898c6 100644 --- a/Strata/Languages/Laurel/LaurelCompilationPipeline.lean +++ b/Strata/Languages/Laurel/LaurelCompilationPipeline.lean @@ -106,11 +106,7 @@ def laurelPipeline : Array LoweringPass := #[ heapParameterizationPass, typeHierarchyTransformPass, modifiesClausesTransformPass, - { name := "PushOldInward" - documentation := "Distributes `old(...)` over its subexpressions until each `old` immediately wraps an inout variable. Warns on `old(e)` where `e` mentions no inout parameter and on nested `old(old(...))`." - run := fun p _m => - let (p', diags) := pushOldInward p - (p', diags, {}) }, + pushOldInwardPass, inferHoleTypesPass, eliminateDeterministicHolesPass, desugarShortCircuitPass, diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index 81c700eafb..e6f19962e3 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -197,12 +197,11 @@ def containsAssignmentOrImperativeCall (imperativeCallees : List String) (expr : mutual - def asLifted { t: Type } (runner: LiftM t) : LiftM t := do let savedState ← get modify fun s => { s with prependedStmts := [], subst := []} let result ← runner - modify fun s => savedState + modify fun _ => savedState return result /-- @@ -292,22 +291,12 @@ def transformExpr (expr : StmtExprMd) : LiftM StmtExprMd := do return ⟨.PrimitiveOp op seqArgs.reverse, source⟩ | .StaticCall callee args => - let model := (← get).model let imperativeCallees := (← get).imperativeCallees if !imperativeCallees.contains callee.text then let seqArgs ← args.reverse.mapM transformExpr let seqCall := ⟨.StaticCall callee seqArgs.reverse, source⟩ return seqCall else - -- Imperative call in expression position: lift to an assignment. - -- Only valid for single-output procedures (or unresolved ones where we - -- fall back to a single target). Multi-output procedures in expression - -- position are a bug in the upstream translation — Resolution should - -- emit a diagnostic for that case. - let outputs := match model.get callee with - | .staticProcedure proc => proc.outputs - | .instanceProcedure _ proc => proc.outputs - | _ => [] let callResultVar ← freshTempVar let callResultType ← computeType expr diff --git a/Strata/Languages/Laurel/PushOldInward.lean b/Strata/Languages/Laurel/PushOldInward.lean index 6fd6119023..c9db47123a 100644 --- a/Strata/Languages/Laurel/PushOldInward.lean +++ b/Strata/Languages/Laurel/PushOldInward.lean @@ -6,6 +6,7 @@ module public import Strata.Languages.Laurel.MapStmtExpr +public import Strata.Languages.Laurel.LaurelPass /-! # Push `Old` Inward @@ -70,12 +71,19 @@ def transformProcedurePushOld (proc : Procedure) : PushOldM Procedure := do /-- Push every `StmtExpr.Old` inward until it immediately wraps an inout variable. Returns the rewritten program and any warnings emitted. -/ -@[expose] def pushOldInward (program : Program) : Program × List DiagnosticModel := let (program', finalState) := (program.staticProcedures.mapM transformProcedurePushOld).run {} ({ program with staticProcedures := program' }, finalState.diagnostics) +/-- Pipeline pass: translate modifies clauses into ensures clauses. -/ +public def pushOldInwardPass : LoweringPass where + name := "pushOldInward" + documentation := "Distributes `old(...)` over its subexpressions until each `old` immediately wraps an inout variable. Warns on `old(e)` where `e` mentions no inout parameter and on nested `old(old(...))`." + run := fun p _ _ => + let (p', diags) := pushOldInward p + (p', diags, {}) + end -- public section end Laurel end Strata From 12b5e5985317119cda10d023a4be5db59b99e448 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Mon, 22 Jun 2026 13:25:04 +0000 Subject: [PATCH 111/115] Fix --- Strata/Languages/Laurel/ContractPass.lean | 60 ++++++++++++++++++- .../Laurel/HeapParameterizationConstants.lean | 3 - 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/Strata/Languages/Laurel/ContractPass.lean b/Strata/Languages/Laurel/ContractPass.lean index c810beda6c..9158e683ec 100644 --- a/Strata/Languages/Laurel/ContractPass.lean +++ b/Strata/Languages/Laurel/ContractPass.lean @@ -59,7 +59,7 @@ private def paramsToArgs (params : List Parameter) : List StmtExprMd := params.map fun p => mkMd (.Var (.Local p.name)) /-- Build a helper function for a single condition over the given parameters. - Preconditions pass `proc.inputs`; postconditions pass `proc.inputs ++ proc.outputs`. -/ + Preconditions pass `proc.inputs`; postconditions use `mkPostConditionProc`. -/ private def mkConditionProc (name : String) (params : List Parameter) (condition : Condition) : Procedure := { name := mkId name @@ -70,6 +70,62 @@ private def mkConditionProc (name : String) (params : List Parameter) isFunctional := true body := .Transparent condition.condition } +/-- Suffix appended to a procedure's output-parameter names when they are lowered + into a postcondition helper *function*. + + A postcondition helper takes both the procedure's inputs and outputs as plain + function parameters. When an output shares a name with an input (e.g. an inout + `$heap`, which the heap-parameterization pass lists in both `inputs` and + `outputs`), the two would collide in the helper's single parameter scope and + produce "Duplicate definition '…' is already defined in this scope". Suffixing + every output keeps the two distinct. The choice is heap-agnostic: it applies to + all outputs, not just `$heap`. -/ +public def outParamSuffix : String := "$out" + +/-- Rewrite a postcondition body so it refers to the renamed output parameters. + + In a postcondition, a bare reference to an output parameter denotes its + post-state value, while `old(x)` denotes the pre-state value of an inout + parameter (the corresponding *input*). The helper is a pure function with two + distinct parameters, so: + - bare `Var (Local n)` where `n` is an output → suffixed name (`n ++ outParamSuffix`) + - `old(Var (Local n))` → `Var (Local n)`, i.e. strip `old` and keep the + un-suffixed name so it resolves to the input parameter (functions have no + two-state semantics, so an unstripped `old` would later be rejected). + + `pushOldInward` guarantees every `old` immediately wraps a local variable. -/ +private def renameOutputsInPostExpr (outputNames : List String) (expr : StmtExprMd) : StmtExprMd := + let suffixIfOutput (n : Identifier) : Identifier := + if outputNames.contains n.text then mkId (n.text ++ outParamSuffix) else n + mapStmtExprPrePostM (m := Id) + (fun e => + match e.val with + | .Old value => + match value.val with + | .Var (.Local _) => some value + | _ => none + | _ => none) + (fun e => + match e.val with + | .Var (.Local n) => ⟨.Var (.Local (suffixIfOutput n)), e.source⟩ + | _ => e) + expr + +/-- Build a postcondition helper function over the procedure's inputs and outputs. + Output parameters are renamed (see `outParamSuffix`) to avoid colliding with + identically-named inputs, and the condition body is rewritten to match. -/ +private def mkPostConditionProc (name : String) (inputs outputs : List Parameter) + (condition : Condition) : Procedure := + let outputNames := outputs.map (·.name.text) + let renamedOutputs := outputs.map (fun p => { p with name := mkId (p.name.text ++ outParamSuffix) }) + { name := mkId name + inputs := inputs ++ renamedOutputs + outputs := [⟨mkId "$result", { val := .TBool, source := none }⟩] + preconditions := [] + decreases := none + isFunctional := true + body := .Transparent (renameOutputsInPostExpr outputNames condition.condition) } + /-- Information about a procedure's contracts. -/ private structure ContractInfo where preNames : List (String × Option String) -- (procName, summary) for each precondition @@ -336,7 +392,7 @@ def lowerContracts (program : Program) : Program × List DiagnosticModel := let preProcs := proc.preconditions.zipIdx.map fun (c, i) => mkConditionProc (preCondProcName proc.name.text i) proc.inputs c let postProcs := postconds.zipIdx.map fun (c, i) => - mkConditionProc (postCondProcName proc.name.text i) (proc.inputs ++ proc.outputs) c + mkPostConditionProc (postCondProcName proc.name.text i) proc.inputs proc.outputs c preProcs ++ postProcs -- Transform procedures: strip contracts, add assume/assert, rewrite call sites diff --git a/Strata/Languages/Laurel/HeapParameterizationConstants.lean b/Strata/Languages/Laurel/HeapParameterizationConstants.lean index 7258f29d6a..a9c6e80206 100644 --- a/Strata/Languages/Laurel/HeapParameterizationConstants.lean +++ b/Strata/Languages/Laurel/HeapParameterizationConstants.lean @@ -18,9 +18,6 @@ public section /-- The name of the heap variable used by the heap parameterization pass. -/ def heapVarName : Identifier := "$heap" -/-- The name of the input heap parameter used by the heap parameterization pass. -/ -def heapInVarName : Identifier := "$heap_in" - /-- The Laurel Core prelude defines the heap model types and operations used by the Laurel-to-Core translator. These declarations are expressed From bfc0b6c896005a42b35dc51fe372a92b3fcca788 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Mon, 22 Jun 2026 13:38:45 +0000 Subject: [PATCH 112/115] Fix test --- Strata/Languages/Laurel/ContractPass.lean | 27 ++++++++++++++++++- .../Examples/Objects/T9_OldHeapTwoState.lean | 2 +- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/Strata/Languages/Laurel/ContractPass.lean b/Strata/Languages/Laurel/ContractPass.lean index 9158e683ec..786d25e440 100644 --- a/Strata/Languages/Laurel/ContractPass.lean +++ b/Strata/Languages/Laurel/ContractPass.lean @@ -223,6 +223,30 @@ private def mkPostAssumes (info : ContractInfo) else info.postNames.map fun (name, _) => ⟨.Assume (mkCall name (tempRefs ++ outputArgs)), src⟩ +/-- Names of the callee's inout parameters: those appearing in both the input and + output lists. By the Laurel inout convention an inout is declared by giving the + input and output the same name, so at a call site the inout argument is the same + variable as the corresponding output target — and the Core lowering relies on + that identity. -/ +private def ContractInfo.inoutNames (info : ContractInfo) : List String := + info.inputParams.filterMap fun p => + if info.outputParams.any (·.name.text == p.name.text) then some p.name.text else none + +/-- Build the positional argument list for the rewritten call. + + Ordinary inputs are passed via their snapshot temp (so pre/postconditions can + reference the pre-call value). Inout inputs, however, are passed as the + *original* argument variable rather than the temp: the call mutates that + variable in place, so it must coincide with the corresponding output target + (the Laurel inout invariant). The temp is still created and used by + `mkPostAssumes` to supply the inout's pre-state to `$post*`. -/ +private def mkCallArgs (info : ContractInfo) (origArgs tempRefs : List StmtExprMd) : List StmtExprMd := + let inout := info.inoutNames + tempRefs.zipIdx.map fun (tempRef, i) => + match info.inputParams[i]? with + | some p => if inout.contains p.name.text then origArgs[i]?.getD tempRef else tempRef + | none => tempRef + /-- Rewrite call sites in a statement/expression tree. -/ private def rewriteCallSites (contractInfoMap : Std.HashMap String ContractInfo) (isFunctional : Bool) (expr : StmtExprMd) : ContractM StmtExprMd := do @@ -269,7 +293,8 @@ private def rewriteCallSites (contractInfoMap : Std.HashMap String ContractInfo) | none => return e' | _ => return e')) let (tempDecls, tempRefs) ← mkTempAssignments args' info.inputParams src - let callWithTemps : StmtExprMd := ⟨.Assign targets ⟨.StaticCall callee tempRefs, callSrc⟩, src⟩ + let callArgs := mkCallArgs info args' tempRefs + let callWithTemps : StmtExprMd := ⟨.Assign targets ⟨.StaticCall callee callArgs, callSrc⟩, src⟩ let preCheck := mkPreChecks info isFunctional tempRefs src let outputArgs := targets.filterMap fun t => match t.val with diff --git a/StrataTest/Languages/Laurel/Examples/Objects/T9_OldHeapTwoState.lean b/StrataTest/Languages/Laurel/Examples/Objects/T9_OldHeapTwoState.lean index 6cc0fc85ff..102c44d637 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/T9_OldHeapTwoState.lean +++ b/StrataTest/Languages/Laurel/Examples/Objects/T9_OldHeapTwoState.lean @@ -210,7 +210,7 @@ procedure bumpUnderWildcard(c: Cell) procedure wrongOldRelation(c: Cell) opaque ensures c#value == old(c#value) - 1 -// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion could not be proved +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: postcondition could not be proved modifies c { c#value := c#value + 1 From 2e2a7b32e21d55c412de54209707b9161f6d9ee7 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Mon, 22 Jun 2026 16:11:44 +0000 Subject: [PATCH 113/115] Fix tests --- .../Laurel/Examples/Objects/T1_MutableFields.lean | 2 +- .../Laurel/Examples/Objects/T9_OldHeapTwoState.lean | 2 +- .../Laurel/LiftImperativeCallsInAssertTest.lean | 9 +++------ 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/StrataTest/Languages/Laurel/Examples/Objects/T1_MutableFields.lean b/StrataTest/Languages/Laurel/Examples/Objects/T1_MutableFields.lean index 2df0e6c486..4bca875744 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/T1_MutableFields.lean +++ b/StrataTest/Languages/Laurel/Examples/Objects/T1_MutableFields.lean @@ -9,7 +9,7 @@ import StrataTest.Util.TestLaurel open StrataTest.Util open Strata -#eval testLaurel +#eval testLaurelKeepIntermediates #strata program Laurel; composite Container { diff --git a/StrataTest/Languages/Laurel/Examples/Objects/T9_OldHeapTwoState.lean b/StrataTest/Languages/Laurel/Examples/Objects/T9_OldHeapTwoState.lean index 102c44d637..89681f2234 100644 --- a/StrataTest/Languages/Laurel/Examples/Objects/T9_OldHeapTwoState.lean +++ b/StrataTest/Languages/Laurel/Examples/Objects/T9_OldHeapTwoState.lean @@ -59,7 +59,7 @@ procedure bumpCell(c: Cell) procedure bumpCellWrong(c: Cell) opaque ensures c#value == old(c#value) + 1 -// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assertion could not be proved +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: postcondition could not be proved modifies c { c#value := c#value + 2 diff --git a/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean b/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean index ba90bb447c..dcc3135105 100644 --- a/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean +++ b/StrataTest/Languages/Laurel/LiftImperativeCallsInAssertTest.lean @@ -40,8 +40,7 @@ info: procedure impure(): int }; procedure test() { - var $cndtn_0: int; - $cndtn_0 := impure(); + var $cndtn_0: int := impure(); assert $cndtn_0 == 1 }; -/ @@ -91,8 +90,7 @@ info: procedure impure(): int }; procedure test() { - var $cndtn_0: int; - $cndtn_0 := impure(); + var $cndtn_0: int := impure(); assume $cndtn_0 == 1 }; -/ @@ -123,8 +121,7 @@ info: procedure multi_out(x: int) }; procedure test() { - var $cndtn_0: BUG_MultiValuedExpr; - $cndtn_0 := multi_out(5); + var $cndtn_0: BUG_MultiValuedExpr := multi_out(5); assert $cndtn_0 == 6 }; -/ From 5f46178199d24cc847da57a7af1378adff7ff4e6 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Mon, 22 Jun 2026 17:15:51 +0000 Subject: [PATCH 114/115] Update expect file --- .../expected_laurel/test_class_mixed_init.expected | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/StrataPython/StrataPythonTest/expected_laurel/test_class_mixed_init.expected b/StrataPython/StrataPythonTest/expected_laurel/test_class_mixed_init.expected index e93d873e67..1966e6aa7b 100644 --- a/StrataPython/StrataPythonTest/expected_laurel/test_class_mixed_init.expected +++ b/StrataPython/StrataPythonTest/expected_laurel/test_class_mixed_init.expected @@ -3,4 +3,4 @@ test_class_mixed_init.py(19, 0): ✔️ always true if reached - class with init test_class_mixed_init.py(19, 0): ✔️ always true if reached - precondition test_class_mixed_init.py(19, 0): ✔️ always true if reached - class with init DETAIL: 4 passed, 0 failed, 0 inconclusive -RESULT: Inconclusive +RESULT: Analysis success From 97870a3f00ca4468248dd3ebdb8701ae0b4d92f7 Mon Sep 17 00:00:00 2001 From: Remy Willems Date: Tue, 23 Jun 2026 16:36:51 +0000 Subject: [PATCH 115/115] Undo added test and fix related to that --- .../Laurel/LiftImperativeExpressions.lean | 12 ++------- .../Fundamentals/T2_ImpureExpressions.lean | 27 ------------------- 2 files changed, 2 insertions(+), 37 deletions(-) diff --git a/Strata/Languages/Laurel/LiftImperativeExpressions.lean b/Strata/Languages/Laurel/LiftImperativeExpressions.lean index 25eed8d0b4..971bafbf91 100644 --- a/Strata/Languages/Laurel/LiftImperativeExpressions.lean +++ b/Strata/Languages/Laurel/LiftImperativeExpressions.lean @@ -198,18 +198,10 @@ def containsAssignmentOrImperativeCall (imperativeCallees : List String) (expr : mutual def asLifted { t: Type } (runner: LiftM t) : LiftM t := do - -- Save only the bookkeeping that `runner` is meant to run in a fresh - -- sub-scope (`prependedStmts` and `subst`). We must NOT restore the whole - -- state: the monotonic counters (`condCounter`, `varCounters`) advanced by - -- `runner` reflect fresh names (e.g. `$cndtn_N`) that escape into the output - -- via the returned/prepended statements. Rolling those counters back would - -- let a later `freshTempVar`/`freshTempFor` reuse the same name, producing a - -- duplicate definition in the same scope. - let savedPrepends := (← get).prependedStmts - let savedSubst := (← get).subst + let savedState ← get modify fun s => { s with prependedStmts := [], subst := []} let result ← runner - modify fun s => { s with prependedStmts := savedPrepends, subst := savedSubst } + modify fun _ => savedState return result /-- diff --git a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean index 089af0d3b4..23885ec665 100644 --- a/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean +++ b/StrataTest/Languages/Laurel/Examples/Fundamentals/T2_ImpureExpressions.lean @@ -192,31 +192,4 @@ procedure liftWithMultipleOutputs() opaque { var x: int := { assign var y: int, var z: int := hasMultipleOutputs() ; y + z } }; -// Regression test for a fresh-name collision in LiftImperativeExpressionsPass. -// -// `nameCollisionHelper` has no contract, so ContractPass leaves its calls in -// place; the lift pass is what hoists them out of expression position via its -// `asLifted` helper. In the first statement the call sits in expression position -// (the `+ 1` keeps it from being a direct assignment RHS) and its argument is a -// conditional containing another such call. Lifting that argument allocates a -// fresh `$cndtn_` variable *inside* `asLifted`, and that declaration escapes into -// the surrounding scope. Previously `asLifted` saved and restored the entire lift -// state, rolling the fresh-name counter back even though the name had already -// escaped. The conditional expression in the second statement then reused the -// rolled-back number, producing a duplicate `$cndtn_` definition and a -// "Duplicate definition" resolution error after the pass. -procedure nameCollisionHelper(x: int) returns (r: int) - opaque -{ - r := x + 1; - r -}; - -procedure liftedCallArgDoesNotReuseCondName(b: bool) - opaque -{ - var y: int := nameCollisionHelper(if b then { nameCollisionHelper(0) } else { 1 }) + 1; - var z: int := (if b then { nameCollisionHelper(5) } else { 6 }) + 1 -}; - #end