/- Copyright (c) 2020 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Leonardo de Moura -/ prelude import Lean.Meta.Transform import Lean.Meta.Tactic.Replace import Lean.Meta.Tactic.UnifyEq import Lean.Meta.Tactic.Simp.Rewrite import Lean.Meta.Match.Value namespace Lean.Meta namespace Simp builtin_initialize congrHypothesisExceptionId : InternalExceptionId ← registerInternalExceptionId `congrHypothesisFailed def throwCongrHypothesisFailed : MetaM α := throw <| Exception.internal congrHypothesisExceptionId /-- Helper method for bootstrapping purposes. It disables `arith` if support theorems have not been defined yet. -/ def Config.updateArith (c : Config) : CoreM Config := do if c.arith then if (← getEnv).contains ``Nat.Linear.ExprCnstr.eq_of_toNormPoly_eq then return c else return { c with arith := false } else return c /-- Return true if `e` is of the form `ofNat n` where `n` is a kernel Nat literal -/ def isOfNatNatLit (e : Expr) : Bool := e.isAppOfArity ``OfNat.ofNat 3 && e.appFn!.appArg!.isRawNatLit private def reduceProjFn? (e : Expr) : SimpM (Option Expr) := do matchConst e.getAppFn (fun _ => pure none) fun cinfo _ => do match (← getProjectionFnInfo? cinfo.name) with | none => return none | some projInfo => /- Helper function for applying `reduceProj?` to the result of `unfoldDefinition?` -/ let reduceProjCont? (e? : Option Expr) : SimpM (Option Expr) := do match e? with | none => pure none | some e => match (← reduceProj? e.getAppFn) with | some f => return some (mkAppN f e.getAppArgs) | none => return none if projInfo.fromClass then -- `class` projection if (← getContext).isDeclToUnfold cinfo.name then /- If user requested `class` projection to be unfolded, we set transparency mode to `.instances`, and invoke `unfoldDefinition?`. Recall that `unfoldDefinition?` has support for unfolding this kind of projection when transparency mode is `.instances`. -/ let e? ← withReducibleAndInstances <| unfoldDefinition? e if e?.isSome then recordSimpTheorem (.decl cinfo.name) return e? else /- Recall that class projections are **not** marked with `[reducible]` because we want them to be in "reducible canonical form". However, if we have a class projection of the form `Class.projFn (Class.mk ...)`, we want to reduce it. See issue #1869 for an example where this is important. -/ unless e.getAppNumArgs > projInfo.numParams do return none let major := e.getArg! projInfo.numParams unless (← isConstructorApp major) do return none reduceProjCont? (← withDefault <| unfoldDefinition? e) else -- `structure` projections reduceProjCont? (← unfoldDefinition? e) private def reduceFVar (cfg : Config) (thms : SimpTheoremsArray) (e : Expr) : SimpM Expr := do let localDecl ← getFVarLocalDecl e if cfg.zetaDelta || thms.isLetDeclToUnfold e.fvarId! || localDecl.isImplementationDetail then if !cfg.zetaDelta && thms.isLetDeclToUnfold e.fvarId! then recordSimpTheorem (.fvar localDecl.fvarId) let some v := localDecl.value? | return e return v else return e /-- Return true if `declName` is the name of a definition of the form ``` def declName ... := match ... with | ... ``` -/ private partial def isMatchDef (declName : Name) : CoreM Bool := do let .defnInfo info ← getConstInfo declName | return false return go (← getEnv) info.value where go (env : Environment) (e : Expr) : Bool := if e.isLambda then go env e.bindingBody! else let f := e.getAppFn f.isConst && isMatcherCore env f.constName! /-- Try to unfold `e`. -/ private def unfold? (e : Expr) : SimpM (Option Expr) := do let f := e.getAppFn if !f.isConst then return none let fName := f.constName! let ctx ← getContext let rec unfoldDeclToUnfold? : SimpM (Option Expr) := do let options ← getOptions let cfg ← getConfig -- Support for issue #2042 if cfg.unfoldPartialApp -- If we are unfolding partial applications, ignore issue #2042 -- When smart unfolding is enabled, and `f` supports it, we don't need to worry about issue #2042 || (smartUnfolding.get options && (← getEnv).contains (mkSmartUnfoldingNameFor fName)) then withDefault <| unfoldDefinition? e else -- `We are not unfolding partial applications, and `fName` does not have smart unfolding support. -- Thus, we must check whether the arity of the function >= number of arguments. let some cinfo := (← getEnv).find? fName | return none let some value := cinfo.value? | return none let arity := value.getNumHeadLambdas -- Partially applied function, return `none`. See issue #2042 if arity > e.getAppNumArgs then return none withDefault <| unfoldDefinition? e if (← isProjectionFn fName) then return none -- should be reduced by `reduceProjFn?` else if ctx.config.autoUnfold then if ctx.simpTheorems.isErased (.decl fName) then return none else if hasSmartUnfoldingDecl (← getEnv) fName then withDefault <| unfoldDefinition? e else if (← isMatchDef fName) then let some value ← withDefault <| unfoldDefinition? e | return none let .reduced value ← reduceMatcher? value | return none return some value else return none else if ctx.isDeclToUnfold fName then unfoldDeclToUnfold? else return none private def reduceStep (e : Expr) : SimpM Expr := do let cfg ← getConfig let f := e.getAppFn if f.isMVar then return (← instantiateMVars e) if cfg.beta then if f.isHeadBetaTargetFn false then return f.betaRev e.getAppRevArgs -- TODO: eta reduction if cfg.proj then match (← reduceProjFn? e) with | some e => return e | none => pure () if cfg.iota then match (← reduceRecMatcher? e) with | some e => return e | none => pure () if cfg.zeta then if let some (args, _, _, v, b) := e.letFunAppArgs? then return mkAppN (b.instantiate1 v) args if e.isLet then return e.letBody!.instantiate1 e.letValue! match (← unfold? e) with | some e' => trace[Meta.Tactic.simp.rewrite] "unfold {mkConst e.getAppFn.constName!}, {e} ==> {e'}" recordSimpTheorem (.decl e.getAppFn.constName!) return e' | none => return e private partial def reduce (e : Expr) : SimpM Expr := withIncRecDepth do let e' ← reduceStep e if e' == e then return e' else reduce e' instance : Inhabited (SimpM α) where default := fun _ _ _ => default partial def lambdaTelescopeDSimp (e : Expr) (k : Array Expr → Expr → SimpM α) : SimpM α := do go #[] e where go (xs : Array Expr) (e : Expr) : SimpM α := do match e with | .lam n d b c => withLocalDecl n c (← dsimp d) fun x => go (xs.push x) (b.instantiate1 x) | e => k xs e inductive SimpLetCase where | dep -- `let x := v; b` is not equivalent to `(fun x => b) v` | nondepDepVar -- `let x := v; b` is equivalent to `(fun x => b) v`, but result type depends on `x` | nondep -- `let x := v; b` is equivalent to `(fun x => b) v`, and result type does not depend on `x` def getSimpLetCase (n : Name) (t : Expr) (b : Expr) : MetaM SimpLetCase := do withLocalDeclD n t fun x => do let bx := b.instantiate1 x /- The following step is potentially very expensive when we have many nested let-decls. TODO: handle a block of nested let decls in a single pass if this becomes a performance problem. -/ if (← isTypeCorrect bx) then let bxType ← whnf (← inferType bx) if (← dependsOn bxType x.fvarId!) then return SimpLetCase.nondepDepVar else return SimpLetCase.nondep else return SimpLetCase.dep def withNewLemmas {α} (xs : Array Expr) (f : SimpM α) : SimpM α := do if (← getConfig).contextual then let mut s ← getSimpTheorems let mut updated := false for x in xs do if (← isProof x) then s ← s.addTheorem (.fvar x.fvarId!) x updated := true if updated then withSimpTheorems s f else f else f def simpLit (e : Expr) : SimpM Result := do match e.natLit? with | some n => /- If `OfNat.ofNat` is marked to be unfolded, we do not pack orphan nat literals as `OfNat.ofNat` applications to avoid non-termination. See issue #788. -/ if (← readThe Simp.Context).isDeclToUnfold ``OfNat.ofNat then return { expr := e } else return { expr := (← mkNumeral (mkConst ``Nat) n) } | none => return { expr := e } def simpProj (e : Expr) : SimpM Result := do match (← reduceProj? e) with | some e => return { expr := e } | none => let s := e.projExpr! let motive? ← withLocalDeclD `s (← inferType s) fun s => do let p := e.updateProj! s if (← dependsOn (← inferType p) s.fvarId!) then return none else let motive ← mkLambdaFVars #[s] (← mkEq e p) if !(← isTypeCorrect motive) then return none else return some motive if let some motive := motive? then let r ← simp s let eNew := e.updateProj! r.expr match r.proof? with | none => return { expr := eNew } | some h => let hNew ← mkEqNDRec motive (← mkEqRefl e) h return { expr := eNew, proof? := some hNew } else return { expr := (← dsimp e) } def simpConst (e : Expr) : SimpM Result := return { expr := (← reduce e) } def simpLambda (e : Expr) : SimpM Result := withParent e <| lambdaTelescopeDSimp e fun xs e => withNewLemmas xs do let r ← simp e let eNew ← mkLambdaFVars xs r.expr match r.proof? with | none => return { expr := eNew } | some h => let p ← xs.foldrM (init := h) fun x h => do mkFunExt (← mkLambdaFVars #[x] h) return { expr := eNew, proof? := p } def simpArrow (e : Expr) : SimpM Result := do trace[Debug.Meta.Tactic.simp] "arrow {e}" let p := e.bindingDomain! let q := e.bindingBody! let rp ← simp p trace[Debug.Meta.Tactic.simp] "arrow [{(← getConfig).contextual}] {p} [{← isProp p}] -> {q} [{← isProp q}]" if (← pure (← getConfig).contextual <&&> isProp p <&&> isProp q) then trace[Debug.Meta.Tactic.simp] "ctx arrow {rp.expr} -> {q}" withLocalDeclD e.bindingName! rp.expr fun h => do let s ← getSimpTheorems let s ← s.addTheorem (.fvar h.fvarId!) h withSimpTheorems s do let rq ← simp q match rq.proof? with | none => mkImpCongr e rp rq | some hq => let hq ← mkLambdaFVars #[h] hq /- We use the default reducibility setting at `mkImpDepCongrCtx` and `mkImpCongrCtx` because they use the theorems ```lean @implies_dep_congr_ctx : ∀ {p₁ p₂ q₁ : Prop}, p₁ = p₂ → ∀ {q₂ : p₂ → Prop}, (∀ (h : p₂), q₁ = q₂ h) → (p₁ → q₁) = ∀ (h : p₂), q₂ h @implies_congr_ctx : ∀ {p₁ p₂ q₁ q₂ : Prop}, p₁ = p₂ → (p₂ → q₁ = q₂) → (p₁ → q₁) = (p₂ → q₂) ``` And the proofs may be from `rfl` theorems which are now omitted. Moreover, we cannot establish that the two terms are definitionally equal using `withReducible`. TODO (better solution): provide the problematic implicit arguments explicitly. It is more efficient and avoids this problem. -/ if rq.expr.containsFVar h.fvarId! then return { expr := (← mkForallFVars #[h] rq.expr), proof? := (← withDefault <| mkImpDepCongrCtx (← rp.getProof) hq) } else return { expr := e.updateForallE! rp.expr rq.expr, proof? := (← withDefault <| mkImpCongrCtx (← rp.getProof) hq) } else mkImpCongr e rp (← simp q) def simpForall (e : Expr) : SimpM Result := withParent e do trace[Debug.Meta.Tactic.simp] "forall {e}" if e.isArrow then simpArrow e else if (← isProp e) then /- The forall is a proposition. -/ let domain := e.bindingDomain! if (← isProp domain) then /- The domain of the forall is also a proposition, and we can use `forall_prop_domain_congr` IF we can simplify the domain. -/ let rd ← simp domain if let some h₁ := rd.proof? then /- Using ``` theorem forall_prop_domain_congr {p₁ p₂ : Prop} {q₁ : p₁ → Prop} {q₂ : p₂ → Prop} (h₁ : p₁ = p₂) (h₂ : ∀ a : p₂, q₁ (h₁.substr a) = q₂ a) : (∀ a : p₁, q₁ a) = (∀ a : p₂, q₂ a) ``` Remark: we should consider whether we want to add congruence lemma support for arbitrary `forall`-expressions. Then, the theroem above can be marked as `@[congr]` and the following code deleted. -/ let p₁ := domain let p₂ := rd.expr let q₁ := mkLambda e.bindingName! e.bindingInfo! p₁ e.bindingBody! let result ← withLocalDecl e.bindingName! e.bindingInfo! p₂ fun a => withNewLemmas #[a] do let prop := mkSort levelZero let h₁_substr_a := mkApp6 (mkConst ``Eq.substr [levelOne]) prop (mkLambda `x .default prop (mkBVar 0)) p₂ p₁ h₁ a let q_h₁_substr_a := e.bindingBody!.instantiate1 h₁_substr_a let rb ← simp q_h₁_substr_a let h₂ ← mkLambdaFVars #[a] (← rb.getProof) let q₂ ← mkLambdaFVars #[a] rb.expr let result ← mkForallFVars #[a] rb.expr let proof := mkApp6 (mkConst ``forall_prop_domain_congr) p₁ p₂ q₁ q₂ h₁ h₂ return { expr := result, proof? := proof } return result let domain ← dsimp domain withLocalDecl e.bindingName! e.bindingInfo! domain fun x => withNewLemmas #[x] do let b := e.bindingBody!.instantiate1 x let rb ← simp b let eNew ← mkForallFVars #[x] rb.expr match rb.proof? with | none => return { expr := eNew } | some h => return { expr := eNew, proof? := (← mkForallCongr (← mkLambdaFVars #[x] h)) } else return { expr := (← dsimp e) } def simpLet (e : Expr) : SimpM Result := do let .letE n t v b _ := e | unreachable! if (← getConfig).zeta then return { expr := b.instantiate1 v } else match (← getSimpLetCase n t b) with | SimpLetCase.dep => return { expr := (← dsimp e) } | SimpLetCase.nondep => let rv ← simp v withLocalDeclD n t fun x => do let bx := b.instantiate1 x let rbx ← simp bx let hb? ← match rbx.proof? with | none => pure none | some h => pure (some (← mkLambdaFVars #[x] h)) let e' := mkLet n t rv.expr (← rbx.expr.abstractM #[x]) match rv.proof?, hb? with | none, none => return { expr := e' } | some h, none => return { expr := e', proof? := some (← mkLetValCongr (← mkLambdaFVars #[x] rbx.expr) h) } | _, some h => return { expr := e', proof? := some (← mkLetCongr (← rv.getProof) h) } | SimpLetCase.nondepDepVar => let v' ← dsimp v withLocalDeclD n t fun x => do let bx := b.instantiate1 x let rbx ← simp bx let e' := mkLet n t v' (← rbx.expr.abstractM #[x]) match rbx.proof? with | none => return { expr := e' } | some h => let h ← mkLambdaFVars #[x] h return { expr := e', proof? := some (← mkLetBodyCongr v' h) } @[export lean_dsimp] private partial def dsimpImpl (e : Expr) : SimpM Expr := do let cfg ← getConfig unless cfg.dsimp do return e let pre (e : Expr) : SimpM TransformStep := do if let Step.visit r ← rewritePre (rflOnly := true) e then if r.expr != e then return .visit r.expr return .continue let post (e : Expr) : SimpM TransformStep := do if let Step.visit r ← rewritePost (rflOnly := true) e then if r.expr != e then return .visit r.expr let mut eNew ← reduce e if eNew.isFVar then eNew ← reduceFVar cfg (← getSimpTheorems) eNew if eNew != e then return .visit eNew else return .done e transform (usedLetOnly := cfg.zeta) e (pre := pre) (post := post) def visitFn (e : Expr) : SimpM Result := do let f := e.getAppFn let fNew ← simp f if fNew.expr == f then return { expr := e } else let args := e.getAppArgs let eNew := mkAppN fNew.expr args if fNew.proof?.isNone then return { expr := eNew } let mut proof ← fNew.getProof for arg in args do proof ← Meta.mkCongrFun proof arg return { expr := eNew, proof? := proof } def congrDefault (e : Expr) : SimpM Result := do if let some result ← tryAutoCongrTheorem? e then result.mkEqTrans (← visitFn result.expr) else withParent e <| e.withApp fun f args => do congrArgs (← simp f) args /-- Process the given congruence theorem hypothesis. Return true if it made "progress". -/ def processCongrHypothesis (h : Expr) : SimpM Bool := do forallTelescopeReducing (← inferType h) fun xs hType => withNewLemmas xs do let lhs ← instantiateMVars hType.appFn!.appArg! let r ← simp lhs let rhs := hType.appArg! rhs.withApp fun m zs => do let val ← mkLambdaFVars zs r.expr unless (← isDefEq m val) do throwCongrHypothesisFailed let mut proof ← r.getProof if hType.isAppOf ``Iff then try proof ← mkIffOfEq proof catch _ => throwCongrHypothesisFailed unless (← isDefEq h (← mkLambdaFVars xs proof)) do throwCongrHypothesisFailed /- We used to return `false` if `r.proof? = none` (i.e., an implicit `rfl` proof) because we assumed `dsimp` would also be able to simplify the term, but this is not true for non-trivial user-provided theorems. Example: ``` @[congr] theorem image_congr {f g : α → β} {s : Set α} (h : ∀ a, mem a s → f a = g a) : image f s = image g s := ... example {Γ: Set Nat}: (image (Nat.succ ∘ Nat.succ) Γ) = (image (fun a => a.succ.succ) Γ) := by simp only [Function.comp_apply] ``` `Function.comp_apply` is a `rfl` theorem, but `dsimp` will not apply it because the composition is not fully applied. See comment at issue #1113 Thus, we have an extra check now if `xs.size > 0`. TODO: refine this test. -/ return r.proof?.isSome || (xs.size > 0 && lhs != r.expr) /-- Try to rewrite `e` children using the given congruence theorem -/ def trySimpCongrTheorem? (c : SimpCongrTheorem) (e : Expr) : SimpM (Option Result) := withNewMCtxDepth do trace[Debug.Meta.Tactic.simp.congr] "{c.theoremName}, {e}" let thm ← mkConstWithFreshMVarLevels c.theoremName let (xs, bis, type) ← forallMetaTelescopeReducing (← inferType thm) if c.hypothesesPos.any (· ≥ xs.size) then return none let isIff := type.isAppOf ``Iff let lhs := type.appFn!.appArg! let rhs := type.appArg! let numArgs := lhs.getAppNumArgs let mut e := e let mut extraArgs := #[] if e.getAppNumArgs > numArgs then let args := e.getAppArgs e := mkAppN e.getAppFn args[:numArgs] extraArgs := args[numArgs:].toArray if (← isDefEq lhs e) then let mut modified := false for i in c.hypothesesPos do let x := xs[i]! try if (← processCongrHypothesis x) then modified := true catch _ => trace[Meta.Tactic.simp.congr] "processCongrHypothesis {c.theoremName} failed {← inferType x}" -- Remark: we don't need to check ex.isMaxRecDepth anymore since `try .. catch ..` -- does not catch runtime exceptions by default. return none unless modified do trace[Meta.Tactic.simp.congr] "{c.theoremName} not modified" return none unless (← synthesizeArgs (.decl c.theoremName) bis xs) do trace[Meta.Tactic.simp.congr] "{c.theoremName} synthesizeArgs failed" return none let eNew ← instantiateMVars rhs let mut proof ← instantiateMVars (mkAppN thm xs) if isIff then try proof ← mkAppM ``propext #[proof] catch _ => return none if (← hasAssignableMVar proof <||> hasAssignableMVar eNew) then trace[Meta.Tactic.simp.congr] "{c.theoremName} has unassigned metavariables" return none congrArgs { expr := eNew, proof? := proof } extraArgs else return none def congr (e : Expr) : SimpM Result := do let f := e.getAppFn if f.isConst then let congrThms ← getSimpCongrTheorems let cs := congrThms.get f.constName! for c in cs do match (← trySimpCongrTheorem? c e) with | none => pure () | some r => return r congrDefault e else congrDefault e def simpApp (e : Expr) : SimpM Result := do if isOfNatNatLit e then -- Recall that we expand "orphan" kernel nat literals `n` into `ofNat n` return { expr := e } else congr e def simpStep (e : Expr) : SimpM Result := do match e with | .mdata m e => let r ← simp e; return { r with expr := mkMData m r.expr } | .proj .. => simpProj e | .app .. => simpApp e | .lam .. => simpLambda e | .forallE .. => simpForall e | .letE .. => simpLet e | .const .. => simpConst e | .bvar .. => unreachable! | .sort .. => return { expr := e } | .lit .. => simpLit e | .mvar .. => return { expr := (← instantiateMVars e) } | .fvar .. => return { expr := (← reduceFVar (← getConfig) (← getSimpTheorems) e) } def cacheResult (e : Expr) (cfg : Config) (r : Result) : SimpM Result := do if cfg.memoize && r.cache then let ctx ← readThe Simp.Context let dischargeDepth := ctx.dischargeDepth modify fun s => { s with cache := s.cache.insert e { r with dischargeDepth } } return r partial def simpLoop (e : Expr) : SimpM Result := withIncRecDepth do let cfg ← getConfig if (← get).numSteps > cfg.maxSteps then throwError "simp failed, maximum number of steps exceeded" else checkSystem "simp" modify fun s => { s with numSteps := s.numSteps + 1 } match (← pre e) with | .done r => cacheResult e cfg r | .visit r => cacheResult e cfg (← r.mkEqTrans (← simpLoop r.expr)) | .continue none => visitPreContinue cfg { expr := e } | .continue (some r) => visitPreContinue cfg r where visitPreContinue (cfg : Config) (r : Result) : SimpM Result := do let eNew ← reduceStep r.expr if eNew != r.expr then let r := { r with expr := eNew } cacheResult e cfg (← r.mkEqTrans (← simpLoop r.expr)) else let r ← r.mkEqTrans (← simpStep r.expr) visitPost cfg r visitPost (cfg : Config) (r : Result) : SimpM Result := do match (← post r.expr) with | .done r' => cacheResult e cfg (← r.mkEqTrans r') | .continue none => visitPostContinue cfg r | .visit r' | .continue (some r') => visitPostContinue cfg (← r.mkEqTrans r') visitPostContinue (cfg : Config) (r : Result) : SimpM Result := do let mut r := r unless cfg.singlePass || e == r.expr do r ← r.mkEqTrans (← simpLoop r.expr) cacheResult e cfg r @[export lean_simp] def simpImpl (e : Expr) : SimpM Result := withIncRecDepth do checkSystem "simp" if (← isProof e) then return { expr := e } go where go : SimpM Result := do let cfg ← getConfig if cfg.memoize then let cache := (← get).cache if let some result := cache.find? e then /- If the result was cached at a dischargeDepth > the current one, it may not be valid. See issue #1234 -/ if result.dischargeDepth ≤ (← readThe Simp.Context).dischargeDepth then return result trace[Meta.Tactic.simp.heads] "{repr e.toHeadIndex}" simpLoop e @[inline] def withSimpConfig (ctx : Context) (x : MetaM α) : MetaM α := withConfig (fun c => { c with etaStruct := ctx.config.etaStruct }) <| withReducible x def main (e : Expr) (ctx : Context) (usedSimps : UsedSimps := {}) (methods : Methods := {}) : MetaM (Result × UsedSimps) := do let ctx := { ctx with config := (← ctx.config.updateArith) } withSimpConfig ctx do withCatchingRuntimeEx do try withoutCatchingRuntimeEx do let (r, s) ← simp e methods.toMethodsRef ctx |>.run { usedTheorems := usedSimps } trace[Meta.Tactic.simp.numSteps] "{s.numSteps}" return (r, s.usedTheorems) catch ex => if ex.isRuntime then throwNestedTacticEx `simp ex else throw ex def dsimpMain (e : Expr) (ctx : Context) (usedSimps : UsedSimps := {}) (methods : Methods := {}) : MetaM (Expr × UsedSimps) := do withSimpConfig ctx do withCatchingRuntimeEx do try withoutCatchingRuntimeEx do let (r, s) ← dsimp e methods.toMethodsRef ctx |>.run { usedTheorems := usedSimps } pure (r, s.usedTheorems) catch ex => if ex.isRuntime then throwNestedTacticEx `dsimp ex else throw ex end Simp open Simp (UsedSimps SimprocsArray) def simp (e : Expr) (ctx : Simp.Context) (simprocs : SimprocsArray := #[]) (discharge? : Option Simp.Discharge := none) (usedSimps : UsedSimps := {}) : MetaM (Simp.Result × UsedSimps) := do profileitM Exception "simp" (← getOptions) do match discharge? with | none => Simp.main e ctx usedSimps (methods := Simp.mkDefaultMethodsCore simprocs) | some d => Simp.main e ctx usedSimps (methods := Simp.mkMethods simprocs d) def dsimp (e : Expr) (ctx : Simp.Context) (usedSimps : UsedSimps := {}) : MetaM (Expr × UsedSimps) := do profileitM Exception "dsimp" (← getOptions) do Simp.dsimpMain e ctx usedSimps (methods := Simp.mkDefaultMethodsCore {}) /-- See `simpTarget`. This method assumes `mvarId` is not assigned, and we are already using `mvarId`s local context. -/ def simpTargetCore (mvarId : MVarId) (ctx : Simp.Context) (simprocs : SimprocsArray := #[]) (discharge? : Option Simp.Discharge := none) (mayCloseGoal := true) (usedSimps : UsedSimps := {}) : MetaM (Option MVarId × UsedSimps) := do let target ← instantiateMVars (← mvarId.getType) let (r, usedSimps) ← simp target ctx simprocs discharge? usedSimps if mayCloseGoal && r.expr.isTrue then match r.proof? with | some proof => mvarId.assign (← mkOfEqTrue proof) | none => mvarId.assign (mkConst ``True.intro) return (none, usedSimps) else return (← applySimpResultToTarget mvarId target r, usedSimps) /-- Simplify the given goal target (aka type). Return `none` if the goal was closed. Return `some mvarId'` otherwise, where `mvarId'` is the simplified new goal. -/ def simpTarget (mvarId : MVarId) (ctx : Simp.Context) (simprocs : SimprocsArray := #[]) (discharge? : Option Simp.Discharge := none) (mayCloseGoal := true) (usedSimps : UsedSimps := {}) : MetaM (Option MVarId × UsedSimps) := mvarId.withContext do mvarId.checkNotAssigned `simp simpTargetCore mvarId ctx simprocs discharge? mayCloseGoal usedSimps /-- Apply the result `r` for `prop` (which is inhabited by `proof`). Return `none` if the goal was closed. Return `some (proof', prop')` otherwise, where `proof' : prop'` and `prop'` is the simplified `prop`. This method assumes `mvarId` is not assigned, and we are already using `mvarId`s local context. -/ def applySimpResultToProp (mvarId : MVarId) (proof : Expr) (prop : Expr) (r : Simp.Result) (mayCloseGoal := true) : MetaM (Option (Expr × Expr)) := do if mayCloseGoal && r.expr.isFalse then match r.proof? with | some eqProof => mvarId.assign (← mkFalseElim (← mvarId.getType) (← mkEqMP eqProof proof)) | none => mvarId.assign (← mkFalseElim (← mvarId.getType) proof) return none else match r.proof? with | some eqProof => return some ((← mkEqMP eqProof proof), r.expr) | none => if r.expr != prop then return some ((← mkExpectedTypeHint proof r.expr), r.expr) else return some (proof, r.expr) def applySimpResultToFVarId (mvarId : MVarId) (fvarId : FVarId) (r : Simp.Result) (mayCloseGoal : Bool) : MetaM (Option (Expr × Expr)) := do let localDecl ← fvarId.getDecl applySimpResultToProp mvarId (mkFVar fvarId) localDecl.type r mayCloseGoal /-- Simplify `prop` (which is inhabited by `proof`). Return `none` if the goal was closed. Return `some (proof', prop')` otherwise, where `proof' : prop'` and `prop'` is the simplified `prop`. This method assumes `mvarId` is not assigned, and we are already using `mvarId`s local context. -/ def simpStep (mvarId : MVarId) (proof : Expr) (prop : Expr) (ctx : Simp.Context) (simprocs : SimprocsArray := #[]) (discharge? : Option Simp.Discharge := none) (mayCloseGoal := true) (usedSimps : UsedSimps := {}) : MetaM (Option (Expr × Expr) × UsedSimps) := do let (r, usedSimps) ← simp prop ctx simprocs discharge? usedSimps return (← applySimpResultToProp mvarId proof prop r (mayCloseGoal := mayCloseGoal), usedSimps) def applySimpResultToLocalDeclCore (mvarId : MVarId) (fvarId : FVarId) (r : Option (Expr × Expr)) : MetaM (Option (FVarId × MVarId)) := do match r with | none => return none | some (value, type') => let localDecl ← fvarId.getDecl if localDecl.type != type' then let mvarId ← mvarId.assert localDecl.userName type' value let mvarId ← mvarId.tryClear localDecl.fvarId let (fvarId, mvarId) ← mvarId.intro1P return some (fvarId, mvarId) else return some (fvarId, mvarId) /-- Simplify `simp` result to the given local declaration. Return `none` if the goal was closed. This method assumes `mvarId` is not assigned, and we are already using `mvarId`s local context. -/ def applySimpResultToLocalDecl (mvarId : MVarId) (fvarId : FVarId) (r : Simp.Result) (mayCloseGoal : Bool) : MetaM (Option (FVarId × MVarId)) := do if r.proof?.isNone then -- New result is definitionally equal to input. Thus, we can avoid creating a new variable if there are dependencies let mvarId ← mvarId.replaceLocalDeclDefEq fvarId r.expr if mayCloseGoal && r.expr.isFalse then mvarId.assign (← mkFalseElim (← mvarId.getType) (mkFVar fvarId)) return none else return some (fvarId, mvarId) else applySimpResultToLocalDeclCore mvarId fvarId (← applySimpResultToFVarId mvarId fvarId r mayCloseGoal) def simpLocalDecl (mvarId : MVarId) (fvarId : FVarId) (ctx : Simp.Context) (simprocs : SimprocsArray := #[]) (discharge? : Option Simp.Discharge := none) (mayCloseGoal := true) (usedSimps : UsedSimps := {}) : MetaM (Option (FVarId × MVarId) × UsedSimps) := do mvarId.withContext do mvarId.checkNotAssigned `simp let type ← instantiateMVars (← fvarId.getType) let (r, usedSimps) ← simpStep mvarId (mkFVar fvarId) type ctx simprocs discharge? mayCloseGoal usedSimps return (← applySimpResultToLocalDeclCore mvarId fvarId r, usedSimps) def simpGoal (mvarId : MVarId) (ctx : Simp.Context) (simprocs : SimprocsArray := #[]) (discharge? : Option Simp.Discharge := none) (simplifyTarget : Bool := true) (fvarIdsToSimp : Array FVarId := #[]) (usedSimps : UsedSimps := {}) : MetaM (Option (Array FVarId × MVarId) × UsedSimps) := do mvarId.withContext do mvarId.checkNotAssigned `simp let mut mvarIdNew := mvarId let mut toAssert := #[] let mut replaced := #[] let mut usedSimps := usedSimps for fvarId in fvarIdsToSimp do let localDecl ← fvarId.getDecl let type ← instantiateMVars localDecl.type let ctx := { ctx with simpTheorems := ctx.simpTheorems.eraseTheorem (.fvar localDecl.fvarId) } let (r, usedSimps') ← simp type ctx simprocs discharge? usedSimps usedSimps := usedSimps' match r.proof? with | some _ => match (← applySimpResultToProp mvarIdNew (mkFVar fvarId) type r) with | none => return (none, usedSimps) | some (value, type) => toAssert := toAssert.push { userName := localDecl.userName, type := type, value := value } | none => if r.expr.isFalse then mvarIdNew.assign (← mkFalseElim (← mvarIdNew.getType) (mkFVar fvarId)) return (none, usedSimps) -- TODO: if there are no forwards dependencies we may consider using the same approach we used when `r.proof?` is a `some ...` -- Reason: it introduces a `mkExpectedTypeHint` mvarIdNew ← mvarIdNew.replaceLocalDeclDefEq fvarId r.expr replaced := replaced.push fvarId if simplifyTarget then match (← simpTarget mvarIdNew ctx simprocs discharge? (usedSimps := usedSimps)) with | (none, usedSimps') => return (none, usedSimps') | (some mvarIdNew', usedSimps') => mvarIdNew := mvarIdNew'; usedSimps := usedSimps' let (fvarIdsNew, mvarIdNew') ← mvarIdNew.assertHypotheses toAssert mvarIdNew := mvarIdNew' let toClear := fvarIdsToSimp.filter fun fvarId => !replaced.contains fvarId mvarIdNew ← mvarIdNew.tryClearMany toClear if ctx.config.failIfUnchanged && mvarId == mvarIdNew then throwError "simp made no progress" return (some (fvarIdsNew, mvarIdNew), usedSimps) def simpTargetStar (mvarId : MVarId) (ctx : Simp.Context) (simprocs : SimprocsArray := #[]) (discharge? : Option Simp.Discharge := none) (usedSimps : UsedSimps := {}) : MetaM (TacticResultCNM × UsedSimps) := mvarId.withContext do let mut ctx := ctx for h in (← getPropHyps) do let localDecl ← h.getDecl let proof := localDecl.toExpr let simpTheorems ← ctx.simpTheorems.addTheorem (.fvar h) proof ctx := { ctx with simpTheorems } match (← simpTarget mvarId ctx simprocs discharge? (usedSimps := usedSimps)) with | (none, usedSimps) => return (TacticResultCNM.closed, usedSimps) | (some mvarId', usedSimps') => if (← mvarId.getType) == (← mvarId'.getType) then return (TacticResultCNM.noChange, usedSimps) else return (TacticResultCNM.modified mvarId', usedSimps') def dsimpGoal (mvarId : MVarId) (ctx : Simp.Context) (simplifyTarget : Bool := true) (fvarIdsToSimp : Array FVarId := #[]) (usedSimps : UsedSimps := {}) : MetaM (Option MVarId × UsedSimps) := do mvarId.withContext do mvarId.checkNotAssigned `simp let mut mvarIdNew := mvarId let mut usedSimps : UsedSimps := usedSimps for fvarId in fvarIdsToSimp do let type ← instantiateMVars (← fvarId.getType) let (typeNew, usedSimps') ← dsimp type ctx usedSimps := usedSimps' if typeNew.isFalse then mvarIdNew.assign (← mkFalseElim (← mvarIdNew.getType) (mkFVar fvarId)) return (none, usedSimps) if typeNew != type then mvarIdNew ← mvarIdNew.replaceLocalDeclDefEq fvarId typeNew if simplifyTarget then let target ← mvarIdNew.getType let (targetNew, usedSimps') ← dsimp target ctx usedSimps usedSimps := usedSimps' if targetNew.isTrue then mvarIdNew.assign (mkConst ``True.intro) return (none, usedSimps) if let some (_, lhs, rhs) := targetNew.consumeMData.eq? then if (← withReducible <| isDefEq lhs rhs) then mvarIdNew.assign (← mkEqRefl lhs) return (none, usedSimps) if target != targetNew then mvarIdNew ← mvarIdNew.replaceTargetDefEq targetNew pure () -- FIXME: bug in do notation if this is removed? if ctx.config.failIfUnchanged && mvarId == mvarIdNew then throwError "dsimp made no progress" return (some mvarIdNew, usedSimps) end Lean.Meta