This PR adds the `binderNameHint` gadget. It can be used in rewrite and
simp rules to preserve a user-provided name where possible.
The expression `binderNameHint v binder e` defined to be `e`.
If it is used on the right-hand side of an equation that is applied by a
tactic like `rw` or `simp`,
and `v` is a local variable, and `binder` is an expression that (after
beta-reduction) is a binder
(so `fun w => …` or `∀ w, …`), then it will rename `v` to the name used
in the binder, and remove
the `binderNameHint`.
A typical use of this gadget would be as follows; the gadget ensures
that after rewriting, the local
variable is still `name`, and not `x`:
```
theorem all_eq_not_any_not (l : List α) (p : α → Bool) :
l.all p = !l.any fun x => binderNameHint x p (!p x) := sorry
example (names : List String) : names.all (fun name => "Waldo".isPrefixOf name) = true := by
rw [all_eq_not_any_not]
-- ⊢ (!names.any fun name => !"Waldo".isPrefixOf name) = true
```
This gadget is supported by `simp`, `dsimp` and `rw` in the
right-hand-side of an equation, but not
in hypotheses or by other tactics.
94 lines
4.7 KiB
Text
94 lines
4.7 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
|
||
-/
|
||
prelude
|
||
import Lean.Meta.AppBuilder
|
||
import Lean.Meta.MatchUtil
|
||
import Lean.Meta.KAbstract
|
||
import Lean.Meta.Check
|
||
import Lean.Meta.Tactic.Util
|
||
import Lean.Meta.Tactic.Apply
|
||
import Lean.Meta.BinderNameHint
|
||
|
||
namespace Lean.Meta
|
||
|
||
structure RewriteResult where
|
||
eNew : Expr
|
||
eqProof : Expr
|
||
mvarIds : List MVarId -- new goals
|
||
|
||
/--
|
||
Rewrite goal `mvarId`
|
||
-/
|
||
def _root_.Lean.MVarId.rewrite (mvarId : MVarId) (e : Expr) (heq : Expr)
|
||
(symm : Bool := false) (config := { : Rewrite.Config }) : MetaM RewriteResult :=
|
||
mvarId.withContext do
|
||
mvarId.checkNotAssigned `rewrite
|
||
let heqIn := heq
|
||
let heqType ← instantiateMVars (← inferType heq)
|
||
let (newMVars, binderInfos, heqType) ← forallMetaTelescopeReducing heqType
|
||
let heq := mkAppN heq newMVars
|
||
let cont (heq heqType : Expr) : MetaM RewriteResult := do
|
||
match (← matchEq? heqType) with
|
||
| none => throwTacticEx `rewrite mvarId m!"equality or iff proof expected{indentExpr heqType}"
|
||
| some (α, lhs, rhs) =>
|
||
let cont (heq heqType lhs rhs : Expr) : MetaM RewriteResult := do
|
||
if lhs.getAppFn.isMVar then
|
||
throwTacticEx `rewrite mvarId m!"pattern is a metavariable{indentExpr lhs}\nfrom equation{indentExpr heqType}"
|
||
let e ← instantiateMVars e
|
||
let eAbst ← withConfig (fun oldConfig => { config, oldConfig with }) <| kabstract e lhs config.occs
|
||
unless eAbst.hasLooseBVars do
|
||
throwTacticEx `rewrite mvarId m!"did not find instance of the pattern in the target expression{indentExpr lhs}"
|
||
-- construct rewrite proof
|
||
let eNew := eAbst.instantiate1 rhs
|
||
let eNew ← instantiateMVars eNew
|
||
let eNew ← if rhs.hasBinderNameHint then eNew.resolveBinderNameHint else pure eNew
|
||
let eType ← inferType e
|
||
let motive := Lean.mkLambda `_a BinderInfo.default α eAbst
|
||
try
|
||
check motive
|
||
catch ex =>
|
||
throwTacticEx `rewrite mvarId m!"\
|
||
motive is not type correct:{indentD motive}\nError: {ex.toMessageData}\
|
||
\n\n\
|
||
Explanation: The rewrite tactic rewrites an expression 'e' using an equality 'a = b' by the following process. \
|
||
First, it looks for all 'a' in 'e'. Second, it tries to abstract these occurrences of 'a' to create a function 'm := fun _a => ...', called the *motive*, \
|
||
with the property that 'm a' is definitionally equal to 'e'. \
|
||
Third, we observe that '{.ofConstName ``congrArg}' implies that 'm a = m b', which can be used with lemmas such as '{.ofConstName ``Eq.mpr}' to change the goal. \
|
||
However, if 'e' depends on specific properties of 'a', then the motive 'm' might not typecheck.\
|
||
\n\n\
|
||
Possible solutions: use rewrite's 'occs' configuration option to limit which occurrences are rewritten, \
|
||
or use 'simp' or 'conv' mode, which have strategies for certain kinds of dependencies \
|
||
(these tactics can handle proofs and '{.ofConstName ``Decidable}' instances whose types depend on the rewritten term, \
|
||
and 'simp' can apply user-defined '@[congr]' theorems as well)."
|
||
unless (← withLocalDeclD `_a α fun a => do isDefEq (← inferType (eAbst.instantiate1 a)) eType) do
|
||
-- NB: using motive.arrow? would disallow motives where the dependency
|
||
-- can be reduced away
|
||
throwTacticEx `rewrite mvarId m!"motive is dependent{indentD motive}"
|
||
let u1 ← getLevel α
|
||
let u2 ← getLevel eType
|
||
let eqPrf := mkApp6 (.const ``congrArg [u1, u2]) α eType lhs rhs motive heq
|
||
postprocessAppMVars `rewrite mvarId newMVars binderInfos
|
||
(synthAssignedInstances := !tactic.skipAssignedInstances.get (← getOptions))
|
||
let newMVarIds ← newMVars.map Expr.mvarId! |>.filterM fun mvarId => not <$> mvarId.isAssigned
|
||
let otherMVarIds ← getMVarsNoDelayed heqIn
|
||
let otherMVarIds := otherMVarIds.filter (!newMVarIds.contains ·)
|
||
let newMVarIds := newMVarIds ++ otherMVarIds
|
||
pure { eNew := eNew, eqProof := eqPrf, mvarIds := newMVarIds.toList }
|
||
match symm with
|
||
| false => cont heq heqType lhs rhs
|
||
| true => do
|
||
let heq ← mkEqSymm heq
|
||
let heqType ← mkEq rhs lhs
|
||
cont heq heqType rhs lhs
|
||
match heqType.iff? with
|
||
| some (lhs, rhs) =>
|
||
let heqType ← mkEq lhs rhs
|
||
let heq := mkApp3 (mkConst `propext) lhs rhs heq
|
||
cont heq heqType
|
||
| none =>
|
||
cont heq heqType
|
||
|
||
end Lean.Meta
|