Skip to content

Commit 0b64c1e

Browse files
thorimurkim-em
andauthored
fix: make sure refine preserves pre-existing natural mvars (leanprover#2435)
* fix: `withCollectingNewGoals` * don't exclude pre-existing natural mvars * test: ensure pre-existing natural mvars are preserved * docs: update comment and include issue number * test: expected.out * docs: add module docstrings to test * also deleted superfluous `add_synthetic_goal` * test: fix expected.out line numbers * Update tests/lean/refinePreservesNaturalMVars.lean Co-authored-by: Scott Morrison <scott@tqft.net> * docs: clarify comment --------- Co-authored-by: Scott Morrison <scott@tqft.net>
1 parent 1dd443a commit 0b64c1e

File tree

3 files changed

+75
-6
lines changed

3 files changed

+75
-6
lines changed

src/Lean/Elab/Tactic/ElabTerm.lean

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,21 +124,23 @@ where
124124
let newMVarIds ← getMVarsNoDelayed val
125125
/- ignore let-rec auxiliary variables, they are synthesized automatically later -/
126126
let newMVarIds ← newMVarIds.filterM fun mvarId => return !(← Term.isLetRecAuxMVar mvarId)
127-
let newMVarIds ← if allowNaturalHoles then
128-
pure newMVarIds.toList
129-
else
127+
/- The following `unless … do` block guards against unassigned natural mvarids created during
128+
`k` in the case that `allowNaturalHoles := false`. If we pass this block without aborting, we
129+
can be assured that `newMVarIds` does not contain unassigned natural mvars created during `k`.
130+
Note that in all cases we must allow `newMVarIds` to contain unassigned natural mvars which
131+
were created *before* `k`; this is the purpose of `mvarCounterSaved`, which lets us distinguish
132+
mvars created before `k` from those created during and after. See issue #2434. -/
133+
unless allowNaturalHoles do
130134
let naturalMVarIds ← newMVarIds.filterM fun mvarId => return (← mvarId.getKind).isNatural
131-
let syntheticMVarIds ← newMVarIds.filterM fun mvarId => return !(← mvarId.getKind).isNatural
132135
let naturalMVarIds ← filterOldMVars naturalMVarIds mvarCounterSaved
133136
logUnassignedAndAbort naturalMVarIds
134-
pure syntheticMVarIds.toList
135137
/-
136138
We sort the new metavariable ids by index to ensure the new goals are ordered using the order the metavariables have been created.
137139
See issue #1682.
138140
Potential problem: if elaboration of subterms is delayed the order the new metavariables are created may not match the order they
139141
appear in the `.lean` file. We should tell users to prefer tagged goals.
140142
-/
141-
let newMVarIds ← sortMVarIdsByIndex newMVarIds
143+
let newMVarIds ← sortMVarIdsByIndex newMVarIds.toList
142144
tagUntaggedGoals (← getMainTag) tagSuffix newMVarIds
143145
return (val, newMVarIds)
144146

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import Lean
2+
3+
/-! Ensures that `refine` does not remove pre-existing natural goals from the goal list. -/
4+
5+
open Lean Meta Elab Tactic Term
6+
7+
elab "add_natural_goal" s:ident " : " t:term : tactic => do
8+
let g ← mkFreshExprMVar (← elabType t) .natural s.getId
9+
appendGoals [g.mvarId!]
10+
11+
/-!
12+
13+
In the following, `refine` would erroneously close each focused goal, leading to a
14+
`(kernel) declaration has metavariables '_example'` error.
15+
16+
This occurred because `withCollectingNewGoalsFrom` was only erroring on new natural goals (as
17+
determined by `index`), while simultaneously only passing through non-natural goals to construct
18+
the resulting goal list. This orphaned old natural metavariables and closed the goal list
19+
erroneously.
20+
21+
As such, all of the following tests should lead to an `unsolved goals` error, followed by a
22+
`no goals` error (instead of a successful focus).
23+
24+
-/
25+
26+
example : Bool × Nat := by
27+
add_natural_goal d : Bool
28+
add_natural_goal e : Nat
29+
· refine (?d,?e)
30+
· refine ?d
31+
· refine ?e
32+
33+
example : Bool × Bool := by
34+
add_natural_goal d : Bool
35+
add_natural_goal e : Bool
36+
· refine (?d,?e)
37+
· case d => refine ?e
38+
· refine ?e
39+
40+
/-!
41+
42+
Previously, this would error, as `refine (?d, ?e)` erroneously closed the goal, leading to a
43+
`no goals` error. Instead, this should succeed.
44+
45+
-/
46+
47+
example : Bool × Bool := by
48+
add_natural_goal d : Bool
49+
add_natural_goal e : Bool
50+
· refine (?d,?e)
51+
refine ?d
52+
refine ?e -- This unifies `?d` and `?e`, so only one goal remains.
53+
exact true
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
refinePreservesNaturalMVars.lean:29:2-29:3: error: unsolved goals
2+
case d
3+
⊢ Bool
4+
5+
case e
6+
⊢ Nat
7+
refinePreservesNaturalMVars.lean:30:2-30:3: error: no goals to be solved
8+
refinePreservesNaturalMVars.lean:36:2-36:3: error: unsolved goals
9+
case d
10+
⊢ Bool
11+
12+
case e
13+
⊢ Bool
14+
refinePreservesNaturalMVars.lean:37:2-37:3: error: no goals to be solved

0 commit comments

Comments
 (0)