lean4-htt/src/Lean/Elab/Tactic/Basic.lean
Leonardo de Moura 5ffbada3df feat: add Lean.MonadEnv, Lean.MonadError, and Lean.MonadOptions
This is the first set of polymorphic methods. I will add more later,
and keep reducing code duplication.

cc @Kha
2020-08-22 16:00:43 -07:00

452 lines
18 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
Copyright (c) 2020 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Sebastian Ullrich
-/
import Lean.Util.CollectMVars
import Lean.Meta.Tactic.Assumption
import Lean.Meta.Tactic.Intro
import Lean.Meta.Tactic.Clear
import Lean.Meta.Tactic.Revert
import Lean.Meta.Tactic.Subst
import Lean.Elab.Util
import Lean.Elab.Term
namespace Lean
namespace Elab
def goalsToMessageData (goals : List MVarId) : MessageData :=
MessageData.joinSep (goals.map $ MessageData.ofGoal) (Format.line ++ Format.line)
def Term.reportUnsolvedGoals (goals : List MVarId) : TermElabM Unit := do
ref ← getRef;
let tailRef := ref.getTailWithPos.getD ref;
throwErrorAt tailRef $ "unsolved goals" ++ Format.line ++ goalsToMessageData goals
namespace Tactic
structure Context :=
(main : MVarId)
structure State :=
(goals : List MVarId)
instance State.inhabited : Inhabited State := ⟨{ goals := [] }⟩
structure BacktrackableState :=
(env : Environment)
(mctx : MetavarContext)
(goals : List MVarId)
abbrev TacticM := ReaderT Context $ StateRefT State $ TermElabM
abbrev Tactic := Syntax → TacticM Unit
def getMCtx : TacticM MetavarContext := do s ← getThe Meta.State; pure s.mctx
def getLCtx : TacticM LocalContext := do ctx ← readThe Meta.Context; pure ctx.lctx
def getLocalInsts : TacticM LocalInstances := do ctx ← readThe Meta.Context; pure ctx.localInstances
def getMVarDecl (mvarId : MVarId) : TacticM MetavarDecl := do mctx ← getMCtx; pure $ mctx.getDecl mvarId
def setMCtx (mctx : MetavarContext) : TacticM Unit := modifyThe Meta.State fun s => { s with mctx := mctx }
@[inline] def modifyMCtx (f : MetavarContext → MetavarContext) : TacticM Unit := modifyThe Meta.State $ fun s => { s with mctx := f s.mctx }
def saveBacktrackableState : TacticM BacktrackableState := do
env ← getEnv;
mctx ← getMCtx;
s ← get;
pure { env := env, mctx := mctx, goals := s.goals }
def BacktrackableState.restore (b : BacktrackableState) : TacticM Unit := do
setEnv b.env;
setMCtx b.mctx;
modify fun s => { s with goals := b.goals }
@[inline] protected def catch {α} (x : TacticM α) (h : Exception → TacticM α) : TacticM α := do
b ← saveBacktrackableState;
catch x (fun ex => do b.restore; h ex)
@[inline] protected def orelse {α} (x y : TacticM α) : TacticM α := do
catch x (fun _ => y)
instance monadExcept : MonadExcept Exception TacticM :=
{ throw := fun _ => throw,
catch := fun _ x h => Tactic.catch x h }
instance hasOrElse {α} : HasOrelse (TacticM α) := ⟨Tactic.orelse⟩
structure SavedState :=
(core : Core.State)
(meta : Meta.State)
(term : Term.State)
(tactic : State)
instance SavedState.inhabited : Inhabited SavedState := ⟨⟨arbitrary _, arbitrary _, arbitrary _, arbitrary _⟩⟩
def saveAllState : TacticM SavedState := do
core ← getThe Core.State;
meta ← getThe Meta.State;
term ← getThe Term.State;
tactic ← get;
pure { core := core, meta := meta, term := term, tactic := tactic }
def SavedState.restore (s : SavedState) : TacticM Unit := do
set s.core; set s.meta; set s.term; set s.tactic
@[inline] def liftTermElabM {α} (x : TermElabM α) : TacticM α :=
liftM x
@[inline] def liftMetaM {α} (x : MetaM α) : TacticM α :=
liftTermElabM $ Term.liftMetaM x
def instantiateMVars (e : Expr) : TacticM Expr := liftTermElabM $ Term.instantiateMVars e
def isExprMVarAssigned (mvarId : MVarId) : TacticM Bool := liftTermElabM $ Term.isExprMVarAssigned mvarId
def assignExprMVar (mvarId : MVarId) (val : Expr) : TacticM Unit := liftTermElabM $ Term.assignExprMVar mvarId val
def ensureHasType (expectedType? : Option Expr) (e : Expr) : TacticM Expr := liftTermElabM $ Term.ensureHasType expectedType? e
def reportUnsolvedGoals (goals : List MVarId) : TacticM Unit := liftTermElabM $ Term.reportUnsolvedGoals goals
def inferType (e : Expr) : TacticM Expr := liftTermElabM $ Term.inferType e
def whnf (e : Expr) : TacticM Expr := liftTermElabM $ Term.whnf e
def whnfCore (e : Expr) : TacticM Expr := liftTermElabM $ Term.whnfCore e
def unfoldDefinition? (e : Expr) : TacticM (Option Expr) := liftTermElabM $ Term.unfoldDefinition? e
def resolveGlobalName (n : Name) : TacticM (List (Name × List String)) := liftTermElabM $ Term.resolveGlobalName n
@[inline] def withRef {α} (ref : Syntax) (x : TacticM α) : TacticM α := do
adaptTheReader Core.Context (Core.Context.replaceRef ref) x
/-- Collect unassigned metavariables -/
def collectMVars (e : Expr) : TacticM (List MVarId) := do
e ← instantiateMVars e;
let s := Lean.collectMVars {} e;
pure s.result.toList
instance monadLog : MonadLog TacticM :=
{ getFileMap := liftTermElabM getFileMap,
getFileName := liftTermElabM getFileName,
addContext := fun msg => Prod.snd <$> addContext Syntax.missing msg,
logMessage := fun msg => liftTermElabM $ logMessage msg }
def checkRecDepth : TacticM Unit :=
liftTermElabM $ Term.checkRecDepth
@[inline] def withIncRecDepth {α} (x : TacticM α) : TacticM α := do
checkRecDepth;
adaptTheReader Core.Context Core.Context.incCurrRecDepth x
protected def getCurrMacroScope : TacticM MacroScope := do ctx ← readThe Term.Context; pure ctx.currMacroScope
protected def getMainModule : TacticM Name := do env ← getEnv; pure env.mainModule
@[inline] protected def withFreshMacroScope {α} (x : TacticM α) : TacticM α := do
fresh ← modifyGetThe Term.State (fun s => (s.nextMacroScope, { s with nextMacroScope := s.nextMacroScope + 1 }));
adaptTheReader Term.Context (fun ctx => { ctx with currMacroScope := fresh }) x
instance monadQuotation : MonadQuotation TacticM := {
getCurrMacroScope := Tactic.getCurrMacroScope,
getMainModule := Tactic.getMainModule,
withFreshMacroScope := @Tactic.withFreshMacroScope
}
unsafe def mkTacticAttribute : IO (KeyedDeclsAttribute Tactic) :=
mkElabAttribute Tactic `Lean.Elab.Tactic.tacticElabAttribute `builtinTactic `tactic `Lean.Parser.Tactic `Lean.Elab.Tactic.Tactic "tactic"
@[init mkTacticAttribute] constant tacticElabAttribute : KeyedDeclsAttribute Tactic := arbitrary _
def logTrace (cls : Name) (msg : MessageData) : TacticM Unit := liftTermElabM $ Term.logTrace cls msg
@[inline] def trace (cls : Name) (msg : Unit → MessageData) : TacticM Unit := liftTermElabM $ Term.trace cls msg
@[inline] def traceAtCmdPos (cls : Name) (msg : Unit → MessageData) : TacticM Unit := liftTermElabM $ Term.traceAtCmdPos cls msg
def dbgTrace {α} [HasToString α] (a : α) : TacticM Unit :=_root_.dbgTrace (toString a) $ fun _ => pure ()
private def evalTacticUsing (s : SavedState) (stx : Syntax) : List Tactic → TacticM Unit
| [] => do
let refFmt := stx.prettyPrint;
throwErrorAt stx ("unexpected syntax" ++ MessageData.nest 2 (Format.line ++ refFmt))
| (evalFn::evalFns) => catch (evalFn stx)
(fun ex => match ex with
| Exception.error _ _ =>
match evalFns with
| [] => throw ex
| _ => do s.restore; evalTacticUsing evalFns
| Exception.internal id =>
if id == unsupportedSyntaxExceptionId then do
s.restore; evalTacticUsing evalFns
else
throw ex)
/- Elaborate `x` with `stx` on the macro stack -/
@[inline] def withMacroExpansion {α} (beforeStx afterStx : Syntax) (x : TacticM α) : TacticM α :=
adaptTheReader Term.Context (fun ctx => { ctx with macroStack := { before := beforeStx, after := afterStx } :: ctx.macroStack }) x
instance : MonadMacroAdapter TacticM :=
{ getEnv := getEnv,
getCurrMacroScope := getCurrMacroScope,
getNextMacroScope := do s ← getThe Term.State; pure s.nextMacroScope,
setNextMacroScope := fun next => modifyThe Term.State $ fun s => { s with nextMacroScope := next },
getCurrRecDepth := do ctx ← readThe Core.Context; pure ctx.currRecDepth,
getMaxRecDepth := do ctx ← readThe Core.Context; pure ctx.maxRecDepth,
throwError := fun α ref msg => throwErrorAt ref msg,
throwUnsupportedSyntax := fun α => throwUnsupportedSyntax }
@[specialize] private def expandTacticMacroFns (evalTactic : Syntax → TacticM Unit) (stx : Syntax) : List Macro → TacticM Unit
| [] => throwErrorAt stx ("tactic '" ++ toString stx.getKind ++ "' has not been implemented")
| m::ms => do
scp ← getCurrMacroScope;
catch
(do stx' ← adaptMacro m stx; evalTactic stx')
(fun ex => match ms with
| [] => throw ex
| _ => expandTacticMacroFns ms)
@[inline] def expandTacticMacro (evalTactic : Syntax → TacticM Unit) (stx : Syntax) : TacticM Unit := do
env ← getEnv;
let k := stx.getKind;
let table := (macroAttribute.ext.getState env).table;
let macroFns := (table.find? k).getD [];
expandTacticMacroFns evalTactic stx macroFns
partial def evalTactic : Syntax → TacticM Unit
| stx => withRef stx $ withIncRecDepth $ withFreshMacroScope $ match stx with
| Syntax.node k args =>
if k == nullKind then
-- list of tactics separated by `;` => evaluate in order
-- Syntax quotations can return multiple ones
stx.forSepArgsM evalTactic
else do
trace `Elab.step fun _ => stx;
env ← getEnv;
s ← saveAllState;
let table := (tacticElabAttribute.ext.getState env).table;
let k := stx.getKind;
match table.find? k with
| some evalFns => evalTacticUsing s stx evalFns
| none => expandTacticMacro evalTactic stx
| _ => throwError "unexpected command"
/-- Adapt a syntax transformation to a regular tactic evaluator. -/
def adaptExpander (exp : Syntax → TacticM Syntax) : Tactic :=
fun stx => do
stx' ← exp stx;
withMacroExpansion stx stx' $ evalTactic stx'
@[inline] def withLCtx {α} (lctx : LocalContext) (localInsts : LocalInstances) (x : TacticM α) : TacticM α :=
adaptTheReader Meta.Context (fun ctx => { ctx with lctx := lctx, localInstances := localInsts }) x
def saveAndResetSynthInstanceCache : TacticM Meta.SynthInstanceCache :=
liftTermElabM Term.saveAndResetSynthInstanceCache
def restoreSynthInstanceCache (cache : Meta.SynthInstanceCache) : TacticM Unit :=
liftTermElabM $ Term.restoreSynthInstanceCache cache
/-- Reset `synthInstance` cache, execute `x`, and restore cache -/
@[inline] def resettingSynthInstanceCache {α} (x : TacticM α) : TacticM α := do
savedSythInstance ← saveAndResetSynthInstanceCache;
finally x (restoreSynthInstanceCache savedSythInstance)
@[inline] def resettingSynthInstanceCacheWhen {α} (b : Bool) (x : TacticM α) : TacticM α :=
if b then resettingSynthInstanceCache x else x
def withMVarContext {α} (mvarId : MVarId) (x : TacticM α) : TacticM α := do
mvarDecl ← getMVarDecl mvarId;
localInsts ← getLocalInsts;
let needReset := localInsts == mvarDecl.localInstances;
withLCtx mvarDecl.lctx mvarDecl.localInstances $ resettingSynthInstanceCacheWhen needReset x
def getGoals : TacticM (List MVarId) := do s ← get; pure s.goals
def setGoals (gs : List MVarId) : TacticM Unit := modify $ fun s => { s with goals := gs }
def appendGoals (gs : List MVarId) : TacticM Unit := modify $ fun s => { s with goals := s.goals ++ gs }
def pruneSolvedGoals : TacticM Unit := do
gs ← getGoals;
gs ← gs.filterM $ fun g => not <$> isExprMVarAssigned g;
setGoals gs
def getUnsolvedGoals : TacticM (List MVarId) := do pruneSolvedGoals; getGoals
def getMainGoal : TacticM (MVarId × List MVarId) := do (g::gs) ← getUnsolvedGoals | throwError "no goals to be solved"; pure (g, gs)
def ensureHasNoMVars (e : Expr) : TacticM Unit := do
e ← instantiateMVars e;
when e.hasMVar $ throwError ("tactic failed, resulting expression contains metavariables" ++ indentExpr e)
def withMainMVarContext {α} (x : TacticM α) : TacticM α := do
(mvarId, _) ← getMainGoal;
withMVarContext mvarId x
@[inline] def liftMetaMAtMain {α} (x : MVarId → MetaM α) : TacticM α := do
(g, _) ← getMainGoal;
withMVarContext g $ liftMetaM $ x g
@[inline] def liftMetaTacticAux {α} (tactic : MVarId → MetaM (α × List MVarId)) : TacticM α := do
(g, gs) ← getMainGoal;
withMVarContext g $ do
(a, gs') ← liftMetaM $ tactic g;
setGoals (gs' ++ gs);
pure a
@[inline] def liftMetaTactic (tactic : MVarId → MetaM (List MVarId)) : TacticM Unit :=
liftMetaTacticAux (fun mvarId => do gs ← tactic mvarId; pure ((), gs))
def done : TacticM Unit := do
gs ← getUnsolvedGoals;
unless gs.isEmpty $ reportUnsolvedGoals gs
def focusAux {α} (tactic : TacticM α) : TacticM α := do
(g, gs) ← getMainGoal;
setGoals [g];
a ← tactic;
gs' ← getGoals;
setGoals (gs' ++ gs);
pure a
def focus {α} (tactic : TacticM α) : TacticM α :=
focusAux (do a ← tactic; done; pure a)
/--
Use `parentTag` to tag untagged goals at `newGoals`.
If there are multiple new goals, they are named using `<parentTag>.<newSuffix>_<idx>` where `idx > 0`.
If there is only one new goal, then we just use `parentTag` -/
def tagUntaggedGoals (parentTag : Name) (newSuffix : Name) (newGoals : List MVarId) : TacticM Unit := do
mctx ← getMCtx;
match newGoals with
| [g] => modifyMCtx $ fun mctx => if mctx.isAnonymousMVar g then mctx.renameMVar g parentTag else mctx
| _ => modifyMCtx $ fun mctx =>
let (mctx, _) := newGoals.foldl
(fun (acc : MetavarContext × Nat) (g : MVarId) =>
let (mctx, idx) := acc;
if mctx.isAnonymousMVar g then
(mctx.renameMVar g (parentTag ++ newSuffix.appendIndexAfter idx), idx+1)
else
acc)
(mctx, 1);
mctx
@[builtinTactic seq] def evalSeq : Tactic :=
fun stx => (stx.getArg 0).forSepArgsM evalTactic
partial def evalChoiceAux (tactics : Array Syntax) : Nat → TacticM Unit
| i =>
if h : i < tactics.size then
let tactic := tactics.get ⟨i, h⟩;
catchInternalId unsupportedSyntaxExceptionId
(evalTactic tactic)
(fun _ => evalChoiceAux (i+1))
else
throwUnsupportedSyntax
@[builtinTactic choice] def evalChoice : Tactic :=
fun stx => evalChoiceAux stx.getArgs 0
@[builtinTactic skip] def evalSkip : Tactic :=
fun stx => pure ()
@[builtinTactic failIfSuccess] def evalFailIfSuccess : Tactic :=
fun stx =>
let tactic := stx.getArg 1;
whenM
(catch
(do evalTactic tactic; pure true)
(fun _ => pure false))
(throwError ("tactic succeeded"))
@[builtinTactic traceState] def evalTraceState : Tactic :=
fun stx => do
gs ← getUnsolvedGoals;
logInfo (goalsToMessageData gs)
@[builtinTactic «assumption»] def evalAssumption : Tactic :=
fun stx => liftMetaTactic $ fun mvarId => do Meta.assumption mvarId; pure []
@[builtinTactic «intro»] def evalIntro : Tactic :=
fun stx => match_syntax stx with
| `(tactic| intro) => liftMetaTactic $ fun mvarId => do (_, mvarId) ← Meta.intro1 mvarId; pure [mvarId]
| `(tactic| intro $h) => liftMetaTactic $ fun mvarId => do (_, mvarId) ← Meta.intro mvarId h.getId; pure [mvarId]
| _ => throwUnsupportedSyntax
private def getIntrosSize : Expr → Nat
| Expr.forallE _ _ b _ => getIntrosSize b + 1
| Expr.letE _ _ _ b _ => getIntrosSize b + 1
| _ => 0
@[builtinTactic «intros»] def evalIntros : Tactic :=
fun stx => match_syntax stx with
| `(tactic| intros) => liftMetaTactic $ fun mvarId => do
type ← Meta.getMVarType mvarId;
type ← Meta.instantiateMVars type;
let n := getIntrosSize type;
(_, mvarId) ← Meta.introN mvarId n;
pure [mvarId]
| `(tactic| intros $ids*) => liftMetaTactic $ fun mvarId => do
(_, mvarId) ← Meta.introN mvarId ids.size (ids.map Syntax.getId).toList;
pure [mvarId]
| _ => throwUnsupportedSyntax
def getFVarId (id : Syntax) : TacticM FVarId :=
withRef id do
fvar? ← liftTermElabM $ Term.isLocalIdent? id;
match fvar? with
| some fvar => pure fvar.fvarId!
| none => throwError ("unknown variable '" ++ toString id.getId ++ "'")
def getFVarIds (ids : Array Syntax) : TacticM (Array FVarId) :=
ids.mapM getFVarId
@[builtinTactic «revert»] def evalRevert : Tactic :=
fun stx => match_syntax stx with
| `(tactic| revert $hs*) => do
(g, gs) ← getMainGoal;
withMVarContext g $ do
fvarIds ← getFVarIds hs;
(_, g) ← liftMetaM $ Meta.revert g fvarIds;
setGoals (g :: gs)
| _ => throwUnsupportedSyntax
def forEachVar (hs : Array Syntax) (tac : MVarId → FVarId → MetaM MVarId) : TacticM Unit :=
hs.forM $ fun h => do
(g, gs) ← getMainGoal;
withMVarContext g $ do
fvarId ← getFVarId h;
g ← liftMetaM $ tac g fvarId;
setGoals (g :: gs)
@[builtinTactic «clear»] def evalClear : Tactic :=
fun stx => match_syntax stx with
| `(tactic| clear $hs*) => forEachVar hs Meta.clear
| _ => throwUnsupportedSyntax
@[builtinTactic «subst»] def evalSubst : Tactic :=
fun stx => match_syntax stx with
| `(tactic| subst $hs*) => forEachVar hs Meta.subst
| _ => throwUnsupportedSyntax
@[builtinTactic paren] def evalParen : Tactic :=
fun stx => evalTactic (stx.getArg 1)
@[builtinTactic nestedTacticBlock] def evalNestedTacticBlock : Tactic :=
fun stx => focus (evalTactic (stx.getArg 1))
@[builtinTactic nestedTacticBlockCurly] def evalNestedTacticBlockCurly : Tactic :=
evalNestedTacticBlock
@[builtinTactic «case»] def evalCase : Tactic :=
fun stx => match_syntax stx with
| `(tactic| case $tag $tac) => do
let tag := tag.getId;
gs ← getUnsolvedGoals;
some g ← gs.findM? (fun g => do mvarDecl ← getMVarDecl g; pure $ tag.isSuffixOf mvarDecl.userName) | throwError "tag not found";
let gs := gs.erase g;
setGoals [g];
evalTactic tac;
done;
setGoals gs
| _ => throwUnsupportedSyntax
@[builtinTactic «orelse»] def evalOrelse : Tactic :=
fun stx => match_syntax stx with
| `(tactic| $tac1 <|> $tac2) => evalTactic tac1 <|> evalTactic tac2
| _ => throwUnsupportedSyntax
@[init] private def regTraceClasses : IO Unit := do
registerTraceClass `Elab.tactic;
pure ()
@[inline] def TacticM.run {α} (x : TacticM α) (ctx : Context) (s : State) : TermElabM (α × State) :=
(x.run ctx).run s
@[inline] def TacticM.run' {α} (x : TacticM α) (ctx : Context) (s : State) : TermElabM α :=
Prod.fst <$> x.run ctx s
end Tactic
end Elab
end Lean