lean4-htt/src/Lean/Elab/Tactic/Simp.lean
2022-04-26 10:39:02 +02:00

311 lines
12 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
-/
import Lean.Meta.Tactic.Simp
import Lean.Meta.Tactic.Replace
import Lean.Elab.BuiltinNotation
import Lean.Elab.Tactic.Basic
import Lean.Elab.Tactic.ElabTerm
import Lean.Elab.Tactic.Location
import Lean.Elab.Tactic.Config
namespace Lean.Elab.Tactic
open Meta
declare_config_elab elabSimpConfigCore Meta.Simp.Config
declare_config_elab elabSimpConfigCtxCore Meta.Simp.ConfigCtx
declare_config_elab elabDSimpConfigCore Meta.DSimp.Config
inductive SimpKind where
| simp
| simpAll
| dsimp
deriving Inhabited, BEq
/--
Implement a `simp` discharge function using the given tactic syntax code.
Recall that `simp` dischargers are in `SimpM` which does not have access to `Term.State`.
We need access to `Term.State` to store messages and update the info tree.
Thus, we create an `IO.ref` to track these changes at `Term.State` when we execute `tacticCode`.
We must set this reference with the current `Term.State` before we execute `simp` using the
generated `Simp.Discharge`. -/
def tacticToDischarge (tacticCode : Syntax) : TacticM (IO.Ref Term.State × Simp.Discharge) := do
let tacticCode ← `(tactic| try ($tacticCode:tacticSeq))
let ref ← IO.mkRef (← getThe Term.State)
let ctx ← readThe Term.Context
let disch : Simp.Discharge := fun e => do
let mvar ← mkFreshExprSyntheticOpaqueMVar e `simp.discharger
let s ← ref.get
let runTac? : TermElabM (Option Expr) :=
try
/- We must only save messages and info tree changes. Recall that `simp` uses temporary metavariables (`withNewMCtxDepth`).
So, we must not save references to them at `Term.State`. -/
withoutModifyingStateWithInfoAndMessages do
Term.withSynthesize (mayPostpone := false) <| Term.runTactic mvar.mvarId! tacticCode
let result ← instantiateMVars mvar
if result.hasExprMVar then
return none
else
return some result
catch _ =>
return none
let (result?, s) ← liftM (m := MetaM) <| Term.TermElabM.run runTac? ctx s
ref.set s
return result?
return (ref, disch)
inductive Simp.DischargeWrapper where
| default
| custom (ref : IO.Ref Term.State) (discharge : Simp.Discharge)
def Simp.DischargeWrapper.with (w : Simp.DischargeWrapper) (x : Option Simp.Discharge → TacticM α) : TacticM α := do
match w with
| default => x none
| custom ref d =>
ref.set (← getThe Term.State)
try
x d
finally
set (← ref.get)
private def mkDischargeWrapper (optDischargeSyntax : Syntax) : TacticM Simp.DischargeWrapper := do
if optDischargeSyntax.isNone then
return Simp.DischargeWrapper.default
else
let (ref, d) ← tacticToDischarge optDischargeSyntax[0][3]
return Simp.DischargeWrapper.custom ref d
/-
`optConfig` is of the form `("(" "config" ":=" term ")")?`
-/
def elabSimpConfig (optConfig : Syntax) (kind : SimpKind) : TermElabM Meta.Simp.Config := do
match kind with
| .simp => elabSimpConfigCore optConfig
| .simpAll => return (← elabSimpConfigCtxCore optConfig).toConfig
| .dsimp => return { (← elabDSimpConfigCore optConfig) with }
private def addDeclToUnfoldOrTheorem (thms : Meta.SimpTheorems) (e : Expr) (post : Bool) (inv : Bool) (kind : SimpKind) : MetaM Meta.SimpTheorems := do
if e.isConst then
let declName := e.constName!
let info ← getConstInfo declName
if (← isProp info.type) then
thms.addConst declName (post := post) (inv := inv)
else
if inv then
throwError "invalid '←' modifier, '{declName}' is a declaration name to be unfolded"
if kind == .dsimp then
return thms.addDeclToUnfoldCore declName
else
thms.addDeclToUnfold declName
else
thms.add #[] e (post := post) (inv := inv)
private def addSimpTheorem (thms : Meta.SimpTheorems) (stx : Syntax) (post : Bool) (inv : Bool) : TermElabM Meta.SimpTheorems := do
let (levelParams, proof) ← Term.withoutModifyingElabMetaStateWithInfo <| withRef stx <| Term.withoutErrToSorry do
let e ← Term.elabTerm stx none
Term.synthesizeSyntheticMVars (mayPostpone := false) (ignoreStuckTC := true)
let e ← instantiateMVars e
let e := e.eta
if e.hasMVar then
let r ← abstractMVars e
return (r.paramNames, r.expr)
else
return (#[], e)
thms.add levelParams proof (post := post) (inv := inv)
structure ElabSimpArgsResult where
ctx : Simp.Context
starArg : Bool := false
inductive ResolveSimpIdResult where
| none
| expr (e : Expr)
| ext (ext : SimpExtension)
/--
Elaborate extra simp theorems provided to `simp`. `stx` is of the `simpTheorem,*`
If `eraseLocal == true`, then we consider local declarations when resolving names for erased theorems (`- id`),
this option only makes sense for `simp_all`.
-/
private def elabSimpArgs (stx : Syntax) (ctx : Simp.Context) (eraseLocal : Bool) (kind : SimpKind) : TacticM ElabSimpArgsResult := do
if stx.isNone then
return { ctx }
else
/-
syntax simpPre := "↓"
syntax simpPost := "↑"
syntax simpLemma := (simpPre <|> simpPost)? term
syntax simpErase := "-" ident
-/
withMainContext do
let mut thmsArray := ctx.simpTheorems
let mut thms := thmsArray[0]
let mut starArg := false
for arg in stx[1].getSepArgs do
if arg.getKind == ``Lean.Parser.Tactic.simpErase then
if eraseLocal && (← Term.isLocalIdent? arg[1]).isSome then
-- We use `eraseCore` because the simp theorem for the hypothesis was not added yet
thms := thms.eraseCore arg[1].getId
else
let declName ← resolveGlobalConstNoOverloadWithInfo arg[1]
if ctx.config.autoUnfold then
thms := thms.eraseCore declName
else
thms ← thms.erase declName
else if arg.getKind == ``Lean.Parser.Tactic.simpLemma then
let post :=
if arg[0].isNone then
true
else
arg[0][0].getKind == ``Parser.Tactic.simpPost
let inv := !arg[1].isNone
let term := arg[2]
match (← resolveSimpIdTheorem? term) with
| .expr e => thms ← addDeclToUnfoldOrTheorem thms e post inv kind
| .ext ext => thmsArray := thmsArray.push (← ext.getTheorems)
| .none => thms ← addSimpTheorem thms term post inv
else if arg.getKind == ``Lean.Parser.Tactic.simpStar then
starArg := true
else
throwUnsupportedSyntax
return { ctx := { ctx with simpTheorems := thmsArray.set! 0 thms }, starArg }
where
resolveSimpIdTheorem? (simpArgTerm : Syntax) : TacticM ResolveSimpIdResult := do
let resolveExt (n : Name) : TacticM ResolveSimpIdResult := do
if let some ext ← getSimpExtension? n then
return .ext ext
else
return .none
if simpArgTerm.isIdent then
try
if let some e ← Term.resolveId? simpArgTerm (withInfo := true) then
return .expr e
else
resolveExt simpArgTerm.getId.eraseMacroScopes
catch _ =>
resolveExt simpArgTerm.getId.eraseMacroScopes
else if let some e ← Term.elabCDotFunctionAlias? simpArgTerm then
return .expr e
else
return .none
structure MkSimpContextResult where
ctx : Simp.Context
dischargeWrapper : Simp.DischargeWrapper
fvarIdToLemmaId : FVarIdToLemmaId
/--
Create the `Simp.Context` for the `simp`, `dsimp`, and `simp_all` tactics.
If `kind != SimpKind.simp`, the `discharge` option must be `none`
TODO: generate error message if non `rfl` theorems are provided as arguments to `dsimp`.
-/
def mkSimpContext (stx : Syntax) (eraseLocal : Bool) (kind := SimpKind.simp) (ignoreStarArg : Bool := false) : TacticM MkSimpContextResult := do
if !stx[2].isNone then
if kind == SimpKind.simpAll then
throwError "'simp_all' tactic does not support 'discharger' option"
if kind == SimpKind.dsimp then
throwError "'dsimp' tactic does not support 'discharger' option"
let dischargeWrapper ← mkDischargeWrapper stx[2]
let simpOnly := !stx[3].isNone
let simpTheorems ←
if simpOnly then
({} : SimpTheorems).addConst ``eq_self
else
getSimpTheorems
let congrTheorems ← getSimpCongrTheorems
let r ← elabSimpArgs stx[4] (eraseLocal := eraseLocal) (kind := kind) {
config := (← elabSimpConfig stx[1] (kind := kind))
simpTheorems := #[simpTheorems], congrTheorems
}
if !r.starArg || ignoreStarArg then
return { r with fvarIdToLemmaId := {}, dischargeWrapper }
else
let ctx := r.ctx
let mut simpTheorems := ctx.simpTheorems
let hs ← getPropHyps
let mut fvarIdToLemmaId := {}
for h in hs do
let localDecl ← getLocalDecl h
unless simpTheorems.isErased localDecl.userName do
let fvarId := localDecl.fvarId
let proof := localDecl.toExpr
let id ← mkFreshUserName `h
fvarIdToLemmaId := fvarIdToLemmaId.insert fvarId id
simpTheorems ← simpTheorems.addTheorem proof (name? := id)
let ctx := { ctx with simpTheorems }
return { ctx, fvarIdToLemmaId, dischargeWrapper }
/--
`simpLocation ctx discharge? varIdToLemmaId loc`
runs the simplifier at locations specified by `loc`,
using the simp theorems collected in `ctx`
optionally running a discharger specified in `discharge?` on generated subgoals.
(Local hypotheses which have been added to the simp theorems must be recorded in
`fvarIdToLemmaId`.)
Its primary use is as the implementation of the
`simp [...] at ...` and `simp only [...] at ...` syntaxes,
but can also be used by other tactics when a `Syntax` is not available.
For many tactics other than the simplifier,
one should use the `withLocation` tactic combinator
when working with a `location`.
-/
def simpLocation (ctx : Simp.Context) (discharge? : Option Simp.Discharge := none) (fvarIdToLemmaId : FVarIdToLemmaId := {}) (loc : Location) : TacticM Unit := do
match loc with
| Location.targets hyps simplifyTarget =>
withMainContext do
let fvarIds ← getFVarIds hyps
go fvarIds simplifyTarget fvarIdToLemmaId
| Location.wildcard =>
withMainContext do
go (← getNondepPropHyps (← getMainGoal)) (simplifyTarget := true) fvarIdToLemmaId
where
go (fvarIdsToSimp : Array FVarId) (simplifyTarget : Bool) (fvarIdToLemmaId : Lean.Meta.FVarIdToLemmaId) : TacticM Unit := do
let mvarId ← getMainGoal
let result? ← simpGoal mvarId ctx (simplifyTarget := simplifyTarget) (discharge? := discharge?) (fvarIdsToSimp := fvarIdsToSimp) (fvarIdToLemmaId := fvarIdToLemmaId)
match result? with
| none => replaceMainGoal []
| some (_, mvarId) => replaceMainGoal [mvarId]
/-
"simp " (config)? (discharger)? ("only ")? ("[" simpLemma,* "]")? (location)?
-/
@[builtinTactic Lean.Parser.Tactic.simp] def evalSimp : Tactic := fun stx => do
let { ctx, fvarIdToLemmaId, dischargeWrapper } ← withMainContext <| mkSimpContext stx (eraseLocal := false)
dischargeWrapper.with fun discharge? =>
simpLocation ctx discharge? fvarIdToLemmaId (expandOptLocation stx[5])
@[builtinTactic Lean.Parser.Tactic.simpAll] def evalSimpAll : Tactic := fun stx => do
let { ctx, .. } ← mkSimpContext stx (eraseLocal := true) (kind := .simpAll) (ignoreStarArg := true)
match (← simpAll (← getMainGoal) ctx) with
| none => replaceMainGoal []
| some mvarId => replaceMainGoal [mvarId]
def dsimpLocation (ctx : Simp.Context) (fvarIdToSimp : FVarIdToLemmaId := {}) (loc : Location) : TacticM Unit := do
match loc with
| Location.targets hyps simplifyTarget =>
withMainContext do
let fvarIds ← getFVarIds hyps
go fvarIds simplifyTarget fvarIdToSimp
| Location.wildcard =>
withMainContext do
go (← getNondepPropHyps (← getMainGoal)) (simplifyTarget := true) fvarIdToSimp
where
go (fvarIdsToSimp : Array FVarId) (simplifyTarget : Bool) (fvarIdToSimp : Lean.Meta.FVarIdToLemmaId) : TacticM Unit := do
let mvarId ← getMainGoal
let result? ← dsimpGoal mvarId ctx (simplifyTarget := simplifyTarget) (fvarIdsToSimp := fvarIdsToSimp)
match result? with
| none => replaceMainGoal []
| some mvarId => replaceMainGoal [mvarId]
@[builtinTactic Lean.Parser.Tactic.dsimp] def evalDSimp : Tactic := fun stx => do
let { ctx, fvarIdToLemmaId, .. } ← withMainContext <| mkSimpContext stx (eraseLocal := false) (kind := .dsimp)
dsimpLocation ctx fvarIdToLemmaId (expandOptLocation stx[5])
end Lean.Elab.Tactic