Featured suggested at https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/Pattern.20matching.20lambda.20body.20in.20conv/near/257193307
158 lines
5.8 KiB
Text
158 lines
5.8 KiB
Text
/-
|
||
Copyright (c) 2021 Microsoft Corporation. All rights reserved.
|
||
Released under Apache 2.0 license as described in the file LICENSE.
|
||
Authors: Leonardo de Moura
|
||
-/
|
||
import Lean.Meta.Reduce
|
||
import Lean.Meta.Tactic.Apply
|
||
import Lean.Meta.Tactic.Replace
|
||
import Lean.Elab.Tactic.Basic
|
||
import Lean.Elab.Tactic.BuiltinTactic
|
||
|
||
namespace Lean.Elab.Tactic.Conv
|
||
open Meta
|
||
|
||
def mkConvGoalFor (lhs : Expr) : MetaM (Expr × Expr) := do
|
||
let lhsType ← inferType lhs
|
||
let rhs ← mkFreshExprMVar lhsType
|
||
let targetNew := mkLHSGoal (← mkEq lhs rhs)
|
||
let newGoal ← mkFreshExprSyntheticOpaqueMVar targetNew
|
||
return (rhs, newGoal)
|
||
|
||
def markAsConvGoal (mvarId : MVarId) : MetaM MVarId := do
|
||
let target ← getMVarType mvarId
|
||
if isLHSGoal? target |>.isSome then
|
||
return mvarId -- it is already tagged as LHS goal
|
||
replaceTargetDefEq mvarId (mkLHSGoal (← getMVarType mvarId))
|
||
|
||
def convert (lhs : Expr) (conv : TacticM Unit) : TacticM (Expr × Expr) := do
|
||
let (rhs, newGoal) ← mkConvGoalFor lhs
|
||
let savedGoals ← getGoals
|
||
try
|
||
setGoals [newGoal.mvarId!]
|
||
conv
|
||
for mvarId in (← getGoals) do
|
||
try
|
||
applyRefl mvarId
|
||
catch _ =>
|
||
pure ()
|
||
pruneSolvedGoals
|
||
unless (← getGoals).isEmpty do
|
||
throwError "convert tactic failed, there are unsolved goals\n{goalsToMessageData (← getGoals)}"
|
||
pure ()
|
||
finally
|
||
setGoals savedGoals
|
||
return (← instantiateMVars rhs, ← instantiateMVars newGoal)
|
||
|
||
def getLhsRhsCore (mvarId : MVarId) : MetaM (Expr × Expr) :=
|
||
withMVarContext mvarId do
|
||
let some (_, lhs, rhs) ← matchEq? (← getMVarType mvarId) | throwError "invalid 'conv' goal"
|
||
return (lhs, rhs)
|
||
|
||
def getLhsRhs : TacticM (Expr × Expr) := do
|
||
getLhsRhsCore (← getMainGoal)
|
||
|
||
def getLhs : TacticM Expr :=
|
||
return (← getLhsRhs).1
|
||
|
||
def getRhs : TacticM Expr :=
|
||
return (← getLhsRhs).2
|
||
|
||
/-- `⊢ lhs = rhs` ~~> `⊢ lhs' = rhs` using `h : lhs = lhs'`. -/
|
||
def updateLhs (lhs' : Expr) (h : Expr) : TacticM Unit := do
|
||
let rhs ← getRhs
|
||
let newGoal ← mkFreshExprSyntheticOpaqueMVar (mkLHSGoal (← mkEq lhs' rhs))
|
||
assignExprMVar (← getMainGoal) (← mkEqTrans h newGoal)
|
||
replaceMainGoal [newGoal.mvarId!]
|
||
|
||
/-- Replace `lhs` with the definitionally equal `lhs'`. -/
|
||
def changeLhs (lhs' : Expr) : TacticM Unit := do
|
||
let rhs ← getRhs
|
||
liftMetaTactic1 fun mvarId => do
|
||
replaceTargetDefEq mvarId (mkLHSGoal (← mkEq lhs' rhs))
|
||
|
||
@[builtinTactic Lean.Parser.Tactic.Conv.whnf] def evalWhnf : Tactic := fun stx =>
|
||
withMainContext do
|
||
changeLhs (← whnf (← getLhs))
|
||
|
||
@[builtinTactic Lean.Parser.Tactic.Conv.reduce] def evalReduce : Tactic := fun stx =>
|
||
withMainContext do
|
||
changeLhs (← reduce (← getLhs))
|
||
|
||
@[builtinTactic Lean.Parser.Tactic.Conv.convSeq1Indented] def evalConvSeq1Indented : Tactic := fun stx => do
|
||
evalTacticSeq1Indented stx
|
||
|
||
@[builtinTactic Lean.Parser.Tactic.Conv.convSeqBracketed] def evalConvSeqBracketed : Tactic := fun stx => do
|
||
let initInfo ← mkInitialTacticInfo stx[0]
|
||
withRef stx[2] <| closeUsingOrAdmit do
|
||
-- save state before/after entering focus on `{`
|
||
withInfoContext (pure ()) initInfo
|
||
evalManyTacticOptSemi stx[1]
|
||
evalTactic (← `(tactic| all_goals (try rfl)))
|
||
|
||
@[builtinTactic Lean.Parser.Tactic.Conv.nestedConv] def evalNestedConv : Tactic := fun stx => do
|
||
evalConvSeqBracketed stx[0]
|
||
|
||
@[builtinTactic Lean.Parser.Tactic.Conv.convSeq] def evalConvSeq : Tactic := fun stx => do
|
||
evalTactic stx[0]
|
||
|
||
@[builtinTactic Lean.Parser.Tactic.Conv.convConvSeq] def evalConvConvSeq : Tactic := fun stx =>
|
||
withMainContext do
|
||
let (lhsNew, proof) ← convert (← getLhs) (evalTactic stx[2][0])
|
||
updateLhs lhsNew proof
|
||
|
||
@[builtinTactic Lean.Parser.Tactic.Conv.paren] def evalParen : Tactic := fun stx =>
|
||
evalTactic stx[1]
|
||
|
||
/-- Mark goals of the form `⊢ a = ?m ..` with the conv goal annotation -/
|
||
def remarkAsConvGoal : TacticM Unit := do
|
||
let newGoals ← (← getUnsolvedGoals).mapM fun mvarId => withMVarContext mvarId do
|
||
let target ← getMVarType mvarId
|
||
if let some (_, lhs, rhs) ← matchEq? target then
|
||
if rhs.getAppFn.isMVar then
|
||
replaceTargetDefEq mvarId (mkLHSGoal target)
|
||
else
|
||
return mvarId
|
||
else
|
||
return mvarId
|
||
setGoals newGoals
|
||
|
||
@[builtinTactic Lean.Parser.Tactic.Conv.nestedTacticCore] def evalNestedTacticCore : Tactic := fun stx => do
|
||
let seq := stx[2]
|
||
evalTactic seq; remarkAsConvGoal
|
||
|
||
@[builtinTactic Lean.Parser.Tactic.Conv.nestedTactic] def evalNestedTactic : Tactic := fun stx => do
|
||
let seq := stx[2]
|
||
let target ← getMainTarget
|
||
if let some _ := isLHSGoal? target then
|
||
liftMetaTactic1 fun mvarId =>
|
||
replaceTargetDefEq mvarId target.mdataExpr!
|
||
focus do evalTactic seq; remarkAsConvGoal
|
||
|
||
private def convTarget (conv : Syntax) : TacticM Unit := withMainContext do
|
||
let target ← getMainTarget
|
||
let (targetNew, proof) ← convert target (evalTactic conv)
|
||
liftMetaTactic1 fun mvarId => replaceTargetEq mvarId targetNew proof
|
||
evalTactic (← `(tactic| try rfl))
|
||
|
||
private def convLocalDecl (conv : Syntax) (hUserName : Name) : TacticM Unit := withMainContext do
|
||
let localDecl ← getLocalDeclFromUserName hUserName
|
||
let (typeNew, proof) ← convert localDecl.type (evalTactic conv)
|
||
liftMetaTactic1 fun mvarId =>
|
||
return some (← replaceLocalDecl mvarId localDecl.fvarId typeNew proof).mvarId
|
||
|
||
@[builtinTactic Lean.Parser.Tactic.Conv.conv] def evalConv : Tactic := fun stx => do
|
||
match stx with
|
||
| `(tactic| conv $[at $loc?]? in $p => $code) =>
|
||
evalTactic (← `(tactic| conv $[at $loc?]? => pattern $p; ($code:convSeq)))
|
||
| `(tactic| conv $[at $loc?]? => $code) =>
|
||
if let some loc := loc? then
|
||
convLocalDecl code loc.getId
|
||
else
|
||
convTarget code
|
||
| _ => throwUnsupportedSyntax
|
||
|
||
@[builtinTactic Lean.Parser.Tactic.Conv.first] partial def evalFirst : Tactic :=
|
||
Tactic.evalFirst
|
||
|
||
end Lean.Elab.Tactic.Conv
|