feat: MVarId.assertAfter fvar alias info, MVarId.replace mvar dependencies, specialize tactic using eta arguments (#13528)
This PR gives the `specialize` tactic the ability to instantiate universal quantifiers other than the first using `specialize h (y := v)` syntax. It also fixes an issue where `MVarId.assertAfter` did not record variable alias information, and an issue where `MVarId.replace` and `MVarId.replaceLocalDecl` did not take metavariables into account when calculating dependencies. Additionally it fixes some uninstantiated metavariables bugs, including one in the Infoview tactic state hypothesis diff. The `specialize` tactic now uses `Lean.MVarId.replace` to simplify the implementation, and as a consequence it tries to keep the specialized hypothesis close to its original spot in the local context. Additional metaprogramming API: - `Lean.Expr.getLambdaBody` to accompany `Lean.Expr.getNumHeadLambdas` - `Lean.LocalContext.setType`, `Lean.MetavarContext.setFVarType`, `Lean.MVarId.setFVarType` - `Lean.MVarId.assertAfter'` to assert a new hypothesis as early as possibly in the context where it is well-formed, as a frontend to `Lean.MVarId.assertAfter`, which assumes the new hypothesis is well-formed Breaking change: metaprograms cannot assume that `MVarId`s change if metavariables are assigned. For example, the `change` tactic will no longer change `MVarId`s if the only effect is incidental metavariable assignments. Mathlib impact: this revealed many `dsimp`s that did nothing and could be deleted. Closes #9893
This commit is contained in:
parent
ad5a3291dc
commit
19baa470e5
26 changed files with 251 additions and 126 deletions
|
|
@ -138,9 +138,7 @@ theorem getElem_zero_flatten.proof {xss : Array (Array α)} (h : 0 < xss.flatten
|
|||
@[grind =]
|
||||
theorem getElem_zero_flatten {xss : Array (Array α)} (h) :
|
||||
(flatten xss)[0] = (xss.findSome? fun xs => xs[0]?).get (getElem_zero_flatten.proof h) := by
|
||||
have t := getElem?_zero_flatten xss
|
||||
simp at t
|
||||
simp [← t]
|
||||
simp [← getElem?_zero_flatten xss]
|
||||
|
||||
@[grind =]
|
||||
theorem findSome?_replicate : findSome? f (replicate n a) = if n = 0 then none else f a := by
|
||||
|
|
|
|||
|
|
@ -1518,7 +1518,6 @@ theorem sdiv_ne_intMin_of_ne_intMin {x y : BitVec w} (h : x ≠ intMin w) :
|
|||
simp only [sdiv, udiv_eq, neg_eq]
|
||||
by_cases hx : x.msb <;> by_cases hy : y.msb
|
||||
<;> simp only [hx, hy, neg_ne_intMin_inj]
|
||||
<;> simp only [Bool.not_eq_true] at hx hy
|
||||
<;> apply ne_intMin_of_msb_eq_false (by omega)
|
||||
<;> rw [msb_udiv]
|
||||
<;> try simp only [hx, Bool.false_and]
|
||||
|
|
@ -1588,8 +1587,7 @@ theorem toInt_sdiv_of_ne_or_ne (a b : BitVec w) (h : a ≠ intMin w ∨ b ≠ -1
|
|||
have := Nat.two_pow_pos (w - 1)
|
||||
|
||||
by_cases hbintMin : b = intMin w
|
||||
· simp only at hbintMin
|
||||
subst hbintMin
|
||||
· subst hbintMin
|
||||
have toIntA_lt := @BitVec.toInt_lt w a; norm_cast at toIntA_lt
|
||||
have le_toIntA := @BitVec.le_toInt w a; norm_cast at le_toIntA
|
||||
simp only [sdiv_intMin, toInt_intMin, wpos,
|
||||
|
|
@ -1607,7 +1605,7 @@ theorem toInt_sdiv_of_ne_or_ne (a b : BitVec w) (h : a ≠ intMin w ∨ b ≠ -1
|
|||
omega
|
||||
|
||||
· by_cases ha : a.msb <;> by_cases hb : b.msb
|
||||
<;> simp only [not_eq_true] at ha hb
|
||||
<;> (try simp only [not_eq_true] at ha hb)
|
||||
· simp only [sdiv_eq, ha, hb, udiv_eq]
|
||||
rw [toInt_eq_neg_toNat_neg_of_nonpos (x := a) (by simp [ha]),
|
||||
toInt_eq_neg_toNat_neg_of_nonpos (x := b) (by simp [hb]),
|
||||
|
|
|
|||
|
|
@ -4625,8 +4625,6 @@ theorem toInt_ediv_toInt_lt_of_nonpos_of_lt_neg_one {w : Nat} {x y : BitVec w} (
|
|||
rcases hx' with hx'|hx'|hx'
|
||||
· simp [hx']; omega
|
||||
· have := BitVec.neg_one_ediv_toInt_eq (y := y)
|
||||
simp only [
|
||||
Int.reduceNeg] at this
|
||||
simp [hx', this]
|
||||
omega
|
||||
· have := Int.ediv_lt_natAbs_self_of_lt_neg_one_of_lt_neg_one (x := x.toInt) (y := y.toInt) (by omega) hy
|
||||
|
|
|
|||
|
|
@ -636,7 +636,6 @@ private theorem eq_of_norm_eq_of_divCoeffs {ctx : Context} {p₁ p₂ : Poly} {k
|
|||
have hz : k ≠ 0 := Int.ne_of_gt h₀
|
||||
replace h₁ := Poly.denote_div_eq_of_divCoeffs ctx p₁ k h₁
|
||||
replace h₂ := congrArg (Poly.denote ctx) h₂
|
||||
simp at h₂
|
||||
rw [h₂, ← h₁]; clear h₁ h₂
|
||||
apply mul_add_cmod_le_iff
|
||||
assumption
|
||||
|
|
|
|||
|
|
@ -970,7 +970,6 @@ theorem eq_push_append_of_mem {xs : Vector α n} {x : α} (h : x ∈ xs) :
|
|||
xs = (as.push x ++ bs).cast h ∧ x ∉ as:= by
|
||||
rcases xs with ⟨xs, rfl⟩
|
||||
obtain ⟨as, bs, h, w⟩ := Array.eq_push_append_of_mem (by simpa using h)
|
||||
simp only at h
|
||||
obtain rfl := h
|
||||
exact ⟨_, _, as.toVector, bs.toVector, by simp, by simp, by simpa using w⟩
|
||||
|
||||
|
|
|
|||
|
|
@ -359,7 +359,7 @@ instance (priority := high) {p} [Semiring α] [AddRightCancel α] [IsCharP α p]
|
|||
replace h := AddRightCancel.add_right_cancel _ _ _ h
|
||||
simp [Semiring.ofNat_eq_natCast, h]
|
||||
have := IsCharP.ofNat_ext_iff p |>.mp h
|
||||
simp at this; assumption
|
||||
exact this
|
||||
next =>
|
||||
intro h
|
||||
have := IsCharP.ofNat_ext_iff (α := α) p |>.mpr h
|
||||
|
|
|
|||
|
|
@ -143,7 +143,6 @@ theorem monotone_mapM (f : γ → α → m β) (xs : List α) (hmono : monotone
|
|||
apply hmono
|
||||
· apply monotone_of_monotone_apply
|
||||
intro y
|
||||
dsimp
|
||||
generalize [y] = ys
|
||||
induction xs generalizing ys with
|
||||
| nil => apply monotone_const
|
||||
|
|
@ -481,8 +480,7 @@ theorem monotone_mapFinIdxM (xs : Array α) (f : γ → (i : Nat) → α → i <
|
|||
apply monotone_const
|
||||
case case2 ih =>
|
||||
apply monotone_bind
|
||||
· dsimp
|
||||
apply monotone_apply
|
||||
· apply monotone_apply
|
||||
apply monotone_apply
|
||||
apply monotone_apply
|
||||
apply hmono
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@ theorem ofNat_sub_dichotomy {a b : Nat} :
|
|||
by_cases h : b ≤ a
|
||||
· left
|
||||
have t := Int.ofNat_sub h
|
||||
simp at t
|
||||
exact ⟨h, t⟩
|
||||
· right
|
||||
have t := Nat.not_le.mp h
|
||||
|
|
|
|||
|
|
@ -1163,16 +1163,6 @@ and less messy than commenting the remainder of the proof.
|
|||
-/
|
||||
macro "stop" tacticSeq : tactic => `(tactic| repeat sorry)
|
||||
|
||||
/--
|
||||
The tactic `specialize h a₁ ... aₙ` works on local hypothesis `h`.
|
||||
The premises of this hypothesis, either universal quantifications or
|
||||
non-dependent implications, are instantiated by concrete terms coming
|
||||
from arguments `a₁` ... `aₙ`.
|
||||
The tactic adds a new hypothesis with the same name `h := h a₁ ... aₙ`
|
||||
and tries to clear the previous one.
|
||||
-/
|
||||
syntax (name := specialize) "specialize " term : tactic
|
||||
|
||||
/--
|
||||
`unhygienic tacs` runs `tacs` with name hygiene disabled.
|
||||
This means that tactics that would normally create inaccessible names will instead
|
||||
|
|
@ -1260,8 +1250,8 @@ macro "nomatch " es:term,+ : tactic =>
|
|||
`(tactic| exact nomatch $es:term,*)
|
||||
|
||||
/--
|
||||
Acts like `have`, but removes a hypothesis with the same name as
|
||||
this one if possible. For example, if the state is:
|
||||
`replace h := e` is like `have h := e`, but it removes a previous hypothesis
|
||||
of the same name as this one if possible. For example, if the state is:
|
||||
|
||||
```lean
|
||||
f : α → β
|
||||
|
|
@ -1286,10 +1276,29 @@ h : β
|
|||
⊢ goal
|
||||
```
|
||||
|
||||
This can be used to simulate the `specialize` and `apply at` tactics of Coq.
|
||||
The tactic `specialize h a₁ ... aₙ` is a way to write `replace h := h a₁ ... aₙ`,
|
||||
automatically inferring which hypothesis should be replaced.
|
||||
|
||||
The `replace` tactic can be used to simulate Rocq's `apply at` tactic.
|
||||
-/
|
||||
syntax (name := replace) "replace" letDecl : tactic
|
||||
|
||||
/--
|
||||
`specialize h a₁ ... aₙ` is equivalent to `replace h := h a₁ ... aₙ`.
|
||||
It specializes the local hypothesis `h` by instantiating
|
||||
universal quantifications and implications using the concrete terms `a₁` ... `aₙ`.
|
||||
The tactic adds a new hypothesis with the same name and tries to remove
|
||||
the original `h` if possible.
|
||||
|
||||
Example: given `h : ∀ (n : Nat), p n → q n` and `h' : p 2`,
|
||||
then `specialize h 2 h'` replaces `h` with `h : q 2`.
|
||||
|
||||
The tactic also supports instantiating particular universal quantifiers
|
||||
using named argument syntax. Example: given `h : ∀ (m n : Nat), p m n`,
|
||||
then `specialize h (n := 2)` replaces `h` with `h : ∀ (m : Nat), p m 2`.
|
||||
-/
|
||||
syntax (name := specialize) "specialize " term : tactic
|
||||
|
||||
/-- `and_intros` applies `And.intro` until it does not make progress. -/
|
||||
syntax "and_intros" : tactic
|
||||
macro_rules | `(tactic| and_intros) => `(tactic| repeat' refine And.intro ?_ ?_)
|
||||
|
|
|
|||
|
|
@ -420,7 +420,6 @@ where
|
|||
for h : idx in 0...chunks do
|
||||
have : idx * 8 + 7 < scalarArgs.size := by
|
||||
have : idx < scalarArgs.size / 8 := Std.Rco.lt_upper_of_mem h
|
||||
simp at this
|
||||
omega
|
||||
let b1 := scalarArgs[idx * 8]
|
||||
let b2 := scalarArgs[idx * 8 + 1]
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ target.
|
|||
-/
|
||||
def convClear (mvarId : MVarId) (fvarId : FVarId) : MetaM MVarId := do
|
||||
let (lhs, rhs) ← getLhsRhsCore mvarId
|
||||
let rhs ← instantiateMVars rhs
|
||||
unless rhs.isMVar do
|
||||
return (← mvarId.clear fvarId)
|
||||
let rhsKind ← rhs.mvarId!.getKind
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ module
|
|||
|
||||
prelude
|
||||
public import Lean.Elab.Tactic.Basic
|
||||
public import Lean.Meta.Tactic.Cleanup
|
||||
import Lean.Meta.Native
|
||||
import Lean.Elab.Tactic.ElabTerm
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ module
|
|||
|
||||
prelude
|
||||
public import Lean.Meta.Tactic.Constructor
|
||||
public import Lean.Meta.Tactic.Assert
|
||||
public import Lean.Meta.Tactic.Cleanup
|
||||
public import Lean.Meta.Tactic.Replace
|
||||
public import Lean.Meta.Tactic.Rename
|
||||
public import Lean.Elab.Tactic.Config
|
||||
|
||||
|
|
@ -234,13 +233,10 @@ def refineCore (stx : Syntax) (tagSuffix : Name) (allowNaturalHoles : Bool) : Ta
|
|||
match stx with
|
||||
| `(tactic| specialize $e:term) =>
|
||||
let (e, mvarIds') ← elabTermWithHoles e none `specialize (allowNaturalHoles := true)
|
||||
let h := e.getAppFn
|
||||
if h.isFVar then
|
||||
let localDecl ← h.fvarId!.getDecl
|
||||
let mvarId ← (← getMainGoal).assert localDecl.userName (← inferType e).headBeta e
|
||||
let (_, mvarId) ← mvarId.intro1P
|
||||
let mvarId ← mvarId.tryClear h.fvarId!
|
||||
replaceMainGoal (mvarIds' ++ [mvarId])
|
||||
if let Expr.fvar fvarId := e.getLambdaBody.getAppFn then
|
||||
let mvarId ← getMainGoal
|
||||
let result ← mvarId.replace fvarId e (← inferType e).headBeta
|
||||
replaceMainGoal (mvarIds' ++ [result.mvarId])
|
||||
else
|
||||
throwError "'specialize' requires a term of the form `h x_1 .. x_n` where `h` appears in the local context"
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
|
|
|||
|
|
@ -1621,6 +1621,15 @@ def getNumHeadLambdas : Expr → Nat
|
|||
| .mdata _ b => getNumHeadLambdas b
|
||||
| _ => 0
|
||||
|
||||
/--
|
||||
Returns the "body" of a nested lambda expression, like in `Expr.getForallBody`.
|
||||
The number of skipped lambdas is given by `Expr.getNumHeadLambdas`.
|
||||
-/
|
||||
def getLambdaBody : Expr → Expr
|
||||
| .lam _ _ b _ => getLambdaBody b
|
||||
| .mdata _ b => getLambdaBody b
|
||||
| e => e
|
||||
|
||||
/--
|
||||
Return true if the given expression is the function of an expression that is target for (head) beta reduction.
|
||||
If `useZeta = true`, then `let`-expressions are visited. That is, it assumes
|
||||
|
|
|
|||
|
|
@ -476,6 +476,9 @@ def setKind (lctx : LocalContext) (fvarId : FVarId)
|
|||
def setBinderInfo (lctx : LocalContext) (fvarId : FVarId) (bi : BinderInfo) : LocalContext :=
|
||||
modifyLocalDecl lctx fvarId fun decl => decl.setBinderInfo bi
|
||||
|
||||
def setType (lctx : LocalContext) (fvarId : FVarId) (type : Expr) : LocalContext :=
|
||||
modifyLocalDecl lctx fvarId fun decl => decl.setType type
|
||||
|
||||
@[export lean_local_ctx_num_indices]
|
||||
def numIndices (lctx : LocalContext) : Nat :=
|
||||
lctx.decls.size
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ prelude
|
|||
public import Lean.Meta.Tactic.FVarSubst
|
||||
public import Lean.Meta.Tactic.Intro
|
||||
public import Lean.Meta.Tactic.Revert
|
||||
public import Lean.Elab.InfoTree.Main
|
||||
public import Lean.Util.ForEachExpr
|
||||
import Lean.Meta.AppBuilder
|
||||
|
||||
|
|
@ -78,11 +79,51 @@ def _root_.Lean.MVarId.assertAfter (mvarId : MVarId) (fvarId : FVarId) (userName
|
|||
let mvarId ← mvarId.assert userName type val
|
||||
let (fvarIdNew, mvarId) ← mvarId.intro1P
|
||||
let (fvarIdsNew, mvarId) ← mvarId.introNP fvarIds.size
|
||||
let lctx := (← mvarId.getDecl).lctx
|
||||
let mut subst := {}
|
||||
for f in fvarIds, fNew in fvarIdsNew do
|
||||
subst := subst.insert f (mkFVar fNew)
|
||||
Elab.pushInfoLeaf (.ofFVarAliasInfo { id := fNew, baseId := f, userName := (lctx.get! fNew).userName })
|
||||
return { fvarId := fvarIdNew, mvarId, subst }
|
||||
|
||||
/--
|
||||
Like `Lean.MVarId.assertAfter`, but asserts the new hypothesis at the earliest point after `fvarId`
|
||||
where `type` is well-formed. Note that `val` may depend on any variables in the local context.
|
||||
|
||||
The expression `type` may contain metavariables, and this procedure ensures they are well-formed
|
||||
at the point in the local context where the hypothesis is asserted.
|
||||
The metavariables in `type` are instantiated to avoid false dependencies.
|
||||
-/
|
||||
def _root_.Lean.MVarId.assertAfter' (mvarId : MVarId) (fvarId : FVarId) (userName : Name) (type : Expr) (val : Expr) :
|
||||
MetaM AssertAfterResult :=
|
||||
mvarId.withContext do
|
||||
-- Instantiate metavariables, which possibly allows the asserted hypothesis to appear earlier.
|
||||
-- Assigned metavariables can create false dependences on variables.
|
||||
let type ← instantiateMVars type
|
||||
-- `type` may contain variables that occur after `fvarId`.
|
||||
-- Thus, we use the auxiliary function `findMaxFVar` to ensure `type` is well-formed
|
||||
-- at the position we are inserting it.
|
||||
let (_, ldecl') ← findMaxFVar type |>.run (← fvarId.getDecl)
|
||||
mvarId.assertAfter ldecl'.fvarId userName type val
|
||||
where
|
||||
/-- Finds the `LocalDecl` for the FVar in `e` with the highest index. -/
|
||||
findMaxFVar (e : Expr) : StateRefT LocalDecl MetaM Unit :=
|
||||
e.forEach' fun e => do
|
||||
if let Expr.fvar fvarId' := e then
|
||||
visitLocalDecl (← fvarId'.getDecl)
|
||||
return false
|
||||
else if let Expr.mvar mvarId' := e then
|
||||
-- Metavariables need to appear after all local variables appearing in their own local contexts
|
||||
let lctx' := (← mvarId'.getDecl).lctx
|
||||
lctx'.forM fun ldecl' => do
|
||||
-- We need the corresponding `LocalDecl` from the current context, to get the correct `index`
|
||||
visitLocalDecl (← ldecl'.fvarId.getDecl)
|
||||
return false
|
||||
else
|
||||
return e.hasFVar || e.hasExprMVar
|
||||
visitLocalDecl (ldecl' : LocalDecl) : StateRefT LocalDecl MetaM Unit :=
|
||||
modify fun ldecl => if ldecl'.index > ldecl.index then ldecl' else ldecl
|
||||
|
||||
structure Hypothesis where
|
||||
userName : Name
|
||||
type : Expr
|
||||
|
|
@ -117,34 +158,4 @@ def _root_.Lean.MVarId.assertHypotheses (mvarId : MVarId) (hs : Array Hypothesis
|
|||
pure lctx
|
||||
return (fvarIds, mvarId)
|
||||
|
||||
/--
|
||||
Replace hypothesis `hyp` in goal `g` with `proof : typeNew`.
|
||||
The new hypothesis is given the same user name as the original,
|
||||
it attempts to avoid reordering hypotheses, and the original is cleared if possible.
|
||||
-/
|
||||
-- adapted from Lean.Meta.replaceLocalDeclCore
|
||||
def _root_.Lean.MVarId.replace (g : MVarId) (hyp : FVarId) (proof : Expr) (typeNew : Option Expr := none) :
|
||||
MetaM AssertAfterResult :=
|
||||
g.withContext do
|
||||
let typeNew ← match typeNew with
|
||||
| some t => pure t
|
||||
| none => inferType proof
|
||||
let ldecl ← hyp.getDecl
|
||||
-- `typeNew` may contain variables that occur after `hyp`.
|
||||
-- Thus, we use the auxiliary function `findMaxFVar` to ensure `typeNew` is well-formed
|
||||
-- at the position we are inserting it.
|
||||
let (_, ldecl') ← findMaxFVar typeNew |>.run ldecl
|
||||
let result ← g.assertAfter ldecl'.fvarId ldecl.userName typeNew proof
|
||||
(return { result with mvarId := ← result.mvarId.clear hyp }) <|> pure result
|
||||
where
|
||||
/-- Finds the `LocalDecl` for the FVar in `e` with the highest index. -/
|
||||
findMaxFVar (e : Expr) : StateRefT LocalDecl MetaM Unit :=
|
||||
e.forEach' fun e => do
|
||||
if e.isFVar then
|
||||
let ldecl' ← e.fvarId!.getDecl
|
||||
modify fun ldecl => if ldecl'.index > ldecl.index then ldecl' else ldecl
|
||||
return false
|
||||
else
|
||||
return e.hasFVar
|
||||
|
||||
end Lean.Meta
|
||||
|
|
|
|||
|
|
@ -49,35 +49,39 @@ def _root_.Lean.MVarId.replaceTargetDefEq (mvarId : MVarId) (targetNew : Expr) :
|
|||
if Expr.equal target targetNew then
|
||||
return mvarId
|
||||
else
|
||||
let tag ← mvarId.getTag
|
||||
let mvarNew ← mkFreshExprSyntheticOpaqueMVar targetNew tag
|
||||
let newVal ← mkExpectedTypeHint mvarNew target
|
||||
mvarId.assign newVal
|
||||
return mvarNew.mvarId!
|
||||
|
||||
private def replaceLocalDeclCore (mvarId : MVarId) (fvarId : FVarId) (typeNew : Expr) (eqProof : Expr) : MetaM AssertAfterResult :=
|
||||
mvarId.withContext do
|
||||
let localDecl ← fvarId.getDecl
|
||||
let typeNewPr ← mkEqMP eqProof (mkFVar fvarId)
|
||||
/- `typeNew` may contain variables that occur after `fvarId`.
|
||||
Thus, we use the auxiliary function `findMaxFVar` to ensure `typeNew` is well-formed at the
|
||||
position we are inserting it.
|
||||
We must `instantiateMVars` first to ensure that there is no mvar in `typeNew` which is
|
||||
assigned to some later-occurring fvar. -/
|
||||
let (_, localDecl') ← findMaxFVar (← instantiateMVars typeNew) |>.run localDecl
|
||||
let result ← mvarId.assertAfter localDecl'.fvarId localDecl.userName typeNew typeNewPr
|
||||
(do let mvarIdNew ← result.mvarId.clear fvarId
|
||||
pure { result with mvarId := mvarIdNew })
|
||||
<|> pure result
|
||||
where
|
||||
findMaxFVar (e : Expr) : StateRefT LocalDecl MetaM Unit :=
|
||||
e.forEach' fun e => do
|
||||
if e.isFVar then
|
||||
let localDecl' ← e.fvarId!.getDecl
|
||||
modify fun localDecl => if localDecl'.index > localDecl.index then localDecl' else localDecl
|
||||
return false
|
||||
-- For an accurate `Expr.equal` check, we need to instantiate metavariables.
|
||||
-- Some tactics depend on this returning the same metavariable to check that they made no progress.
|
||||
let target ← instantiateMVars target
|
||||
let targetNew ← instantiateMVars targetNew
|
||||
if Expr.equal target targetNew then
|
||||
mvarId.setType target -- cache the instantiated type
|
||||
return mvarId
|
||||
else
|
||||
return e.hasFVar
|
||||
let tag ← mvarId.getTag
|
||||
let mvarNew ← mkFreshExprSyntheticOpaqueMVar targetNew tag
|
||||
let newVal ← mkExpectedTypeHint mvarNew target
|
||||
mvarId.assign newVal
|
||||
return mvarNew.mvarId!
|
||||
|
||||
/--
|
||||
Given a hypothesis `fvarId` named `h`, asserts a new hypothesis `h : type`
|
||||
as soon after `fvarId` as possible, then tries to clear `h`. This is useful to avoid
|
||||
reordering hypotheses.
|
||||
|
||||
The new hypothesis is asserted after any local variables that `type` depends on, and it may contain
|
||||
metavariables. The value `val : type` can depend on any variables in the local context.
|
||||
|
||||
See also `Lean.MVarId.assertAfter'`, which asserts hypotheses without clearing.
|
||||
-/
|
||||
def _root_.Lean.MVarId.replace
|
||||
(mvarId : MVarId) (fvarId : FVarId) (val : Expr) (type? : Option Expr := none) (userName? : Option Name := none) :
|
||||
MetaM AssertAfterResult :=
|
||||
mvarId.withContext do
|
||||
let type ← type?.getDM (inferType val)
|
||||
let userName ← userName?.getDM (LocalDecl.userName <$> fvarId.getDecl)
|
||||
let result ← mvarId.assertAfter' fvarId userName type val
|
||||
let mvarId ← result.mvarId.tryClear fvarId
|
||||
return { result with mvarId }
|
||||
|
||||
/--
|
||||
Replace type of the local declaration with id `fvarId` with one with the same user-facing name, but with type `typeNew`.
|
||||
|
|
@ -89,35 +93,43 @@ where
|
|||
well-formed. That is, if `typeNew` involves declarations which occur later than `fvarId` in the
|
||||
local context, the new local declaration will be inserted immediately after the latest-occurring
|
||||
one. Otherwise, it will be inserted immediately after `fvarId`.
|
||||
|
||||
Note: `replaceLocalDecl` should not be used when unassigned pending mvars might be present in
|
||||
`typeNew`, as these may later be synthesized to fvars which occur after `fvarId` (by e.g.
|
||||
`Term.withSynthesize` or `Term.synthesizeSyntheticMVars`) .
|
||||
-/
|
||||
@[inline] def _root_.Lean.MVarId.replaceLocalDecl (mvarId : MVarId) (fvarId : FVarId) (typeNew : Expr) (eqProof : Expr) : MetaM AssertAfterResult :=
|
||||
replaceLocalDeclCore mvarId fvarId typeNew eqProof
|
||||
mvarId.withContext do
|
||||
let typeNewPr ← mkEqMP eqProof (mkFVar fvarId)
|
||||
mvarId.replace fvarId typeNewPr typeNew
|
||||
|
||||
/--
|
||||
Replaces the type of `fvarId` at `mvarId` with `typeNew`.
|
||||
Remark: this method assumes that `typeNew` is definitionally equal to the current type of `fvarId`.
|
||||
Remark: this method assumes that `typeNew` is definitionally equal to the current type of `fvarId`,
|
||||
and that `typeNew` is well-formed at this position.
|
||||
|
||||
If `typeNew` is equal to current type of `fvarId`, then returns `mvarId` unchanged.
|
||||
Uses `Expr.equal` for the comparison so that it is possible to update binder names, etc., which are user-visible.
|
||||
-/
|
||||
def _root_.Lean.MVarId.replaceLocalDeclDefEq (mvarId : MVarId) (fvarId : FVarId) (typeNew : Expr) : MetaM MVarId := do
|
||||
mvarId.withContext do
|
||||
if Expr.equal typeNew (← fvarId.getType) then
|
||||
let type ← fvarId.getType
|
||||
if Expr.equal type typeNew then
|
||||
return mvarId
|
||||
else
|
||||
let mvarDecl ← mvarId.getDecl
|
||||
let lctxNew := (← getLCtx).modifyLocalDecl fvarId (·.setType typeNew)
|
||||
withLCtx' lctxNew do
|
||||
-- `typeNew` might not be defeq to the old type at reducible transparency (e.g. a definition was unfolded)
|
||||
-- so it might now be recognized as an instance.
|
||||
withLocalInstances [lctxNew.get! fvarId] do
|
||||
let mvarNew ← mkFreshExprMVar mvarDecl.type mvarDecl.kind mvarDecl.userName
|
||||
mvarId.assign mvarNew
|
||||
return mvarNew.mvarId!
|
||||
-- For an accurate `Expr.equal` check, we need to instantiate metavariables.
|
||||
-- Some tactics depend on this returning the same metavariable to check that they made no progress.
|
||||
let type ← instantiateMVars type
|
||||
let typeNew ← instantiateMVars typeNew
|
||||
if Expr.equal type typeNew then
|
||||
mvarId.setFVarType fvarId type -- cache the instantiated type
|
||||
return mvarId
|
||||
else
|
||||
let lctxNew := (← getLCtx).setType fvarId typeNew
|
||||
withLCtx' lctxNew do
|
||||
-- `typeNew` might not be defeq to the old type at reducible transparency (e.g. a definition was unfolded)
|
||||
-- so it might now be recognized as an instance.
|
||||
withLocalInstances [lctxNew.get! fvarId] do
|
||||
let mvarDecl ← mvarId.getDecl
|
||||
let mvarNew ← mkFreshExprMVar mvarDecl.type mvarDecl.kind mvarDecl.userName
|
||||
mvarId.assign mvarNew
|
||||
return mvarNew.mvarId!
|
||||
|
||||
/--
|
||||
Replace the target type of `mvarId` with `typeNew`.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Authors: Siddhartha Gadgil, Mario Carneiro, Kim Morrison
|
|||
module
|
||||
prelude
|
||||
public import Lean.Meta.Reduce
|
||||
public import Lean.Meta.Tactic.Assert
|
||||
public import Lean.Meta.Tactic.Replace
|
||||
public import Lean.Meta.DiscrTree.Main
|
||||
import Lean.Meta.AppBuilder
|
||||
public section
|
||||
|
|
|
|||
|
|
@ -911,6 +911,10 @@ def setFVarBinderInfo (mctx : MetavarContext) (mvarId : MVarId)
|
|||
(fvarId : FVarId) (bi : BinderInfo) : MetavarContext :=
|
||||
mctx.modifyExprMVarLCtx mvarId (·.setBinderInfo fvarId bi)
|
||||
|
||||
def setFVarType (mctx : MetavarContext) (mvarId : MVarId)
|
||||
(fvarId : FVarId) (type : Expr) : MetavarContext :=
|
||||
mctx.modifyExprMVarLCtx mvarId (·.setType fvarId type)
|
||||
|
||||
def findLevelDepth? (mctx : MetavarContext) (mvarId : LMVarId) : Option Nat :=
|
||||
(mctx.findLevelDecl? mvarId).map LevelMetavarDecl.depth
|
||||
|
||||
|
|
@ -1535,6 +1539,14 @@ def setFVarBinderInfo [MonadMCtx m] (mvarId : MVarId) (fvarId : FVarId)
|
|||
(bi : BinderInfo) : m Unit :=
|
||||
modifyMCtx (·.setFVarBinderInfo mvarId fvarId bi)
|
||||
|
||||
/--
|
||||
Set the `BinderInfo` of an fvar. If the given metavariable is not declared or
|
||||
the given fvar doesn't exist in its context, nothing happens.
|
||||
-/
|
||||
def setFVarType [MonadMCtx m] (mvarId : MVarId) (fvarId : FVarId)
|
||||
(type : Expr) : m Unit :=
|
||||
modifyMCtx (·.setFVarType mvarId fvarId type)
|
||||
|
||||
end MVarId
|
||||
|
||||
end Lean
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ def diffHypothesesBundle (useAfter : Bool) (ctx₀ : LocalContext) (h₁ : Inte
|
|||
if !(ctx₀.contains fvid) then
|
||||
if let some decl₀ := ctx₀.findFromUserName? (.mkSimple ppName) then
|
||||
-- on ctx₀ there is an fvar with the same name as this one.
|
||||
let t₀ := decl₀.type
|
||||
let t₀ ← instantiateMVars decl₀.type
|
||||
return ← withTypeDiff t₀ h₁
|
||||
else
|
||||
if useAfter then
|
||||
|
|
@ -219,7 +219,7 @@ where
|
|||
withTypeDiff (t₀ : Expr) (h₁ : InteractiveHypothesisBundle) : MetaM InteractiveHypothesisBundle := do
|
||||
let some x₁ := h₁.fvarIds[0]?
|
||||
| throwError "internal error: empty fvar list!"
|
||||
let t₁ ← inferType <| Expr.fvar x₁
|
||||
let t₁ ← instantiateMVars =<< inferType (Expr.fvar x₁)
|
||||
let tδ ← exprDiff t₀ t₁ useAfter
|
||||
let c₁ ← addDiffTags useAfter tδ h₁.type
|
||||
return {h₁ with type := c₁}
|
||||
|
|
|
|||
|
|
@ -1511,8 +1511,7 @@ theorem any_toList {p : (_ : α) → β → Bool} :
|
|||
simp only [List.forIn'_cons, Id.run_bind, List.any_cons]
|
||||
by_cases h : p hd.fst hd.snd = true
|
||||
· simp [h]
|
||||
· simp only at ih
|
||||
simp [h, ih]
|
||||
· simp [h, ih]
|
||||
|
||||
theorem any_eq_true [LawfulHashable α] [EquivBEq α] {p : (_ : α) → β → Bool} (h : m.1.WF) :
|
||||
m.1.any p = true ↔ ∃ (a : α) (h : m.contains a), p (m.getKey a h) (Const.get m a h) := by
|
||||
|
|
@ -1576,8 +1575,7 @@ theorem all_toList {p : (_ : α) → β → Bool} :
|
|||
simp only [List.forIn'_cons, Id.run_bind, List.all_cons]
|
||||
by_cases h : p hd.fst hd.snd = false
|
||||
· simp [h]
|
||||
· simp only at ih
|
||||
simp [h, ih]
|
||||
· simp [h, ih]
|
||||
|
||||
theorem all_eq_true [EquivBEq α] [LawfulHashable α] {p : (a : α) → β → Bool} (h : m.1.WF) :
|
||||
m.1.all p = true ↔ ∀ (a : α) (h : m.contains a), p (m.getKey a h) (Const.get m a h) := by
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ theorem mem_of_necessary_assignment {n : Nat} {p : (PosFin n) → Bool} {c : Def
|
|||
next hne => simp [h] at pv
|
||||
· specialize p'_not_entails_c v
|
||||
have h := p'_not_entails_c.2 v_in_c
|
||||
simp only at h
|
||||
split at h
|
||||
next heq => simp [Literal.negate, ← heq, h, v_in_c]
|
||||
next hne => simp [h] at pv
|
||||
|
|
@ -175,7 +174,6 @@ theorem assignmentsInvariant_insertRatUnits {n : Nat} (f : DefaultFormula n)
|
|||
· rfl
|
||||
have hp1 := hp j1_unit ((Or.inr ∘ Or.inr) j1_unit_in_insertRatUnits_res)
|
||||
have hp2 := hp j2_unit ((Or.inr ∘ Or.inr) j2_unit_in_insertRatUnits_res)
|
||||
simp only at hp1 hp2
|
||||
rcases hp1 with ⟨i1, hp1⟩
|
||||
rcases hp2 with ⟨i2, hp2⟩
|
||||
simp only [Fin.getElem_fin] at h1 h2
|
||||
|
|
|
|||
|
|
@ -728,7 +728,6 @@ theorem sat_of_confirmRupHint_insertRup_fold {n : Nat} (f : DefaultFormula n)
|
|||
specialize pc v
|
||||
rw [v'_eq_v] at v'_in_c
|
||||
have pv := pc.2 v'_in_c
|
||||
simp only at pv
|
||||
simp only [p_unsat_c] at pv
|
||||
cases pv
|
||||
· simp only [negate_eq, List.mem_map, Prod.exists, Bool.exists_bool] at v_in_neg_c
|
||||
|
|
@ -742,7 +741,6 @@ theorem sat_of_confirmRupHint_insertRup_fold {n : Nat} (f : DefaultFormula n)
|
|||
specialize pc v
|
||||
rw [v'_eq_v] at v'_in_c
|
||||
have pv := pc.1 v'_in_c
|
||||
simp only at pv
|
||||
simp only [p_unsat_c] at pv
|
||||
cases pv
|
||||
· grind [Literal.negate]
|
||||
|
|
|
|||
90
tests/elab/9893.lean
Normal file
90
tests/elab/9893.lean
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import Lean.Elab.Tactic
|
||||
open Lean Elab Tactic
|
||||
|
||||
/-!
|
||||
# `MVarId.replaceLocalDeclDefEq` should do equality checks after instantiating metavariables
|
||||
https://github.com/leanprover/lean4/issues/9893
|
||||
-/
|
||||
|
||||
set_option linter.unusedVariables false
|
||||
|
||||
elab "test_change_to" t:term "at" h:ident : tactic => withMainContext do
|
||||
let e : Expr ← elabTerm t none
|
||||
let fvarId ← getFVarId h
|
||||
let ty ← fvarId.getType
|
||||
logInfo m!"{h} has metavariables: {ty.hasExprMVar}"
|
||||
liftMetaTactic1 fun g ↦ do
|
||||
let g' ← g.replaceLocalDeclDefEq fvarId e
|
||||
logInfo m!"old goal equals new goal: {g == g'}"
|
||||
return g'
|
||||
|
||||
/-!
|
||||
This always output 'true'
|
||||
-/
|
||||
/--
|
||||
info: h has metavariables: false
|
||||
---
|
||||
info: old goal equals new goal: true
|
||||
---
|
||||
trace: x y : Nat
|
||||
h : x ≤ y
|
||||
⊢ True
|
||||
-/
|
||||
#guard_msgs in
|
||||
example {x y : Nat} (h : x ≤ y) : True := by
|
||||
test_change_to x ≤ y at h
|
||||
trace_state
|
||||
exact trivial
|
||||
|
||||
/-!
|
||||
This used to output 'false'
|
||||
-/
|
||||
/--
|
||||
info: h has metavariables: true
|
||||
---
|
||||
info: old goal equals new goal: true
|
||||
---
|
||||
trace: x y : Nat
|
||||
h : x ≤ y
|
||||
⊢ True
|
||||
---
|
||||
trace: x y : Nat
|
||||
h : x ≤ y
|
||||
⊢ True
|
||||
---
|
||||
warning: declaration uses `sorry`
|
||||
-/
|
||||
#guard_msgs in
|
||||
example {x y : Nat} : True := by
|
||||
have h : ?foo := sorry
|
||||
case foo => refine x ≤ ?baz; exact y
|
||||
trace_state
|
||||
test_change_to x ≤ y at h
|
||||
trace_state
|
||||
exact trivial
|
||||
|
||||
/-!
|
||||
Make sure the goal changes when the hypothesis changes.
|
||||
-/
|
||||
/--
|
||||
info: h has metavariables: true
|
||||
---
|
||||
info: old goal equals new goal: false
|
||||
---
|
||||
trace: x y : Nat
|
||||
h : x ≤ y
|
||||
⊢ True
|
||||
---
|
||||
trace: x y : Nat
|
||||
h : x ≥ y
|
||||
⊢ True
|
||||
---
|
||||
warning: declaration uses `sorry`
|
||||
-/
|
||||
#guard_msgs in
|
||||
example {x y : Nat} : True := by
|
||||
have h : x ≤ y := sorry
|
||||
trace_state
|
||||
test_change_to x ≥ y at h
|
||||
trace_state
|
||||
exact trivial
|
||||
|
|
@ -16,6 +16,7 @@ example (B C : Prop) (f : forall {A : Prop}, A → C) (x : B) : C := by
|
|||
|
||||
example (X : Type) [Add X] (f : forall {A : Type} [Add A], A → A → A) (x : X) : X := by
|
||||
specialize f x x
|
||||
clear x
|
||||
assumption
|
||||
|
||||
def ex (f : Nat → Nat → Nat) : Nat := by
|
||||
|
|
|
|||
|
|
@ -3387,8 +3387,6 @@ theorem toInt_ediv_toInt_lt_of_nonpos_of_lt_neg_one {w : Nat} {x y : BitVec w} (
|
|||
rcases hx' with hx'|hx'|hx'
|
||||
· simp [hx']; omega
|
||||
· have := BitVec.neg_one_ediv_toInt_eq (y := y)
|
||||
simp only [
|
||||
Int.reduceNeg] at this
|
||||
simp [hx', this]
|
||||
omega
|
||||
· have := Int.ediv_lt_natAbs_self_of_lt_neg_one_of_lt_neg_one (x := x.toInt) (y := y.toInt) (by omega) hy
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue