Skip to content

Commit 82f640b

Browse files
committed
fix: remove superfluous Monad constraints from some spec lemmas (#10564)
1 parent 4338a8b commit 82f640b

File tree

4 files changed

+88
-36
lines changed

4 files changed

+88
-36
lines changed

src/Lean/Elab/Tactic/Do/VCGen.lean

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ partial def genVCs (goal : MVarId) (ctx : Context) (fuel : Fuel) : MetaM Result
5757
mv.setTag (Name.mkSimple ("inv" ++ toString (idx + 1)))
5858
for h : idx in [:state.vcs.size] do
5959
let mv := state.vcs[idx]
60-
mv.setTag (Name.mkSimple ("vc" ++ toString (idx + 1)) ++ (← mv.getTag))
60+
mv.setTag (Name.mkSimple ("vc" ++ toString (idx + 1)) ++ (← mv.getTag).eraseMacroScopes)
6161
return { invariants := state.invariants, vcs := state.vcs }
6262
where
6363
onFail (goal : MGoal) (name : Name) : VCGenM Expr := do
@@ -85,9 +85,11 @@ where
8585
let ty ← mvar.getType
8686
if ← isProp ty then
8787
-- Might contain more `P ⊢ₛ wp⟦prog⟧ Q` apps. Try and prove it!
88-
mvar.assign (← tryGoal ty (← mvar.getTag))
88+
let prf ← tryGoal ty (← mvar.getTag)
89+
if ← mvar.isAssigned then
90+
throwError "Tried to assign already assigned metavariable `{← mvar.getTag}` while `tryGoal`. MVar: {mvar}\nAssignment: {mkMVar mvar}\nNew assignment: {prf}"
91+
mvar.assign prf
8992
return
90-
9193
if ty.isAppOf ``PostCond || ty.isAppOf ``Invariant || ty.isAppOf ``SPred then
9294
-- Here we make `mvar` a synthetic opaque goal upon discharge failure.
9395
-- This is the right call for (previously natural) holes such as loop invariants, which
@@ -124,7 +126,7 @@ where
124126
let e ← instantiateMVarsIfMVarApp e
125127
let e := e.headBeta
126128
let goal := goal.withNewProg e -- to persist the instantiation of `e` and `trans`
127-
trace[Elab.Tactic.Do.vcgen] "Program: {e}"
129+
withTraceNode `Elab.Tactic.Do.vcgen (msg := fun _ => return m!"Program: {e}") do
128130

129131
-- let-expressions
130132
if let .letE x ty val body _nonDep := e.getAppFn' then
@@ -179,6 +181,7 @@ where
179181
let res ← Simp.mkCongrArg context res
180182
return ← res.mkEqMPR prf
181183
assignMVars specHoles.toList
184+
trace[Elab.Tactic.Do.vcgen] "Unassigned specHoles: {(← specHoles.filterM (not <$> ·.isAssigned)).map fun m => (m.name, mkMVar m)}"
182185
return prf
183186
return ← onFail goal name
184187

@@ -373,36 +376,36 @@ def elabInvariants (stx : Syntax) (invariants : Array MVarId) (suggestInvariant
373376

374377
let mut dotOrCase := LBool.undef -- .true => dot
375378
for h : n in 0...alts.size do
376-
let alt := alts[n]
377-
match alt with
378-
| `(invariantDotAlt| · $rhs) =>
379-
if dotOrCase matches .false then
380-
logErrorAt alt m!"Alternation between labelled and bulleted invariants is not supported."
381-
break
382-
dotOrCase := .true
383-
let some mv := invariants[n]? | do
384-
logErrorAt alt m!"More invariants have been defined ({alts.size}) than there were unassigned invariants goals `inv<n>` ({invariants.size})."
385-
continue
386-
withRef rhs do
387-
discard <| evalTacticAt (← `(tactic| exact $rhs)) mv
388-
| `(invariantCaseAlt| | $tag $args* => $rhs) =>
389-
if dotOrCase matches .true then
390-
logErrorAt alt m!"Alternation between labelled and bulleted invariants is not supported."
391-
break
392-
dotOrCase := .false
393-
let n? : Option Nat := do
394-
let `(binderIdent| $tag:ident) := tag | some n -- fall back to ordinal
395-
let .str .anonymous s := tag.getId | none
396-
s.dropPrefix? "inv" >>= Substring.toNat?
397-
let some mv := do invariants[(← n?) - 1]? | do
398-
logErrorAt alt m!"No invariant with label {tag} {repr tag}."
399-
continue
400-
if ← mv.isAssigned then
401-
logErrorAt alt m!"Invariant {n?.get!} is already assigned."
402-
continue
403-
withRef rhs do
404-
discard <| evalTacticAt (← `(tactic| rename_i $args*; exact $rhs)) mv
405-
| _ => logErrorAt alt m!"Expected `invariantDotAlt`, got {alt}"
379+
let alt := alts[n]
380+
match alt with
381+
| `(invariantDotAlt| · $rhs) =>
382+
if dotOrCase matches .false then
383+
logErrorAt alt m!"Alternation between labelled and bulleted invariants is not supported."
384+
break
385+
dotOrCase := .true
386+
let some mv := invariants[n]? | do
387+
logErrorAt alt m!"More invariants have been defined ({alts.size}) than there were unassigned invariants goals `inv<n>` ({invariants.size})."
388+
continue
389+
withRef rhs do
390+
discard <| evalTacticAt (← `(tactic| exact $rhs)) mv
391+
| `(invariantCaseAlt| | $tag $args* => $rhs) =>
392+
if dotOrCase matches .true then
393+
logErrorAt alt m!"Alternation between labelled and bulleted invariants is not supported."
394+
break
395+
dotOrCase := .false
396+
let n? : Option Nat := do
397+
let `(binderIdent| $tag:ident) := tag | some n -- fall back to ordinal
398+
let .str .anonymous s := tag.getId | none
399+
s.dropPrefix? "inv" >>= Substring.toNat?
400+
let some mv := do invariants[(← n?) - 1]? | do
401+
logErrorAt alt m!"No invariant with label {tag} {repr tag}."
402+
continue
403+
if ← mv.isAssigned then
404+
logErrorAt alt m!"Invariant {n?.get!} is already assigned."
405+
continue
406+
withRef rhs do
407+
discard <| evalTacticAt (← `(tactic| rename_i $args*; exact $rhs)) mv
408+
| _ => logErrorAt alt m!"Expected `invariantDotAlt`, got {alt}"
406409

407410
if let `(invariantsKW| invariants) := invariantsKW then
408411
if alts.size < invariants.size then
@@ -469,18 +472,25 @@ def elabMVCGen : Tactic := fun stx => withMainContext do
469472
let goal ← getMainGoal
470473
let goal ← if ctx.config.elimLets then elimLets goal else pure goal
471474
let { invariants, vcs } ← VCGen.genVCs goal ctx fuel
475+
trace[Elab.Tactic.Do.vcgen] "after genVCs {← (invariants ++ vcs).mapM fun m => m.getTag}"
472476
let runOnVCs (tac : TSyntax `tactic) (vcs : Array MVarId) : TermElabM (Array MVarId) :=
473477
vcs.flatMapM fun vc => List.toArray <$> Term.withSynthesize do
474478
Tactic.run vc (Tactic.evalTactic tac *> Tactic.pruneSolvedGoals)
475479
let invariants ← Term.TermElabM.run' do
476480
let invariants ← if ctx.config.leave then runOnVCs (← `(tactic| try mleave)) invariants else pure invariants
481+
trace[Elab.Tactic.Do.vcgen] "before elabInvariants {← (invariants ++ vcs).mapM fun m => m.getTag}"
477482
elabInvariants stx[3] invariants (suggestInvariant vcs)
483+
let invariants ← invariants.filterM (not <$> ·.isAssigned)
484+
trace[Elab.Tactic.Do.vcgen] "before trying trivial VCs {← (invariants ++ vcs).mapM fun m => m.getTag}"
478485
let vcs ← Term.TermElabM.run' do
479486
let vcs ← if ctx.config.trivial then runOnVCs (← `(tactic| try mvcgen_trivial)) vcs else pure vcs
480487
let vcs ← if ctx.config.leave then runOnVCs (← `(tactic| try mleave)) vcs else pure vcs
481488
return vcs
482489
-- Eliminating lets here causes some metavariables in `mkFreshPair_triple` to become nonassignable
483490
-- so we don't do it. Presumably some weird delayed assignment thing is going on.
484491
-- let vcs ← if ctx.config.elimLets then liftMetaM <| vcs.mapM elimLets else pure vcs
492+
trace[Elab.Tactic.Do.vcgen] "before elabVCs {← (invariants ++ vcs).mapM fun m => m.getTag}"
485493
let vcs ← elabVCs stx[4] vcs
494+
trace[Elab.Tactic.Do.vcgen] "before replacing main goal {← (invariants ++ vcs).mapM fun m => m.getTag}"
486495
replaceMainGoal (invariants ++ vcs).toList
496+
-- trace[Elab.Tactic.Do.vcgen] "replaced main goal, new: {← getGoals}"

src/Lean/MetavarContext.lean

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,16 @@ run_meta do
596596
let e' ← instantiateMVars e
597597
-- e' is `Nat.succ 42`, `?m.773` is assigned to `42`
598598
```
599+
600+
There are a few gotchas concerning delayed assignments.
601+
602+
* Regular assignments take precedence over delayed ones. This happens when an error occurs, in which
603+
case Lean assigns `sorry` to all unassigned metavariables, delayed assigned or not.
604+
* Unless a delayed assigned metavariable `?m := fun fvars => ?pending` is fully applied to its free
605+
variables `fvars`, it will not be instantiated to `?pending`.
606+
* A delayed assigned metavariable `?m := fun fvars => ?pending` that occurs fully applied `?m fvars`
607+
will not be instantiated to `?pending` unless (1) `?pending` is assigned, and (2) the
608+
assignment to `?pending` is ground (i.e., does not contain unassigned metavariables).
599609
-/
600610
def instantiateMVars [Monad m] [MonadMCtx m] (e : Expr) : m Expr := do
601611
if !e.hasMVar then

src/Std/Do/Triple/SpecLemmas.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,15 +399,15 @@ theorem Spec.tryCatch_MonadExcept [MonadExceptOf ε m] [WP m ps] (Q : PostCond
399399
Triple (tryCatch x h : m α) (spred(wp⟦MonadExceptOf.tryCatch x h : m α⟧ Q)) spred(Q) := SPred.entails.rfl
400400

401401
@[spec]
402-
theorem Spec.throw_ReaderT [WP m sh] [Monad m] [MonadExceptOf ε m] :
402+
theorem Spec.throw_ReaderT [WP m sh] [MonadExceptOf ε m] :
403403
Triple (MonadExceptOf.throw e : ReaderT ρ m α) (spred(wp⟦MonadLift.monadLift (MonadExceptOf.throw (ε:=ε) e : m α) : ReaderT ρ m α⟧ Q)) spred(Q) := SPred.entails.rfl
404404

405405
@[spec]
406406
theorem Spec.throw_StateT [WP m ps] [Monad m] [MonadExceptOf ε m] (Q : PostCond α (.arg σ ps)) :
407407
Triple (MonadExceptOf.throw e : StateT σ m α) (spred(wp⟦MonadLift.monadLift (MonadExceptOf.throw (ε:=ε) e : m α) : StateT σ m α⟧ Q)) spred(Q) := SPred.entails.rfl
408408

409409
@[spec]
410-
theorem Spec.throw_ExceptT_lift [WP m ps] [Monad m] [MonadExceptOf ε m] (Q : PostCond α (.except ε' ps)) :
410+
theorem Spec.throw_ExceptT_lift [WP m ps] [MonadExceptOf ε m] (Q : PostCond α (.except ε' ps)) :
411411
Triple (ps:=.except ε' ps)
412412
(MonadExceptOf.throw e : ExceptT ε' m α)
413413
(wp⟦MonadExceptOf.throw (ε:=ε) e : m (Except ε' α)⟧ (fun | .ok a => Q.1 a | .error e => Q.2.1 e, Q.2.2))

tests/lean/run/10564.lean

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import Std.Tactic.Do
2+
open Std.Do
3+
4+
set_option mvcgen.warning false
5+
6+
structure Supply where
7+
counter : Nat
8+
limit : Nat
9+
property : counter ≤ limit
10+
11+
def mkFreshN2 (n : Nat) : ExceptT Char (EStateM String Supply) (List Nat) := do
12+
let mut acc := #[]
13+
for _ in [:n] do
14+
let supply ← get
15+
if h : supply.counter = supply.limit then
16+
throwThe String s!"Supply exhausted: {supply.counter} = {supply.limit}"
17+
else
18+
let n := supply.counter
19+
have := supply.property
20+
set {supply with counter := n + 1, property := by omega}
21+
acc := acc.push n
22+
pure acc.toList
23+
24+
theorem mkFreshN2_spec2 (n : Nat) :
25+
⦃⌜True⌝⦄
26+
mkFreshN2 n
27+
⦃post⟨fun r => ⌜r.Nodup⌝, fun _ => ⌜False⌝, fun _msg state => ⌜state.counter = state.limit⌝⟩⦄ := by
28+
mvcgen [mkFreshN2] invariants
29+
· post⟨fun ⟨xs, acc⟩ state => ⌜(∀ n ∈ acc, n < state.counter) ∧ acc.toList.Nodup⌝,
30+
fun _ => ⌜False⌝,
31+
fun _msg state => ⌜state.counter = state.limit⌝⟩
32+
with grind

0 commit comments

Comments
 (0)