Skip to content

Commit d98f25c

Browse files
committed
feat: clear_value tactic
This PR upstreams and extends the Mathlib `clear_value` tactic. Given a local definition `x : T := v`, the tactic `clear_value x` replaces it with a hypothesis `x : T`, or throws an error if the goal does not depend on the value `v`. The syntax `clear_value x with h` creates a hypothesis `h : x = v` before clearing the value of `x`. Furthermore, `clear_value *` clears all values that can be cleared, or throws an error if none can be cleared.
1 parent 4eccb5b commit d98f25c

File tree

4 files changed

+324
-0
lines changed

4 files changed

+324
-0
lines changed

src/Init/Tactics.lean

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,21 @@ references to a hypothesis.
139139
-/
140140
syntax (name := clear) "clear" (ppSpace colGt term:max)+ : tactic
141141

142+
/--
143+
Syntax for trying to clear the values of all local definitions.
144+
-/
145+
syntax clearValueStar := "*"
146+
/--
147+
* `clear_value x...` clears the values of the given local definitions.
148+
For example, a local definition `x : α := v` becomes a hypothesis `x : α`.
149+
150+
* `clear_value x with h` adds a hypothesis `h : x = v` before clearing the value of `x`.
151+
152+
* `clear_value *` clears values of all hypotheses that can be cleared.
153+
Fails if none can be cleared.
154+
-/
155+
syntax (name := clearValue) "clear_value" (ppSpace colGt (clearValueStar <|> term:max))+ (" with" (ppSpace colGt binderIdent)+)? : tactic
156+
142157
/--
143158
`subst x...` substitutes each `x` with `e` in the goal if there is a hypothesis
144159
of type `x = e` or `e = x`.

src/Lean/Elab/Tactic/BuiltinTactic.lean

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,54 @@ where
344344
replaceMainGoal [mvarId]
345345
| _ => throwUnsupportedSyntax
346346

347+
@[builtin_tactic Lean.Parser.Tactic.clearValue] def evalClearValue : Tactic := fun stx =>
348+
match stx with
349+
| `(tactic| clear_value $xs* $[with $hs*]?) => withMainContext do
350+
let hs := hs.getD #[]
351+
if xs.size < hs.size then throwErrorAt hs[xs.size]! "Too many binders provided."
352+
-- The bool means "clearing must succeed"
353+
let mut fvarIds : Array FVarId := #[]
354+
let mut hasStar := false
355+
for i in [0:xs.size], x in xs do
356+
if x.raw.isOfKind ``clearValueStar then
357+
if i < hs.size then
358+
throwErrorAt hs[i]! "When using `*`, no binder may be provided for it."
359+
hasStar := true
360+
else
361+
let fvarId ← getFVarId x
362+
unless (← fvarId.isLetVar) do
363+
throwErrorAt x "Hypothesis `{mkFVar fvarId}` is not a local definition."
364+
if fvarIds.contains fvarId then
365+
throwErrorAt x "Hypothesis `{mkFVar fvarId}` appears multiple times."
366+
fvarIds := fvarIds.push fvarId
367+
let mut g ← popMainGoal
368+
unless hs.isEmpty do
369+
let mut hyps : Array Hypothesis := #[]
370+
for fvarId in fvarIds, h in hs do
371+
let hName ←
372+
match h with
373+
| `(binderIdent| $n:ident) => pure n.raw.getId
374+
| _ => mkFreshBinderNameForTactic `h
375+
let type ← mkEq (mkFVar fvarId) (← fvarId.getValue?).get!
376+
let value ← mkEqRefl (mkFVar fvarId)
377+
hyps := hyps.push { userName := hName, type, value }
378+
g ← Prod.snd <$> g.assertHypotheses hyps
379+
let toClear ← g.withContext do
380+
if hasStar then (·.map Expr.fvarId!) <$> getLocalHyps
381+
else sortFVarIds fvarIds
382+
let mut succeeded := false
383+
for fvarId in toClear.reverse do
384+
try
385+
g ← g.clearValue fvarId
386+
succeeded := true
387+
catch _ =>
388+
if fvarIds.contains fvarId then
389+
g.withContext do throwError "Tactic `clear_value` could not clear value of `{Expr.fvar fvarId}`.\n{g}"
390+
unless succeeded do
391+
g.withContext do throwError "Tactic `clear_value` failed to clear any values.\n{g}"
392+
pushGoal g
393+
| _ => throwUnsupportedSyntax
394+
347395
def forEachVar (hs : Array Syntax) (tac : MVarId → FVarId → MetaM MVarId) : TacticM Unit := do
348396
for h in hs do
349397
withMainContext do

src/Lean/Meta/Tactic/Replace.lean

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,27 @@ def _root_.Lean.MVarId.modifyTargetEqLHS (mvarId : MVarId) (f : Expr → MetaM E
216216
else
217217
throwTacticEx `modifyTargetEqLHS mvarId m!"equality expected{indentExpr target}"
218218

219+
220+
/--
221+
Clears the value of the local definition `fvarId`. Ensures that the resulting goal state
222+
is still type correct. Throws an error if it is a local hypothesis without a value.
223+
-/
224+
def _root_.Lean.MVarId.clearValue (mvarId : MVarId) (fvarId : FVarId) : MetaM MVarId := do
225+
mvarId.checkNotAssigned `clear_value
226+
let tag ← mvarId.getTag
227+
let (_, mvarId) ← mvarId.withReverted #[fvarId] fun mvarId' fvars => mvarId'.withContext do
228+
let tgt ← mvarId'.getType
229+
unless tgt.isLet do
230+
mvarId.withContext <|
231+
throwTacticEx `clear_value mvarId m!"hypothesis `{Expr.fvar fvarId}` is not a local definition."
232+
let tgt' := Expr.forallE tgt.letName! tgt.letType! tgt.letBody! .default
233+
unless ← isTypeCorrect tgt' do
234+
mvarId.withContext <|
235+
throwTacticEx `clear_value mvarId
236+
m!"cannot clear {Expr.fvar fvarId}, the resulting context is not type correct."
237+
let mvarId'' ← mkFreshExprSyntheticOpaqueMVar tgt' tag
238+
mvarId'.assign <| .app mvarId'' tgt.letValue!
239+
return ((), fvars.map .some, mvarId''.mvarId!)
240+
return mvarId
241+
219242
end Lean.Meta

tests/lean/run/clear_value.lean

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
/-!
2+
# Tests for `clear_value` tactic
3+
-/
4+
5+
/-!
6+
Check that `clear_value` clears the value.
7+
-/
8+
/--
9+
trace: x : Nat
10+
⊢ 0 ≤ x
11+
-/
12+
#guard_msgs in
13+
example : let x := 22; 0 ≤ x := by
14+
intro x
15+
clear_value x
16+
trace_state
17+
exact Nat.zero_le _
18+
19+
/-!
20+
Test `*` mode.
21+
-/
22+
/--
23+
trace: x : Nat
24+
⊢ 0 ≤ x
25+
-/
26+
#guard_msgs in
27+
example : let x := 22; 0 ≤ x := by
28+
intro x
29+
clear_value *
30+
trace_state
31+
exact Nat.zero_le _
32+
33+
/-!
34+
Check that `clear_value` fails if there is no value to clear.
35+
-/
36+
/-- error: Hypothesis `x` is not a local definition. -/
37+
#guard_msgs in
38+
example (x : Nat) : 0 ≤ x := by
39+
clear_value x
40+
41+
/-!
42+
Check that `clear_value *` fails if it does nothing.
43+
-/
44+
/--
45+
error: Tactic `clear_value` failed to clear any values.
46+
x : Nat
47+
⊢ 0 ≤ x
48+
-/
49+
#guard_msgs in
50+
example (x : Nat) : 0 ≤ x := by
51+
clear_value *
52+
53+
/-!
54+
Check for validation that hypotheses aren't mentioned multiple times.
55+
-/
56+
/-- error: Hypothesis `x` appears multiple times. -/
57+
#guard_msgs in
58+
example : let x := 22; 0 ≤ x := by
59+
intro x
60+
clear_value x x
61+
62+
/-!
63+
Check that `clear_value ... with` creates an equality hypothesis.
64+
-/
65+
/--
66+
trace: x : Nat
67+
h : x = 22
68+
⊢ 0 ≤ 22
69+
-/
70+
#guard_msgs in
71+
example : let x := 22; 0 ≤ x := by
72+
intro x
73+
clear_value x with h
74+
rw [h]
75+
trace_state
76+
exact Nat.zero_le _
77+
78+
/-!
79+
Check `clear_value ... with` with `_`.
80+
-/
81+
/--
82+
trace: x : Nat
83+
h✝ : x = 22
84+
⊢ 0 ≤ 22
85+
-/
86+
#guard_msgs in
87+
example : let x := 22; 0 ≤ x := by
88+
intro x
89+
clear_value x with _
90+
rw [‹x = 22›]
91+
trace_state
92+
exact Nat.zero_le _
93+
94+
/-!
95+
Check `clear_value ... with` with too many `with` binders.
96+
-/
97+
/-- error: Too many binders provided. -/
98+
#guard_msgs in
99+
example : let x := 22; 0 ≤ x := by
100+
intro x
101+
clear_value x with h1 h2
102+
103+
/-!
104+
Check that `clear_value` checks for type correctness.
105+
-/
106+
/--
107+
error: Tactic `clear_value` could not clear value of `x`.
108+
x : Nat := 22
109+
y : Fin x := 0
110+
⊢ ↑y < x
111+
-/
112+
#guard_msgs in
113+
example : let x := 22; let y : Fin x := 0; y.1 < x := by
114+
intro x y
115+
clear_value x
116+
117+
/-!
118+
Even though we cannot clear `x` on its own, we can clear it if we clear `y` first.
119+
-/
120+
/--
121+
trace: x : Nat
122+
y : Fin x
123+
⊢ ↑y < x
124+
-/
125+
#guard_msgs in
126+
example : let x := 22; let y : Fin x := 0; y.1 < x := by
127+
intro x y
128+
clear_value y
129+
clear_value x
130+
trace_state
131+
exact y.2
132+
133+
/-!
134+
We can clear the values `x` and `y` in any order.
135+
-/
136+
/--
137+
trace: x : Nat
138+
y : Fin x
139+
⊢ ↑y < x
140+
-/
141+
#guard_msgs in
142+
example : let x := 22; let y : Fin x := 0; y.1 < x := by
143+
intro x y
144+
clear_value y x
145+
trace_state
146+
exact y.2
147+
/--
148+
trace: x : Nat
149+
y : Fin x
150+
⊢ ↑y < x
151+
-/
152+
#guard_msgs in
153+
example : let x := 22; let y : Fin x := 0; y.1 < x := by
154+
intro x y
155+
clear_value x y
156+
trace_state
157+
exact y.2
158+
159+
/-!
160+
Clearing `x` on its own and `y` on its own are OK because `y + 1` is nonzero.
161+
-/
162+
/--
163+
trace: x y : Nat
164+
z : Fin (y + 1) := 0
165+
⊢ ↑z < y + 1
166+
-/
167+
#guard_msgs in
168+
example : let x := 22; let y : Nat := x; let z : Fin (y + 1) := 0; z.1 < y + 1 := by
169+
intro x y z
170+
clear_value x
171+
clear_value y
172+
trace_state
173+
exact z.2
174+
175+
/-!
176+
Dependence, `clear_value` fails.
177+
-/
178+
/--
179+
error: Tactic `clear_value` could not clear value of `α`.
180+
α : Type := Nat
181+
x : α := 1
182+
⊢ x = x
183+
-/
184+
#guard_msgs in
185+
example : let α := Nat; let x : α := 1; @Eq α x x := by
186+
intro α x
187+
clear_value α
188+
189+
/-!
190+
`clear_value *` manages to clear `x` but not `α`
191+
-/
192+
/--
193+
trace: α : Type := Nat
194+
x : α
195+
⊢ x = 1
196+
---
197+
warning: declaration uses 'sorry'
198+
-/
199+
#guard_msgs in
200+
example : let α := Nat; let x : α := 1; @Eq α x 1 := by
201+
intro α x
202+
clear_value *
203+
trace_state
204+
sorry
205+
206+
/-!
207+
`clear_value *` fails if `x` is already cleared
208+
-/
209+
/--
210+
error: Tactic `clear_value` failed to clear any values.
211+
α : Type := Nat
212+
x : α
213+
⊢ x = 1
214+
-/
215+
#guard_msgs in
216+
example : let α := Nat; let x : α := 1; @Eq α x 1 := by
217+
intro α x
218+
clear_value x
219+
clear_value *
220+
221+
/-!
222+
Can use `with` clause with `*` so long as it's paired with a local.
223+
-/
224+
#guard_msgs in
225+
example : let α := Nat; let x : α := 1; @Eq α x 1 := by
226+
intro α x
227+
clear_value x * with h
228+
exact h
229+
230+
/-!
231+
Can't use `with` with `*` directly.
232+
-/
233+
/-- error: When using `*`, no binder may be provided for it. -/
234+
#guard_msgs in
235+
example : let α := Nat; let x : α := 1; @Eq α x 1 := by
236+
intro α x
237+
clear_value * with h
238+
exact h

0 commit comments

Comments
 (0)