lean4-htt/src/Lean/Meta/Tactic/Rewrite.lean
Joachim Breitner dc001a01e5
feat: binderNameHint (#6947)
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.
2025-02-06 11:03:27 +00:00

94 lines
4.7 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
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