feat: simp discharger

This commit is contained in:
Leonardo de Moura 2021-09-16 10:11:27 -07:00
parent 06dbc56629
commit c2a5e37c60
6 changed files with 133 additions and 17 deletions

View file

@ -30,7 +30,7 @@ syntax (name := change) "change " term : conv
syntax (name := delta) "delta " ident : conv
syntax (name := pattern) "pattern " term : conv
syntax (name := rewrite) "rewrite " (config)? rwRuleSeq : conv
syntax (name := simp) "simp " ("(" &"config" " := " term ")")? (&"only ")? ("[" (simpStar <|> simpErase <|> simpLemma),* "]")? : conv
syntax (name := simp) "simp " (config)? (discharger)? (&"only ")? ("[" (simpStar <|> simpErase <|> simpLemma),* "]")? : conv
syntax (name := simpMatch) "simpMatch " : conv
/-- Execute the given tactic block without converting `conv` goal into a regular goal -/

View file

@ -319,7 +319,7 @@ macro "sorry" : tactic => `(exact sorry)
macro "inferInstance" : tactic => `(exact inferInstance)
/-- Optional configuration option for tactics -/
syntax config := ("(" &"config" " := " term ")")
syntax config := atomic("(" &"config") " := " term ")"
syntax locationWildcard := "*"
syntax locationHyp := (colGt ident)+ ("⊢" <|> "|-")? -- TODO: delete
@ -352,13 +352,15 @@ syntax (name := injection) "injection " term (" with " (colGt (ident <|> "_"))+)
syntax (name := injections) "injections" : tactic
syntax discharger := atomic("(" (&"discharger" <|> &"disch")) " := " tacticSeq ")"
syntax simpPre := "↓"
syntax simpPost := "↑"
syntax simpLemma := (simpPre <|> simpPost)? ("←" <|> "<-")? term
syntax simpErase := "-" ident
syntax simpStar := "*"
syntax (name := simp) "simp " (config)? (&"only ")? ("[" (simpStar <|> simpErase <|> simpLemma),* "]")? (location)? : tactic
syntax (name := simpAll) "simp_all " (config)? (&"only ")? ("[" (simpErase <|> simpLemma),* "]")? : tactic
syntax (name := simp) "simp " (config)? (discharger)? (&"only ")? ("[" (simpStar <|> simpErase <|> simpLemma),* "]")? (location)? : tactic
syntax (name := simpAll) "simp_all " (config)? (discharger)? (&"only ")? ("[" (simpErase <|> simpLemma),* "]")? : tactic
/--
Delta expand the given definition.

View file

@ -17,8 +17,10 @@ def applySimpResult (result : Simp.Result) : TacticM Unit := do
updateLhs result.expr (← result.getProof)
@[builtinTactic Lean.Parser.Tactic.Conv.simp] def evalSimp : Tactic := fun stx => withMainContext do
let { ctx, .. } ← mkSimpContext stx (eraseLocal := false)
applySimpResult (← simp (← getLhs) ctx)
let { ctx, dischargeWrapper, .. } ← mkSimpContext stx (eraseLocal := false)
let lhs ← getLhs
let result ← dischargeWrapper.with fun d? => simp lhs ctx (discharge? := d?)
applySimpResult result
@[builtinTactic Lean.Parser.Tactic.Conv.simpMatch] def evalSimpMatch : Tactic := fun stx => withMainContext do
applySimpResult (← Split.simpMatch (← getLhs))

View file

@ -17,6 +17,59 @@ open Meta
declare_config_elab elabSimpConfigCore Meta.Simp.Config
declare_config_elab elabSimpConfigCtxCore Meta.Simp.ConfigCtx
/--
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 → MetaM α) : 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 ")")?`
If `ctx == false`, the argument is assumed to have type `Meta.Simp.Config`, and `Meta.Simp.ConfigCtx` otherwise. -/
@ -121,11 +174,17 @@ private def getPropHyps : MetaM (Array FVarId) := do
return result
structure MkSimpContextResult where
ctx : Simp.Context
fvarIdToLemmaId : FVarIdToLemmaId
ctx : Simp.Context
dischargeWrapper : Simp.DischargeWrapper
fvarIdToLemmaId : FVarIdToLemmaId
-- If `ctx == false`, the argument is assumed to have type `Meta.Simp.Config`, and `Meta.Simp.ConfigCtx` otherwise. -/
/--
If `ctx == false`, the config argument is assumed to have type `Meta.Simp.Config`, and `Meta.Simp.ConfigCtx` otherwise.
If `ctx == false`, the `discharge` option must be none -/
def mkSimpContext (stx : Syntax) (eraseLocal : Bool) (ctx := false) (ignoreStarArg : Bool := false) : TacticM MkSimpContextResult := do
if ctx && !stx[2].isNone then
throwError "'simp_all' tactic does not support 'discharger' option"
let dischargeWrapper ← mkDischargeWrapper stx[2]
let simpOnly := !stx[3].isNone
let simpLemmas ←
if simpOnly then
@ -138,7 +197,7 @@ def mkSimpContext (stx : Syntax) (eraseLocal : Bool) (ctx := false) (ignoreStarA
simpLemmas, congrLemmas
}
if !r.starArg || ignoreStarArg then
return { r with fvarIdToLemmaId := {} }
return { r with fvarIdToLemmaId := {}, dischargeWrapper }
else
let ctx := r.ctx
let erased := ctx.simpLemmas.erased
@ -154,27 +213,30 @@ def mkSimpContext (stx : Syntax) (eraseLocal : Bool) (ctx := false) (ignoreStarA
fvarIdToLemmaId := fvarIdToLemmaId.insert fvarId id
let simpLemmas ← ctx.simpLemmas.add #[] proof (name? := id)
ctx := { ctx with simpLemmas }
return { ctx, fvarIdToLemmaId }
return { ctx, fvarIdToLemmaId, dischargeWrapper }
/-
"simp " (config)? (discharger)? ("only ")? ("[" simpLemma,* "]")? (location)?
-/
@[builtinTactic Lean.Parser.Tactic.simp] def evalSimp : Tactic := fun stx => do
let { ctx, fvarIdToLemmaId } ← withMainContext <| mkSimpContext stx (eraseLocal := false)
let { ctx, fvarIdToLemmaId, dischargeWrapper } ← withMainContext <| mkSimpContext stx (eraseLocal := false)
-- trace[Meta.debug] "Lemmas {← toMessageData ctx.simpLemmas.post}"
let loc := expandOptLocation stx[5]
match loc with
| Location.targets hUserNames simplifyTarget =>
withMainContext do
let fvarIds ← hUserNames.mapM fun hUserName => return (← getLocalDeclFromUserName hUserName).fvarId
go ctx fvarIds simplifyTarget fvarIdToLemmaId
go ctx dischargeWrapper fvarIds simplifyTarget fvarIdToLemmaId
| Location.wildcard =>
withMainContext do
go ctx (← getNondepPropHyps (← getMainGoal)) (simplifyTarget := true) fvarIdToLemmaId
go ctx dischargeWrapper (← getNondepPropHyps (← getMainGoal)) (simplifyTarget := true) fvarIdToLemmaId
where
go (ctx : Simp.Context) (fvarIdsToSimp : Array FVarId) (simplifyTarget : Bool) (fvarIdToLemmaId : FVarIdToLemmaId) : TacticM Unit := do
liftMetaTactic1 fun mvarId =>
return (← simpGoal mvarId ctx (simplifyTarget := simplifyTarget) (fvarIdsToSimp := fvarIdsToSimp) (fvarIdToLemmaId := fvarIdToLemmaId)).map (·.2)
go (ctx : Simp.Context) (dischargeWrapper : Simp.DischargeWrapper) (fvarIdsToSimp : Array FVarId) (simplifyTarget : Bool) (fvarIdToLemmaId : FVarIdToLemmaId) : TacticM Unit := do
let mvarId ← getMainGoal
let result? ← dischargeWrapper.with fun discharge? => return (← simpGoal mvarId ctx (simplifyTarget := simplifyTarget) (discharge? := discharge?) (fvarIdsToSimp := fvarIdsToSimp) (fvarIdToLemmaId := fvarIdToLemmaId)).map (·.2)
match result? with
| none => replaceMainGoal []
| some mvarId => replaceMainGoal [mvarId]
@[builtinTactic Lean.Parser.Tactic.simpAll] def evalSimpAll : Tactic := fun stx => do
let { ctx, .. } ← mkSimpContext stx (eraseLocal := true) (ctx := true) (ignoreStarArg := true)

24
tests/lean/simpDisch.lean Normal file
View file

@ -0,0 +1,24 @@
constant f : Nat → Nat
@[simp] axiom fEq (x : Nat) (h : x ≠ 0) : f x = x
example (x : Nat) (h : x ≠ 0) : f x = x + 0 := by
simp (discharger := traceState; exact (fun h' => h') h)
example (x y : Nat) (h1 : x ≠ 0) (h2 : y ≠ 0) (h3 : x = y) : f x = f y + 0 := by
simp (discharger := traceState; assumption)
assumption
example (x y : Nat) (h1 : x ≠ 0) (h2 : y ≠ 0) (h3 : x = y) : f x = f y + 0 := by
simp (discharger := assumption)
assumption
example (x y : Nat) (h1 : x ≠ 0) (h2 : y ≠ 0) (h3 : x = y) : f x = f y + 0 := by
simp (disch := assumption)
assumption
example (x y : Nat) (h1 : x ≠ 0) (h2 : y ≠ 0) (h3 : x = y) : f x = f y + 0 := by
conv => lhs; simp (disch := assumption)
traceState
conv => rhs; simp (disch := assumption)
traceState
assumption

View file

@ -0,0 +1,26 @@
case simp.discharger
x : Nat
h : x ≠ 0
⊢ x ≠ 0
case simp.discharger
x y : Nat
h1 : x ≠ 0
h2 : y ≠ 0
h3 : x = y
⊢ x ≠ 0
case simp.discharger
x y : Nat
h1 : x ≠ 0
h2 : y ≠ 0
h3 : x = y
⊢ y ≠ 0
x y : Nat
h1 : x ≠ 0
h2 : y ≠ 0
h3 : x = y
⊢ x = f y + 0
x y : Nat
h1 : x ≠ 0
h2 : y ≠ 0
h3 : x = y
⊢ x = y