lean4-htt/src/Lean/Meta/Tactic/Rfl.lean
Scott Morrison 02c5700c63
feat: change apply_rfl tactic so that it does not operate on = (#3784)
Previously:

If the `rfl` macro was going to fail, it would:
1. expand to `eq_refl`, which is implemented by
`Lean.Elab.Tactic.evalRefl`, and call `Lean.MVarId.refl` which would:
* either try kernel defeq (if in `.default` or `.all` transparency mode)
  * otherwise try `IsDefEq`
  * then fail.
2. Next expand to the `apply_rfl` tactic, which is implemented by
`Lean.Elab.Tactic.Rfl.evalApplyRfl`, and call `Lean.MVarId.applyRefl`
which would look for lemmas labelled `@[refl]`, and unfortunately in
Mathlib find `Eq.refl`, so try applying that (resulting in another
`IsDefEq`)
3. Because of an accidental duplication, if `Lean.Elab.Tactic.Rfl` was
imported, it would *again* expand to `apply_rfl`.

Now:
1. Same behaviour in `eq_refl`.
2. The `@[refl]` attribute will reject `Eq.refl`, and `MVarId.applyRefl`
will fail when applied to equality goals.
3. The duplication has been removed.
2024-03-27 12:04:22 +00:00

110 lines
4 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) 2022 Newell Jensen. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Newell Jensen, Thomas Murrills
-/
prelude
import Lean.Meta.Tactic.Apply
import Lean.Elab.Tactic.Basic
import Lean.Meta.Tactic.Refl
/-!
# `rfl` tactic extension for reflexive relations
This extends the `rfl` tactic so that it works on any reflexive relation,
provided the reflexivity lemma has been marked as `@[refl]`.
-/
namespace Lean.Meta.Rfl
open Lean Meta
/-- Discrimation tree settings for the `refl` extension. -/
def reflExt.config : WhnfCoreConfig := {}
/-- Environment extensions for `refl` lemmas -/
initialize reflExt :
SimpleScopedEnvExtension (Name × Array DiscrTree.Key) (DiscrTree Name) ←
registerSimpleScopedEnvExtension {
addEntry := fun dt (n, ks) => dt.insertCore ks n
initial := {}
}
initialize registerBuiltinAttribute {
name := `refl
descr := "reflexivity relation"
add := fun decl _ kind => MetaM.run' do
let declTy := (← getConstInfo decl).type
let (_, _, targetTy) ← withReducible <| forallMetaTelescopeReducing declTy
let fail := throwError
"@[refl] attribute only applies to lemmas proving x x, got {declTy}"
let .app (.app rel lhs) rhs := targetTy | fail
if let .app (.const ``Eq [_]) _ := rel then
throwError "@[refl] attribute may not be used on `Eq.refl`."
unless ← withNewMCtxDepth <| isDefEq lhs rhs do fail
let key ← DiscrTree.mkPath rel reflExt.config
reflExt.add (decl, key) kind
}
open Elab Tactic
/-- `MetaM` version of the `rfl` tactic.
This tactic applies to a goal whose target has the form `x ~ x`,
where `~` is a reflexive relation other than `=`,
that is, a relation which has a reflexive lemma tagged with the attribute @[refl].
-/
def _root_.Lean.MVarId.applyRfl (goal : MVarId) : MetaM Unit := do
let .app (.app rel _) _ ← whnfR <|← instantiateMVars <|← goal.getType
| throwError "reflexivity lemmas only apply to binary relations, not{
indentExpr (← goal.getType)}"
if let .app (.const ``Eq [_]) _ := rel then
throwError "MVarId.applyRfl does not solve `=` goals. Use `MVarId.refl` instead."
else
let s ← saveState
let mut ex? := none
for lem in ← (reflExt.getState (← getEnv)).getMatch rel reflExt.config do
try
let gs ← goal.apply (← mkConstWithFreshMVarLevels lem)
if gs.isEmpty then return () else
logError <| MessageData.tagged `Tactic.unsolvedGoals <| m!"unsolved goals\n{
goalsToMessageData gs}"
catch e =>
ex? := ex? <|> (some (← saveState, e)) -- stash the first failure of `apply`
s.restore
if let some (sErr, e) := ex? then
sErr.restore
throw e
else
throwError "rfl failed, no lemma with @[refl] applies"
/-- Helper theorem for `Lean.MVarId.liftReflToEq`. -/
private theorem rel_of_eq_and_refl {α : Sort _} {R : αα → Prop}
{x y : α} (hxy : x = y) (h : R x x) : R x y :=
hxy ▸ h
/--
Convert a goal of the form `x ~ y` into the form `x = y`, where `~` is a reflexive
relation, that is, a relation which has a reflexive lemma tagged with the attribute `@[refl]`.
If this can't be done, returns the original `MVarId`.
-/
def _root_.Lean.MVarId.liftReflToEq (mvarId : MVarId) : MetaM MVarId := do
mvarId.checkNotAssigned `liftReflToEq
let .app (.app rel _) _ ← withReducible mvarId.getType' | return mvarId
if rel.isAppOf `Eq then
-- No need to lift Eq to Eq
return mvarId
for lem in ← (reflExt.getState (← getEnv)).getMatch rel reflExt.config do
let res ← observing? do
-- First create an equality relating the LHS and RHS
-- and reduce the goal to proving that LHS is related to LHS.
let [mvarIdEq, mvarIdR] ← mvarId.apply (← mkConstWithFreshMVarLevels ``rel_of_eq_and_refl)
| failure
-- Then fill in the proof of the latter by reflexivity.
let [] ← mvarIdR.apply (← mkConstWithFreshMVarLevels lem) | failure
return mvarIdEq
if let some mvarIdEq := res then
return mvarIdEq
return mvarId
end Lean.Meta.Rfl