Skip to content

Commit 3af9ab6

Browse files
authored
feat: subst tactic can substitute let values (#8450)
This PR adds a feature to the `subst` tactic so that when `x : X := v` is a local definition, `subst x` substitutes `v` for `x` in the goal and removes `x`. Previously the tactic would throw an error.
1 parent a6dd6a4 commit 3af9ab6

File tree

5 files changed

+136
-57
lines changed

5 files changed

+136
-57
lines changed

src/Init/Tactics.lean

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,16 @@ but adds the `hx : x = v` hypothesis first. Having a `with` binding associated t
160160
syntax (name := clearValue) "clear_value" (ppSpace colGt (clearValueStar <|> term:max))+ (" with" (ppSpace colGt binderIdent)+)? : tactic
161161

162162
/--
163-
`subst x...` substitutes each `x` with `e` in the goal if there is a hypothesis
164-
of type `x = e` or `e = x`.
165-
If `x` is itself a hypothesis of type `y = e` or `e = y`, `y` is substituted instead.
163+
`subst x...` substitutes each hypothesis `x` with a definition found in the local context,
164+
then eliminates the hypothesis.
165+
- If `x` is a local definition, then its definition is used.
166+
- Otherwise, if there is a hypothesis of the form `x = e` or `e = x`,
167+
then `e` is used for the definition of `x`.
168+
169+
If `h : a = b`, then `subst h` may be used if either `a` or `b` unfolds to a local hypothesis.
170+
This is similar to the `cases h` tactic.
171+
172+
See also: `subst_vars` for substituting all local hypotheses that have a defining equation.
166173
-/
167174
syntax (name := subst) "subst" (ppSpace colGt term:max)+ : tactic
168175

src/Lean/Elab/Tactic/BuiltinTactic.lean

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,20 @@ def forEachVar (hs : Array Syntax) (tac : MVarId → FVarId → MetaM MVarId) :
403403

404404
@[builtin_tactic Lean.Parser.Tactic.subst] def evalSubst : Tactic := fun stx =>
405405
match stx with
406-
| `(tactic| subst $hs*) => forEachVar hs Meta.subst
406+
| `(tactic| subst $hs*) => forEachVar hs fun mvarId fvarId => do
407+
let decl ← fvarId.getDecl
408+
if decl.isLet then
409+
-- Zeta delta reduce the let and eliminate it.
410+
let (_, mvarId) ← mvarId.withReverted #[fvarId] fun mvarId' fvars => mvarId'.withContext do
411+
let tgt ← mvarId'.getType
412+
assert! tgt.isLet
413+
let mvarId'' ← mvarId'.replaceTargetDefEq (tgt.letBody!.instantiate1 tgt.letValue!)
414+
-- Dropped the let fvar
415+
let aliasing := (fvars.extract 1).map some
416+
return ((), aliasing, mvarId'')
417+
return mvarId
418+
else
419+
Meta.subst mvarId fvarId
407420
| _ => throwUnsupportedSyntax
408421

409422
@[builtin_tactic Lean.Parser.Tactic.substVars] def evalSubstVars : Tactic := fun _ =>

tests/lean/run/substlet.lean

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/-!
2+
# Tests of the `subst` tactic when `let`s are present.
3+
-/
4+
5+
/-!
6+
Eliminates `a` even though `e : id a = m`.
7+
-/
8+
/--
9+
trace: case intro
10+
n : Nat
11+
m : Nat := n
12+
a : Nat
13+
e : id a = m
14+
⊢ 0 + n = n
15+
---
16+
trace: case intro
17+
a : Nat
18+
m : Nat := id a
19+
⊢ 0 + id a = id a
20+
-/
21+
#guard_msgs in
22+
theorem ex1 (n : Nat) : 0 + n = n := by
23+
let m := n
24+
have h : ∃ k, id k = m := ⟨m, rfl⟩
25+
cases h with
26+
| intro a e =>
27+
trace_state
28+
subst e
29+
trace_state
30+
apply Nat.zero_add
31+
32+
/-!
33+
Eliminates `a` even though `e : m = id a`.
34+
-/
35+
/--
36+
trace: case intro
37+
n : Nat
38+
m : Nat := n
39+
a : Nat
40+
e : m = id a
41+
⊢ 0 + n = n
42+
---
43+
trace: case intro
44+
n : Nat
45+
m : Nat := n
46+
⊢ 0 + n = n
47+
-/
48+
#guard_msgs in
49+
theorem ex2 (n : Nat) : 0 + n = n := by
50+
let m := n
51+
have h : ∃ k, m = id k := ⟨m, rfl⟩
52+
cases h with
53+
| intro a e =>
54+
trace_state
55+
subst e
56+
trace_state
57+
apply Nat.zero_add
58+
59+
/-!
60+
Since `v` is a let binding, the `subst v` tactic instead
61+
zeta delta reduces it everywhere and then clears it.
62+
-/
63+
/--
64+
trace: n : Nat
65+
h : n = 0
66+
m : Nat := n + 1
67+
v : Nat := m + 1
68+
this : v = n + 2
69+
⊢ 0 + n = 0
70+
---
71+
trace: n : Nat
72+
h : n = 0
73+
m : Nat := n + 1
74+
this : m + 1 = n + 2
75+
⊢ 0 + n = 0
76+
---
77+
trace: m : Nat := 0 + 1
78+
this : m + 1 = 0 + 2
79+
⊢ 0 + 0 = 0
80+
-/
81+
#guard_msgs in
82+
theorem ex3 (n : Nat) (h : n = 0) : 0 + n = 0 := by
83+
let m := n + 1
84+
let v := m + 1
85+
have : v = n + 2 := rfl
86+
trace_state
87+
subst v
88+
trace_state
89+
subst n
90+
trace_state
91+
rfl
92+
93+
/-!
94+
Can't do `subst this` with `this : v = n + 2` since `v` is a let binding.
95+
The tactic sees `m + 1 = n + 2` and fails.
96+
-/
97+
/--
98+
error: tactic 'subst' failed, invalid equality proof, it is not of the form (x = t) or (t = x)
99+
v = n + 2
100+
n : Nat
101+
h : n = 0
102+
m : Nat := n + 1
103+
v : Nat := m + 1
104+
this : v = n + 2
105+
⊢ 0 + n = 0
106+
-/
107+
#guard_msgs in
108+
theorem ex4 (n : Nat) (h : n = 0) : 0 + n = 0 := by
109+
let m := n + 1
110+
let v := m + 1
111+
have : v = n + 2 := rfl
112+
subst this

tests/lean/substlet.lean

Lines changed: 0 additions & 26 deletions
This file was deleted.

tests/lean/substlet.lean.expected.out

Lines changed: 0 additions & 27 deletions
This file was deleted.

0 commit comments

Comments
 (0)