Skip to content

Commit c9881ea

Browse files
authored
Better errors/warning messages for Loom (#23)
* add dummy measure to model omitted decreasing clause * better errors/warning for decreasing clause * require at least one invariant * fix chashmere * add test suite for new error/warning messages * Refactor invariant handling in Loom: rename 'invariants' to 'invariantSeq' for consistency and clarity across multiple files. * Make invariantGadget infer MAlg instance to determine the type of assertion language * Add option to off warnings and errors relted to decreasing clause * add warning for empty invariant clauses * Add options to control warnings and errors for Loom program annotations * fix velvet case studies
1 parent 3d12676 commit c9881ea

File tree

9 files changed

+236
-59
lines changed

9 files changed

+236
-59
lines changed

CaseStudies/Cashmere/Syntax_Cashmere.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ macro_rules
247247
for _ in Lean.Loop.mk do
248248
invariantGadget [ $[$inv:term],* ]
249249
onDoneGadget ($invd_some:term)
250+
decreasingGadget none
250251
if $t then
251252
$seq:doSeq
252253
else break)

CaseStudies/Macro.lean

Lines changed: 105 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,55 @@ open Lean Elab Command
1010

1111
macro "assert" t:term : term => `(assertGadget $t)
1212

13-
macro "decreasing" t:term : term => `(decreasingGadget $t)
13+
/--
14+
Elaboration rule for the `decreasing` annotation. Here we will
15+
- Throw an error if the termination semantics is unspecified.
16+
- Throw an error if the decreasing annotation is missing in total correctness semantics.
17+
- Throw a warning if the decreasing annotation is present in partial correctness semantics.
18+
-/
19+
elab "decreasing" t:term : term => do
20+
let opts <- getOptions
21+
let semantics := opts.getString (defVal := "unspecified") `loom.semantics.termination
22+
if semantics = "unspecified" then
23+
throwError "First, you need to specify the termination semantics using `set_option loom.semantics.termination <partial/total>`"
24+
let decr <- `(decreasingGadget $t)
25+
let decrIsNone := match t with
26+
| `(none) => true
27+
| _ => false
28+
let errorsEnabled := opts.getBool (defVal := true) `loom.linter.errors
29+
let warningsEnabled := opts.getBool (defVal := true) `loom.linter.warnings
30+
if semantics = "total" && decrIsNone && errorsEnabled then
31+
throwError "Decreasing annotations are required in total correctness semantics
32+
To turn off this error, use `set_option loom.linter.errors false`."
33+
if semantics = "partial" && !decrIsNone && warningsEnabled then
34+
logWarningAt (<- getRef) "Decreasing annotations are not checked in partial correctness semantics
35+
To turn off this warning, use `set_option loom.linter.warnings false`."
36+
Term.elabTerm decr none
37+
38+
elab "invariants" invs:term : term => do
39+
let opts <- getOptions
40+
let invsIsEmpty := match invs with
41+
| `([]) => true
42+
| _ => false
43+
let warningsEnabled := opts.getBool (defVal := true) `loom.linter.warnings
44+
if invsIsEmpty && warningsEnabled then
45+
logWarningAt (<- getRef) "Invariant annotations are not specified, so the loop invariant is assumed to be just `True`.
46+
To turn off this warning, use `set_option loom.linter.warnings false`."
47+
Term.elabTerm (<- `(invariantGadget $invs:term)) none
48+
1449

1550
declare_syntax_cat doneWith
1651
declare_syntax_cat decreasingTerm
1752
declare_syntax_cat invariantClause
18-
declare_syntax_cat invariants
53+
declare_syntax_cat invariantClauses
1954
syntax "invariant" termBeforeDo linebreak : invariantClause
2055
syntax "done_with" termBeforeDo : doneWith
2156
syntax "decreasing" termBeforeDo : decreasingTerm
22-
syntax (invariantClause linebreak)* : invariants
57+
syntax (invariantClause linebreak)* : invariantClauses
2358

2459
syntax "let" term ":|" term : doElem
2560
syntax "while" term
26-
(invariantClause)+
61+
(invariantClause)*
2762
(doneWith)?
2863
(decreasingTerm)?
2964
"do" doSeq : doElem
@@ -39,32 +74,76 @@ syntax "for" ident "in" termBeforeInvariant
3974
macro_rules
4075
| `(doElem| let $x:term :| $t) => `(doElem| let $x:term <- pickSuchThat _ (fun $x => $t))
4176

77+
/-
78+
### While loop elaboration rules
79+
80+
The general syntax for a while loop is:
81+
```lean
82+
while <condition>
83+
[invariant <invariant1>]
84+
[invariant <invariant2>]
85+
...
86+
[done_with <termination_condition>]?
87+
[decreasing <measure>]?
88+
do <body>
89+
```
90+
Unlike ordinary Lean's `while` loop, `Velvet` loops require at least one
91+
invariant annotation. Invariant annotations are followed by an optional `done_with` statement.
92+
In a nutshell, `done_with` is a condition that is true upon loop termination. Such condition is
93+
usefull to specify when the loop contains `break` or `continue` statements. Which means that loop
94+
might terminate without reaching the stated where the loop condition is false.
95+
If unspecified, it is assumed to be the negation of the loop condition.
96+
97+
The optional `decreasing` annotation is used to specify a measure function that is used to prove termination.
98+
This measure elaborates to a term of type `β → Option ℕ` where `β` is a record of the loop variables. We need
99+
`Option ℕ` to incorporate the cases when the measure is not specified, which should be the case for partial
100+
correctness semantics.
101+
If `decreasing` is unspecified, measure is assumed to be `none`.
102+
-/
42103
macro_rules
104+
| `(doElem| while $t do $seq:doSeq) => do
105+
let decr <- withRef (<- getRef) `(decreasing none)
106+
let invs <- withRef (<- getRef) `(invariants [])
107+
`(doElem|
108+
for _ in Lean.Loop.mk do
109+
$invs:term
110+
onDoneGadget (with_name_prefix `done ¬$t:term)
111+
$decr:term
112+
if $t then
113+
$seq:doSeq
114+
else break)
43115
| `(doElem| while $t
44116
$[invariant $inv:term
45117
]*
46118
$[done_with $inv_done]?
47119
$[decreasing $measure]?
48120
do $seq:doSeq) => do
121+
let invs <- `(invariants [ $[(with_name_prefix `invariant $inv:term)],* ])
49122
let invd_some ← match inv_done with
50123
| some invd_some => withRef invd_some ``($invd_some)
51124
| none => ``(¬$t:term)
52125
match measure with
53-
| some measure_some => `(doElem|
54-
for _ in Lean.Loop.mk do
55-
invariantGadget [ $[(with_name_prefix `invariant $inv:term)],* ]
56-
onDoneGadget (with_name_prefix `done $invd_some:term)
57-
decreasingGadget (type_with_name_prefix `decreasing $measure_some:term)
58-
if $t then
59-
$seq:doSeq
60-
else break)
61-
| none => `(doElem|
62-
for _ in Lean.Loop.mk do
63-
invariantGadget [ $[(with_name_prefix `invariant $inv:term)],* ]
64-
onDoneGadget (with_name_prefix `done $invd_some:term)
65-
if $t then
66-
$seq:doSeq
67-
else break)
126+
| some measure_some =>
127+
let decr <- withRef measure_some `(decreasing type_with_name_prefix `decreasing $measure_some)
128+
`(doElem|
129+
for _ in Lean.Loop.mk do
130+
$invs:term
131+
onDoneGadget (with_name_prefix `done $invd_some:term)
132+
$decr:term
133+
if $t then
134+
$seq:doSeq
135+
else break)
136+
| none => do
137+
let decr <- withRef (<- getRef) `(decreasing none)
138+
let invs <- `(invariants [ $[(with_name_prefix `invariant $inv:term)],* ])
139+
`(doElem|
140+
for _ in Lean.Loop.mk do
141+
$invs:term
142+
onDoneGadget (with_name_prefix `done $invd_some:term)
143+
$decr:term
144+
if $t then
145+
$seq:doSeq
146+
else break)
68147
| `(doElem| while_some $x:ident :| $t do $seq:doSeq) =>
69148
match seq with
70149
| `(doSeq| $[$seq:doElem]*)
@@ -80,34 +159,36 @@ macro_rules
80159
]*
81160
$[done_with $inv_done]? do
82161
$seq:doSeq) => do
162+
let invs <- `(invariants [ $[(with_name_prefix `invariant $inv:term)],* ])
83163
let invd_some ← match inv_done with
84164
| some invd_some => withRef invd_some ``($invd_some)
85165
| none => ``(¬$t:term)
86166
match seq with
87167
| `(doSeq| $[$seq:doElem]*)
88168
| `(doSeq| $[$seq:doElem;]*)
89169
| `(doSeq| { $[$seq:doElem]* }) =>
170+
let decr <- withRef (<- getRef) `(decreasing none)
90171
`(doElem|
91172
for _ in Lean.Loop.mk do
92-
invariantGadget [ $[(with_name_prefix `invariant $inv:term)],* ]
173+
$invs:term
93174
onDoneGadget (with_name_prefix `done $invd_some:term)
175+
$decr:term
94176
if ∃ $x:ident, $t then
95177
let $x :| $t
96178
$[$seq:doElem]*
97179
else break)
98180
| _ => Lean.Macro.throwError "while_some expects a sequence of do-elements"
99181
| `(doElem| for $x:ident in $t
100-
invariant $inv':term
101182
$[invariant $inv:term
102183
]*
103-
do $seq:doSeq) =>
184+
do $seq:doSeq) => do
185+
let invs <- `(invariants [ $[(with_name_prefix `invariant $inv:term)],* ])
104186
match seq with
105187
| `(doSeq| $[$seq:doElem]*)
106188
| `(doSeq| $[$seq:doElem;]*)
107189
| `(doSeq| { $[$seq:doElem]* }) =>
108-
-- let inv := invs.push inv
109190
`(doElem|
110191
for $x:ident in $t do
111-
invariantGadget [ $inv':term, $[$inv:term],* ]
192+
$invs:term
112193
$[$seq:doElem]*)
113194
| _ => Lean.Macro.throwError "for expects a sequence of do-elements"

CaseStudies/Tactic.lean

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ elab_rules : tactic
7373
wpgen
7474
try simp only [loomWpSimp]
7575
try unfold spec
76-
try simp only [invariants]
76+
try simp only [invariantSeq]
7777
try simp only [WithName.mk']
7878
try simp only [WithName.erase]
7979
try simp only [typeWithName.erase]
@@ -151,17 +151,17 @@ elab "loom_solve?" : tactic => withMainContext do
151151
try simp only [$(mkIdent `loomAbstractionSimp):ident] at *
152152
wpgen
153153
try simp only [$(mkIdent `loomWpSimp):ident]
154-
try simp only [$(mkIdent `WithName):ident]
155-
try simp only [$(mkIdent `typeWithName):ident]
154+
try simp only [$(mkIdent ``WithName):ident]
155+
try simp only [$(mkIdent ``typeWithName):ident]
156156
try unfold spec
157-
try simp only [$(mkIdent `invariants):ident]
158-
try simp only [$(mkIdent `WithName.mk'):ident]
159-
try simp only [$(mkIdent `WithName.erase):ident]
160-
try simp only [$(mkIdent `typeWithName.erase):ident]
161-
try simp only [$(mkIdent `List.foldr):ident]
157+
try simp only [$(mkIdent ``invariantSeq):ident]
158+
try simp only [$(mkIdent ``WithName.mk'):ident]
159+
try simp only [$(mkIdent ``WithName.erase):ident]
160+
try simp only [$(mkIdent ``typeWithName.erase):ident]
161+
try simp only [$(mkIdent ``List.foldr):ident]
162162
try simp only [$(mkIdent `loomLogicSimp):ident]
163163
try simp only [$(mkIdent `simpMAlg):ident]
164-
repeat' (apply $(mkIdent `And.intro) <;> (repeat loom_intro))
164+
repeat' (apply $(mkIdent ``And.intro) <;> (repeat loom_intro))
165165
any_goals auto [$hints,*]
166166
))
167167
Tactic.TryThis.addSuggestion (<-getRef) tac
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import Auto
2+
import Lean
3+
4+
import Mathlib.Algebra.BigOperators.Intervals
5+
import Mathlib.Algebra.Ring.Int.Defs
6+
7+
import Loom.MonadAlgebras.NonDetT.Extract
8+
import Loom.MonadAlgebras.WP.Tactic
9+
import Loom.MonadAlgebras.WP.DoNames'
10+
11+
import CaseStudies.Velvet.Std
12+
13+
section
14+
15+
set_option loom.semantics.termination "partial"
16+
set_option loom.semantics.choice "demonic"
17+
18+
/--
19+
warning: Invariant annotations are not specified, so the loop invariant is assumed to be just `True`.
20+
To turn off this warning, use `set_option loom.linter.warnings false`.
21+
-/
22+
#guard_msgs in
23+
method zero_invariant (mut n : Nat) return (res : Nat) do
24+
while n > 0 do
25+
n := n - 1
26+
return n
27+
28+
end
29+
30+
section
31+
set_option loom.semantics.termination "partial"
32+
set_option loom.semantics.choice "demonic"
33+
34+
/--
35+
warning: Decreasing annotations are not checked in partial correctness semantics
36+
To turn off this warning, use `set_option loom.linter.warnings false`.
37+
-/
38+
#guard_msgs in
39+
method redundant_decreasing (mut n : Nat) return (res : Nat) do
40+
while n > 0
41+
invariant n >= 0
42+
decreasing n
43+
do
44+
n := n - 1
45+
return n
46+
47+
end
48+
49+
section
50+
set_option loom.semantics.termination "total"
51+
set_option loom.semantics.choice "demonic"
52+
53+
/--
54+
error: Decreasing annotations are required in total correctness semantics
55+
To turn off this error, use `set_option loom.linter.errors false`.
56+
-/
57+
#guard_msgs in
58+
method missing_decreasing (mut n : Nat) return (res : Nat) do
59+
while n > 0
60+
invariant n >= 0
61+
done_with n = 0
62+
do
63+
n := n - 1
64+
return n
65+
66+
end
67+
68+
section
69+
set_option loom.semantics.termination "partial"
70+
set_option loom.semantics.choice "demonic"
71+
72+
set_option loom.linter.errors false
73+
set_option loom.linter.warnings false
74+
75+
#guard_msgs in
76+
method no_msg (mut n : Nat) return (res : Nat) do
77+
while n > 0 do
78+
n := n - 1
79+
return n
80+
end

CaseStudies/Velvet/VelvetExamples/Total_Partial_example.lean

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ end
9595
set_option loom.semantics.termination "total"
9696
set_option loom.semantics.choice "demonic"
9797

98+
set_option loom.linter.errors false
99+
set_option loom.linter.warnings false
100+
101+
98102
method insertionSort_result
99103
(mut arr: Array Int) return (u: Unit)
100104
ensures forall i j, 0 ≤ i ∧ i ≤ j ∧ j < arr.size → arr[i]! ≤ arr[j]!
@@ -107,13 +111,9 @@ method insertionSort_result
107111
return
108112
else
109113
let mut n := 1
110-
while n ≠ arr.size
111-
invariant True
112-
do
114+
while n ≠ arr.size do
113115
let mut mind := n
114-
while mind ≠ 0
115-
invariant True
116-
do
116+
while mind ≠ 0 do
117117
if arr[mind]! < arr[mind - 1]! then
118118
swap! arr[mind]! arr[mind - 1]!
119119
mind := mind - 1

0 commit comments

Comments
 (0)