| import Lean | |
| /-! | |
| # Ensure `replaceLocalDecl` instantiates metavariables | |
| These tests ensure that `replaceLocalDecl` is aware of `FVarId`s present in the assignments of | |
| metavariables present in the inserted declaration, and thus does not introduce unknown `FVarId`s by | |
| inserting a local declaration before it's well-formed. | |
| ## Context | |
| `replaceLocalDecl mvarId fvarId typeNew eqProof` replaces `fvarId` with a new FVarId that has the | |
| same name but type `typeNew`. It does this by inserting a new local declaration with type `typeNew` | |
| and clearing the old one if possible. | |
| It makes sure that the new local declaration is inserted at the soonest point after `fvarId` at | |
| which `typeNew` is well-formed. It does this by traversing `typeNew` and finding the `FVarId` with | |
| the maximum index among all `FVarId`s occurring in `typeNew`. | |
| If `replaceLocalDecl` encounters a metavariable during this traversal, it simply continues | |
| traversing. Previously, it might have been the case that this metavariable we encountered was | |
| assigned to an expression which contains an `FVarId` occurring after `fvarId`. This can lead to | |
| `replaceLocalDecl` inserting a local declaration with a type which depends on `FVarId`s which come | |
| after it in the local context, thus creating unknown `FVarId`s. (Note that the `FVarId`s of the | |
| local declarations occurring after the insertion are modified, so the `FVarId` involved in the | |
| assignment may not even exist in the local context anymore.) | |
| We now attempt to prevent `replaceLocalDecl` from encountering assigned metavariables by | |
| calling `instantiateMVars` in `replaceLocalDecl` before traversal. | |
| Note that this is not a perfect solution to the overall problem; `Term.synthesizeSyntheticMVars` | |
| may introduce assignments to inaccessible `FVarId`s after `replaceLocalDecl` has run, in which case | |
| `instantiateMVars` is ineffective (as the metavariable has not even been assigned yet). See issue | |
| #2727. | |
| -/ | |
| /-! ## Direct test of instantiated mvars -/ | |
| open Lean Meta Elab Tactic in | |
| /-- Replace the type of `fvar₁` with `fvar₂ = fvar₂` where the expression `fvar₂ = fvar₂` is hidden | |
| under a metavariable assignment. Note that initially `fvar₁` must come before `fvar₂` in order to | |
| make sure `replaceLocalDecl` is behaving correctly. -/ | |
| elab "replace " fvar₁:ident " with " fvar₂:ident " via " proof:term : tactic => withMainContext do | |
| let fvar₁ ← getFVarId fvar₁ | |
| let fvar₂ ← getFVarId fvar₂ | |
| let underlyingTypeNew ← inferType <|← mkEqRefl (Expr.fvar fvar₂) | |
| -- make a metavariable to use as the type in `replaceLocalDecl`; assign it to `underlyingTypeNew` | |
| let typeNewMVar ← mkFreshExprMVar none | |
| typeNewMVar.mvarId!.assign underlyingTypeNew | |
| let proof ← elabTerm proof underlyingTypeNew | |
| let { mvarId .. } ← (← getMainGoal).replaceLocalDecl fvar₁ typeNewMVar proof | |
| replaceMainGoal [mvarId] | |
| example : True := by | |
| have h₁ : True := trivial | |
| have h₂ : Nat := 0 | |
| replace h₁ with h₂ via eq_true rfl |>.symm | |
| /- | |
| Previously, goal state was: | |
| h₁: _uniq.NNNN = _uniq.NNNN | |
| h₂: Nat | |
| ⊢ True | |
| -/ |