Skip to content

Commit cef370e

Browse files
alexjbestScott Morrison
andcommitted
feat: rewrites at location (#4157)
Co-authored-by: Scott Morrison <scott.morrison@anu.edu.au>
1 parent 1d27904 commit cef370e

File tree

3 files changed

+75
-39
lines changed

3 files changed

+75
-39
lines changed

Mathlib/Tactic/Rewrites.lean

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@ import Mathlib.Control.Basic
2121
Suggestions are printed as `rw [h]` or `rw [←h]`.
2222
2323
## Future work
24-
It would be nice to have `rw? at h`.
2524
26-
We could also try discharging side goals via `assumption` or `solve_by_elim`.
25+
We could try discharging side goals via `assumption` or `solve_by_elim`.
2726
2827
-/
2928

@@ -135,13 +134,11 @@ This core function returns a monadic list, to allow the caller to decide how lon
135134
See also `rewrites` for a more convenient interface.
136135
-/
137136
def rewritesCore (lemmas : DiscrTree (Name × Bool × Nat) s × DiscrTree (Name × Bool × Nat) s)
138-
(goal : MVarId) : ListM MetaM RewriteResult := ListM.squash do
139-
let type ← instantiateMVars (← goal.getType)
137+
(goal : MVarId) (target : Expr) : ListM MetaM RewriteResult := ListM.squash do
140138

141139
-- Get all lemmas which could match some subexpression
142-
-- DiscrTree.getSubexpressionMatches
143-
let candidates := (← lemmas.1.getSubexpressionMatches type)
144-
++ (← lemmas.2.getSubexpressionMatches type)
140+
let candidates := (← lemmas.1.getSubexpressionMatches target)
141+
++ (← lemmas.2.getSubexpressionMatches target)
145142

146143
-- Sort them by our preferring weighting
147144
-- (length of discriminant key, doubled for the forward implication)
@@ -154,7 +151,7 @@ def rewritesCore (lemmas : DiscrTree (Name × Bool × Nat) s × DiscrTree (Name
154151
let candidates := ListM.ofList candidates.toList
155152
pure <| candidates.filterMapM fun ⟨lem, symm, weight⟩ => do
156153
trace[Tactic.rewrites] "considering {if symm then "" else ""}{lem}"
157-
let some result ← try? do goal.rewrite type (← mkConstWithFreshMVarLevels lem) symm
154+
let some result ← try? do goal.rewrite target (← mkConstWithFreshMVarLevels lem) symm
158155
| return none
159156
return if result.mvarIds.isEmpty then
160157
some ⟨lem, symm, weight, result, none⟩
@@ -164,17 +161,17 @@ def rewritesCore (lemmas : DiscrTree (Name × Bool × Nat) s × DiscrTree (Name
164161

165162
/-- Find lemmas which can rewrite the goal. -/
166163
def rewrites (lemmas : DiscrTree (Name × Bool × Nat) s × DiscrTree (Name × Bool × Nat) s)
167-
(goal : MVarId) (max : Nat := 20) (leavePercentHeartbeats : Nat := 10) :
168-
MetaM (List RewriteResult) := do
169-
let results ← rewritesCore lemmas goal
164+
(goal : MVarId) (target : Expr) (stop_at_rfl : Bool := False) (max : Nat := 20)
165+
(leavePercentHeartbeats : Nat := 10) : MetaM (List RewriteResult) := do
166+
let results ← rewritesCore lemmas goal target
170167
-- Don't use too many heartbeats.
171168
|>.whileAtLeastHeartbeatsPercent leavePercentHeartbeats
172169
-- Stop if we find a rewrite after which `with_reducible rfl` would succeed.
173-
|>.mapM RewriteResult.computeRfl
174-
|>.takeUpToFirst (fun r => r.rfl? = some true)
170+
|>.mapM RewriteResult.computeRfl -- TODO could simply not compute this if stop_at_rfl is False
171+
|>.takeUpToFirst (fun r => stop_at_rfl && r.rfl? = some true)
175172
-- Bound the number of results.
176173
|>.takeAsList max
177-
return match results.filter (fun r => r.rfl? = some true) with
174+
return match results.filter (fun r => stop_at_rfl && r.rfl? = some true) with
178175
| [] =>
179176
-- TODO consider sorting the results,
180177
-- e.g. if we use solveByElim to fill arguments,
@@ -192,27 +189,52 @@ open Lean.Parser.Tactic
192189
Suggestions are printed as `rw [h]` or `rw [←h]`.
193190
`rw?!` is the "I'm feeling lucky" mode, and will run the first rewrite it finds.
194191
-/
195-
syntax (name := rewrites') "rw?" "!"? : tactic
192+
syntax (name := rewrites') "rw?" "!"? (ppSpace location)? : tactic
196193

197194
open Elab.Tactic Elab Tactic in
198195
elab_rules : tactic |
199-
`(tactic| rw?%$tk $[!%$lucky]?) => do
196+
`(tactic| rw?%$tk $[!%$lucky]? $[$loc]?) => do
197+
let lems ← rewriteLemmas.get
198+
reportOutOfHeartbeats `rewrites tk
200199
let goal ← getMainGoal
201-
goal.withContext do
202-
let results ← rewrites (← rewriteLemmas.get) goal
203-
reportOutOfHeartbeats `rewrites tk
204-
if results.isEmpty then
205-
throwError "rw? could not find any lemmas which can rewrite the goal"
206-
for r in results do
207-
let newGoal := if r.rfl? = some true then Expr.lit (.strVal "no goals") else r.result.eNew
208-
addRewriteSuggestion tk (← mkConstWithFreshMVarLevels r.name) r.symm newGoal
209-
if lucky.isSome then
210-
match results[0]? with
211-
| some r => do
212-
replaceMainGoal
213-
((← goal.replaceTargetEq r.result.eNew r.result.eqProof) :: r.result.mvarIds)
214-
evalTactic (← `(tactic| rfl))
215-
| _ => failure
216-
217-
@[inherit_doc rewrites'] macro "rw?!" : tactic =>
218-
`(tactic| rw? !)
200+
-- TODO fix doc of core to say that * fails only if all failed
201+
withLocation (expandOptLocation (Lean.mkOptionalNode loc))
202+
fun f => do
203+
let some a ← f.findDecl? | return
204+
if a.isImplementationDetail then return
205+
let target ← instantiateMVars (← f.getType)
206+
let results ← rewrites lems goal target (stop_at_rfl := false)
207+
reportOutOfHeartbeats `rewrites tk
208+
if results.isEmpty then
209+
throwError "Could not find any lemmas which can rewrite the hypothesis {
210+
← f.getUserName}"
211+
for r in results do
212+
addRewriteSuggestion tk (← mkConstWithFreshMVarLevels r.name) r.symm
213+
r.result.eNew (loc? := .some (.fvar f)) (origSpan? := ← getRef)
214+
if lucky.isSome then
215+
match results[0]? with
216+
| some r => do
217+
let replaceResult ← goal.replaceLocalDecl f r.result.eNew r.result.eqProof
218+
replaceMainGoal (replaceResult.mvarId :: r.result.mvarIds)
219+
| _ => failure
220+
do
221+
let target ← instantiateMVars (← goal.getType)
222+
let results ← rewrites lems goal target (stop_at_rfl := true)
223+
reportOutOfHeartbeats `rewrites tk
224+
if results.isEmpty then
225+
throwError "Could not find any lemmas which can rewrite the goal"
226+
for r in results do
227+
let newGoal := if r.rfl? = some true then Expr.lit (.strVal "no goals") else r.result.eNew
228+
addRewriteSuggestion tk (← mkConstWithFreshMVarLevels r.name) r.symm
229+
newGoal (origSpan? := ← getRef)
230+
if lucky.isSome then
231+
match results[0]? with
232+
| some r => do
233+
replaceMainGoal
234+
((← goal.replaceTargetEq r.result.eNew r.result.eqProof) :: r.result.mvarIds)
235+
evalTactic (← `(tactic| try rfl))
236+
| _ => failure
237+
(λ _ => throwError "Failed to find a rewrite for some location")
238+
239+
@[inherit_doc rewrites'] macro "rw?!" h:(ppSpace location)? : tactic =>
240+
`(tactic| rw? ! $[$h]?)

Mathlib/Tactic/TryThis.lean

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,24 @@ def addHaveSuggestion (ref : Syntax) (t? : Option Expr) (e : Expr) :
3232
`(tactic| let this := $estx)
3333
addSuggestion ref tac
3434

35-
/-- Add a suggestion for `rw [h]`. -/
36-
def addRewriteSuggestion (ref : Syntax) (e : Expr) (symm : Bool) (type? : Option Expr := none) :
35+
/-- Add a suggestion for `rw [h] at loc`. -/
36+
def addRewriteSuggestion (ref : Syntax) (e : Expr) (symm : Bool)
37+
(type? : Option Expr := none) (loc? : Option Expr := none)
38+
(origSpan? : Option Syntax := none) :
3739
TermElabM Unit := do
3840
let estx ← delabToRefinableSyntax e
39-
let tac ← if symm then `(tactic| rw [← $estx]) else `(tactic| rw [$estx:term])
40-
let mut tacMsg := if symm then m!"rw [← {e}]" else m!"rw [{e}]"
41+
let tac ← do
42+
let loc ← loc?.mapM fun loc => do `(Lean.Parser.Tactic.location| at $(← delab loc):term)
43+
if symm then `(tactic| rw [← $estx] $(loc)?) else `(tactic| rw [$estx:term] $(loc)?)
44+
45+
let mut tacMsg :=
46+
if let some loc := loc? then
47+
if symm then m!"rw [← {e}] at {loc}" else m!"rw [{e}] at {loc}"
48+
else
49+
if symm then m!"rw [← {e}]" else m!"rw [{e}]"
4150
let mut extraMsg := ""
4251
if let some type := type? then
4352
tacMsg := tacMsg ++ m!"\n-- {type}"
4453
extraMsg := extraMsg ++ s!"\n-- {← PrettyPrinter.ppExpr type}"
45-
addSuggestion ref tac (suggestionForMessage? := tacMsg) (extraMsg := extraMsg)
54+
addSuggestion ref tac (suggestionForMessage? := tacMsg)
55+
(extraMsg := extraMsg) (origSpan? := origSpan?)

test/rewrites.lean

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,7 @@ example [Group G] (g h : G) : g * g⁻¹ * h = h := by
5353

5454
lemma prime_of_prime (n : ℕ) : Prime n ↔ Nat.Prime n := by
5555
rw?!
56+
57+
example [Group G] (h : G) (hyp : g * 1 = h) : g = h := by
58+
rw?! at hyp
59+
assumption

0 commit comments

Comments
 (0)