lean4-htt/src/Lean/Meta/Tactic/Replace.lean
2021-09-09 13:07:33 -07:00

130 lines
5.8 KiB
Text

/-
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.Util.ForEachExpr
import Lean.Meta.AppBuilder
import Lean.Meta.MatchUtil
import Lean.Meta.Tactic.Util
import Lean.Meta.Tactic.Revert
import Lean.Meta.Tactic.Intro
import Lean.Meta.Tactic.Clear
import Lean.Meta.Tactic.Assert
namespace Lean.Meta
/--
Convert the given goal `Ctx |- target` into `Ctx |- targetNew` using an equality proof `eqProof : target = targetNew`.
It assumes `eqProof` has type `target = targetNew` -/
def replaceTargetEq (mvarId : MVarId) (targetNew : Expr) (eqProof : Expr) : MetaM MVarId :=
withMVarContext mvarId do
checkNotAssigned mvarId `replaceTarget
let tag ← getMVarTag mvarId
let mvarNew ← mkFreshExprSyntheticOpaqueMVar targetNew tag
let target ← getMVarType mvarId
let u ← getLevel target
let eq ← mkEq target targetNew
let newProof ← mkExpectedTypeHint eqProof eq
let val := mkAppN (Lean.mkConst `Eq.mpr [u]) #[target, targetNew, eqProof, mvarNew]
assignExprMVar mvarId val
return mvarNew.mvarId!
/--
Convert the given goal `Ctx | target` into `Ctx |- targetNew`. It assumes the goals are definitionally equal.
We use the proof term
```
@id target mvarNew
```
to create a checkpoint. -/
def replaceTargetDefEq (mvarId : MVarId) (targetNew : Expr) : MetaM MVarId :=
withMVarContext mvarId do
checkNotAssigned mvarId `change
let target ← getMVarType mvarId
if target == targetNew then
return mvarId
else
let tag ← getMVarTag mvarId
let mvarNew ← mkFreshExprSyntheticOpaqueMVar targetNew tag
let newVal ← mkExpectedTypeHint mvarNew target
assignExprMVar mvarId mvarNew
return mvarNew.mvarId!
/--
Replace type of the local declaration with id `fvarId` with one with the same user-facing name, but with type `typeNew`.
This method assumes `eqProof` is a proof that type of `fvarId` is equal to `typeNew`.
This tactic actually adds a new declaration and (try to) clear the old one.
If the old one cannot be cleared, then at least its user-facing name becomes inaccessible.
Remark: the new declaration is added immediately after `fvarId`.
`typeNew` must be well-formed at `fvarId`, but `eqProof` may contain variables declared after `fvarId`. -/
def replaceLocalDecl (mvarId : MVarId) (fvarId : FVarId) (typeNew : Expr) (eqProof : Expr) : MetaM AssertAfterResult :=
withMVarContext mvarId do
let localDecl ← getLocalDecl fvarId
let typeNewPr ← mkEqMP eqProof (mkFVar fvarId)
-- `typeNew` may contain variables that occur after `fvarId`.
-- Thus, we use the auxiliary function `findMaxFVar` to ensure `typeNew` is well-formed at the position we are inserting it.
let (_, localDecl') ← findMaxFVar typeNew |>.run localDecl
let result ← assertAfter mvarId localDecl'.fvarId localDecl.userName typeNew typeNewPr
(do let mvarIdNew ← clear result.mvarId fvarId
pure { result with mvarId := mvarIdNew })
<|> pure result
where
findMaxFVar (e : Expr) : StateRefT LocalDecl MetaM Unit :=
e.forEach' fun e => do
if e.isFVar then
let localDecl' ← getLocalDecl e.fvarId!
modify fun localDecl => if localDecl'.index > localDecl.index then localDecl' else localDecl
return false
else
return e.hasFVar
def replaceLocalDeclDefEq (mvarId : MVarId) (fvarId : FVarId) (typeNew : Expr) : MetaM MVarId := do
withMVarContext mvarId do
let mvarDecl ← getMVarDecl mvarId
if typeNew == mvarDecl.type then
return mvarId
else
let lctxNew := (← getLCtx).modifyLocalDecl fvarId (·.setType typeNew)
let mvarNew ← mkFreshExprMVarAt lctxNew (← getLocalInstances) mvarDecl.type mvarDecl.kind mvarDecl.userName
assignExprMVar mvarId mvarNew
return mvarNew.mvarId!
def change (mvarId : MVarId) (targetNew : Expr) (checkDefEq := true) : MetaM MVarId := withMVarContext mvarId do
let target ← getMVarType mvarId
if checkDefEq then
unless (← isDefEq target targetNew) do
throwTacticEx `change mvarId m!"given type{indentExpr targetNew}\nis not definitionally equal to{indentExpr target}"
replaceTargetDefEq mvarId targetNew
def changeLocalDecl (mvarId : MVarId) (fvarId : FVarId) (typeNew : Expr) (checkDefEq := true) : MetaM MVarId := do
checkNotAssigned mvarId `changeLocalDecl
let (xs, mvarId) ← revert mvarId #[fvarId] true
withMVarContext mvarId do
let numReverted := xs.size
let target ← getMVarType mvarId
let check (typeOld : Expr) : MetaM Unit := do
if checkDefEq then
unless (← isDefEq typeNew typeOld) do
throwTacticEx `changeHypothesis mvarId m!"given type{indentExpr typeNew}\nis not definitionally equal to{indentExpr typeOld}"
let finalize (targetNew : Expr) : MetaM MVarId := do
let mvarId ← replaceTargetDefEq mvarId targetNew
let (_, mvarId) ← introNP mvarId numReverted
pure mvarId
match target with
| Expr.forallE n d b c => do check d; finalize (mkForall n c.binderInfo typeNew b)
| Expr.letE n t v b _ => do check t; finalize (mkLet n typeNew v b)
| _ => throwTacticEx `changeHypothesis mvarId "unexpected auxiliary target"
def modifyTarget (mvarId : MVarId) (f : Expr → MetaM Expr) : MetaM MVarId := do
withMVarContext mvarId do
checkNotAssigned mvarId `modifyTarget
change mvarId (← f (← getMVarType mvarId)) (checkDefEq := false)
def modifyTargetEqLHS (mvarId : MVarId) (f : Expr → MetaM Expr) : MetaM MVarId := do
modifyTarget mvarId fun target => do
if let some (_, lhs, rhs) ← matchEq? target then
mkEq (← f lhs) rhs
else
throwTacticEx `modifyTargetEqLHS mvarId m!"equality expected{indentExpr target}"
end Lean.Meta