Skip to content

Commit 66fc4b9

Browse files
committed
adapt says, hint and guard_msg outputs
1 parent eab0683 commit 66fc4b9

22 files changed

+257
-244
lines changed

Mathlib.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4092,6 +4092,7 @@ import Mathlib.InformationTheory.KullbackLeibler.KLFun
40924092
import Mathlib.Init
40934093
import Mathlib.Lean.ContextInfo
40944094
import Mathlib.Lean.CoreM
4095+
import Mathlib.Lean.Elab.InfoTree
40954096
import Mathlib.Lean.Elab.Tactic.Basic
40964097
import Mathlib.Lean.Elab.Tactic.Meta
40974098
import Mathlib.Lean.Elab.Term

Mathlib/Lean/Elab/InfoTree.lean

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/-
2+
Copyright (c) 2025 Marc Huisinga. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Marc Huisinga
5+
-/
6+
import Lean.Server.InfoUtils
7+
import Lean.Meta.TryThis
8+
9+
/-!
10+
# Additions to `Lean.Elab.InfoTree.Main`
11+
-/
12+
13+
namespace Lean.Elab
14+
15+
open Lean.Meta
16+
open Lean.Meta.Tactic.TryThis
17+
18+
/--
19+
Collects all suggestions from all `TryThisInfo`s in `trees`.
20+
`trees` is visited in order and suggestions in each tree are collected in post-order.
21+
-/
22+
def collectTryThisSuggestions (trees : PersistentArray InfoTree) :
23+
Array Suggestion :=
24+
go.run #[] |>.2
25+
where
26+
/-- Visits all trees. -/
27+
go : StateM (Array Suggestion) Unit := do
28+
for tree in trees do
29+
tree.visitM' (postNode := visitNode)
30+
/-- Visits a node in a tree. -/
31+
visitNode (_ctx : ContextInfo) (i : Info) (_children : PersistentArray InfoTree) :
32+
StateM (Array Suggestion) Unit := do
33+
let .ofCustomInfo ci := i
34+
| return
35+
let some tti := ci.value.get? TryThisInfo
36+
| return
37+
modify (·.push tti.suggestion)

Mathlib/Tactic/Basic.lean

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,31 @@ elab (name := clearAuxDecl) "clear_aux_decl" : tactic => withMainContext do
125125

126126
attribute [pp_with_univ] ULift PUnit PEmpty
127127

128+
/-- Result of `withResetServerInfo`. -/
129+
structure withResetServerInfo.Result (α : Type) where
130+
/-- Return value of the executed tactic. -/
131+
result? : Option α
132+
/-- Messages produced by the executed tactic. -/
133+
msgs : MessageLog
134+
/-- Info trees produced by the executed tactic, wrapped in `CommandContextInfo.save`. -/
135+
trees : PersistentArray InfoTree
136+
137+
/--
138+
Runs a tactic, returning any new messages and info trees rather than adding them to the state.
139+
-/
140+
def withResetServerInfo {α : Type} (t : TacticM α) :
141+
TacticM (withResetServerInfo.Result α) := do
142+
let (savedMsgs, savedTrees) ← modifyGetThe Core.State fun st =>
143+
((st.messages, st.infoState.trees), { st with messages := {}, infoState.trees := {} })
144+
Prod.snd <$> MonadFinally.tryFinally' t fun result? => do
145+
let msgs ← Core.getMessageLog
146+
let ist ← getInfoState
147+
let trees ← ist.trees.mapM fun tree => do
148+
let tree := tree.substitute ist.assignment
149+
let ctx := .commandCtx <| ← CommandContextInfo.save
150+
return InfoTree.context ctx tree
151+
modifyThe Core.State fun st =>
152+
{ st with messages := savedMsgs, infoState.trees := savedTrees }
153+
return { result?, msgs, trees }
154+
128155
end Mathlib.Tactic

Mathlib/Tactic/Hint.lean

Lines changed: 9 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import Lean.Meta.Tactic.TryThis
77
import Batteries.Linter.UnreachableTactic
88
import Batteries.Control.Nondet.Basic
99
import Mathlib.Init
10+
import Mathlib.Lean.Elab.InfoTree
11+
import Mathlib.Tactic.Basic
1012

1113
/-!
1214
# The `hint` tactic.
@@ -64,28 +66,14 @@ elab (name := registerHintStx)
6466
initialize
6567
Batteries.Linter.UnreachableTactic.ignoreTacticKindsRef.modify fun s => s.insert ``registerHintStx
6668

67-
/--
68-
Extracts the `MessageData` from the first clickable `Try This:` diff widget in the message.
69-
Preserves (only) contexts and tags.
70-
-/
71-
private def getFirstTryThisFromMessage? : MessageData → Option MessageData
72-
| .ofWidget w msg => if w.id == ``Meta.Hint.tryThisDiffWidget then msg else none
73-
| .nest _ msg
74-
| .group msg => getFirstTryThisFromMessage? msg
75-
| .compose msg₁ msg₂ => getFirstTryThisFromMessage? msg₁ <|> getFirstTryThisFromMessage? msg₂
76-
| .withContext ctx msg => (getFirstTryThisFromMessage? msg).map <| .withContext ctx
77-
| .withNamingContext ctx msg => (getFirstTryThisFromMessage? msg).map <| .withNamingContext ctx
78-
| .tagged tag msg => (getFirstTryThisFromMessage? msg).map <| .tagged tag
79-
| .ofFormatWithInfos _ | .ofGoal _ | .trace .. | .ofLazy .. => none
80-
8169
/--
8270
Construct a suggestion for a tactic.
8371
* Check the passed `MessageLog` for an info message beginning with "Try this: ".
8472
* If found, use that as the suggestion.
8573
* Otherwise use the provided syntax.
8674
* Also, look for remaining goals and pretty print them after the suggestion.
8775
-/
88-
def suggestion (tac : TSyntax `tactic) (msgs : MessageLog := {}) : TacticM Suggestion := do
76+
def suggestion (tac : TSyntax `tactic) (trees : PersistentArray InfoTree) : TacticM Suggestion := do
8977
-- TODO `addExactSuggestion` has an option to construct `postInfo?`
9078
-- Factor that out so we can use it here instead of copying and pasting?
9179
let goals ← getGoals
@@ -102,27 +90,12 @@ def suggestion (tac : TSyntax `tactic) (msgs : MessageLog := {}) : TacticM Sugge
10290
-/
10391
-- let style? := if goals.isEmpty then some .success else none
10492
let preInfo? := if goals.isEmpty then some "🎉 " else none
105-
let msg? : Option MessageData := msgs.toList.firstM (getFirstTryThisFromMessage? ·.data)
106-
let suggestion match msg? with
107-
| some m => pure <| SuggestionText.string (← m.toString)
108-
| none => pure <| SuggestionText.tsyntax tac
93+
let suggestions := collectTryThisSuggestions trees
94+
let suggestion := match suggestions[0]? with
95+
| some s => s.suggestion
96+
| none => SuggestionText.tsyntax tac
10997
return { preInfo?, suggestion, postInfo? }
11098

111-
/-- Run a tactic, returning any new messages rather than adding them to the message log. -/
112-
def withMessageLog (t : TacticM Unit) : TacticM MessageLog := do
113-
let initMsgs ← modifyGetThe Core.State fun st => (st.messages, { st with messages := {} })
114-
t
115-
modifyGetThe Core.State fun st => (st.messages, { st with messages := initMsgs })
116-
117-
/--
118-
Run a tactic, but revert any changes to info trees.
119-
We use this to inhibit the creation of widgets by subsidiary tactics.
120-
-/
121-
def withoutInfoTrees (t : TacticM Unit) : TacticM Unit := do
122-
let trees := (← getInfoState).trees
123-
t
124-
modifyInfoState fun s => { s with trees }
125-
12699
/--
127100
Run all tactics registered using `register_hint`.
128101
Print a "Try these:" suggestion for each of the successful tactics.
@@ -136,11 +109,11 @@ def hint (stx : Syntax) : TacticM Unit := withMainContext do
136109
let tacs := (← getHints).toArray.qsort (·.1 > ·.1) |>.toList.map (·.2)
137110
let tacs := Nondet.ofList tacs
138111
let results := tacs.filterMapM fun t : TSyntax `tactic => do
139-
if let some msgs ← observing? (withMessageLog (withoutInfoTrees (evalTactic t))) then
112+
if let some { msgs, trees, .. } ← observing? (withResetServerInfo (evalTactic t)) then
140113
if msgs.hasErrors then
141114
return none
142115
else
143-
return some (← getGoals, ← suggestion t msgs)
116+
return some (← getGoals, ← suggestion t trees)
144117
else
145118
return none
146119
let results ← (results.toMLList.takeUpToFirst fun r => r.1.1.isEmpty).asArray

Mathlib/Tactic/Says.lean

Lines changed: 22 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import Mathlib.Init
77
import Lean.Meta.Tactic.TryThis
88
import Batteries.Linter.UnreachableTactic
99
import Qq.Match
10+
import Mathlib.Lean.Elab.InfoTree
11+
import Mathlib.Tactic.Basic
1012

1113
/-!
1214
# The `says` tactic combinator.
@@ -57,45 +59,30 @@ def parseAsTacticSeq (env : Environment) (input : String) (fileName := "<input>"
5759
else
5860
Except.error ((s.mkError "end of input").toErrorMsg ictx)
5961

60-
/--
61-
Run `evalTactic`, capturing any new messages.
62-
The optional `only` argument allows selecting which messages should be captured,
63-
or left in the message log.
64-
-/
65-
def evalTacticCapturingMessages (tac : TSyntax `tactic) (only : Message → Bool := fun _ => true) :
66-
TacticM (List Message) := do
67-
let mut msgs ← modifyGetThe Core.State fun st => (st.messages, { st with messages := {} })
68-
try
69-
evalTactic tac
70-
let (capture, leave) := (← getThe Core.State).messages.toList.partition only
71-
msgs := leave.foldl (·.add) msgs
72-
return capture
73-
catch e =>
74-
msgs := msgs ++ (← getThe Core.State).messages
75-
throw e
76-
finally
77-
modifyThe Core.State fun st => { st with messages := msgs }
78-
79-
/--
80-
Run `evalTactic`, capturing any new info messages.
81-
-/
82-
def evalTacticCapturingInfo (tac : TSyntax `tactic) : TacticM (List Message) :=
83-
evalTacticCapturingMessages tac fun m => match m.severity with | .information => true | _ => false
84-
8562
/--
8663
Run `evalTactic`, capturing a "Try this:" message and converting it back to syntax.
8764
-/
8865
def evalTacticCapturingTryThis (tac : TSyntax `tactic) : TacticM (TSyntax ``tacticSeq) := do
89-
let msg ← match ← evalTacticCapturingInfo tac with
90-
| [] => throwError m!"Tactic `{tac}` did not produce any messages."
91-
| [msg] => msg.toString
92-
| _ => throwError m!"Tactic `{tac}` produced multiple messages."
93-
let tryThis ← match msg.dropPrefix? "Try this:" with
94-
| none => throwError m!"Tactic output did not begin with 'Try this:': {msg}"
95-
| some S => pure S.toString.removeLeadingSpaces
96-
match parseAsTacticSeq (← getEnv) tryThis.trim with
97-
| .ok stx => return stx
98-
| .error err => throwError m!"Failed to parse tactic output: {tryThis}\n{err}"
66+
let { trees, ..} ← withResetServerInfo <| evalTactic tac
67+
let suggestions := collectTryThisSuggestions trees
68+
let some s := suggestions[0]?
69+
| throwError m!"Tactic `{tac}` did not produce a 'Try this:' suggestion."
70+
let suggestion ← do
71+
if let some msg := s.messageData? then
72+
pure <| SuggestionText.string <| ← msg.toString
73+
else
74+
pure <| s.suggestion
75+
match suggestion with
76+
| .tsyntax (kind := ``tacticSeq) stx =>
77+
return stx
78+
| .tsyntax (kind := `tactic) stx =>
79+
return ← `(tacticSeq| $stx:tactic)
80+
| .tsyntax stx =>
81+
throwError m!"Tactic `{tac}` produced a 'Try this:' suggestion with a non-tactic syntax: {stx}"
82+
| .string s =>
83+
match parseAsTacticSeq (← getEnv) s with
84+
| .ok stx => return stx
85+
| .error err => throwError m!"Failed to parse 'Try this:' suggestion: {s}\n{err}"
9986

10087
/--
10188
If you write `X says`, where `X` is a tactic that produces a "Try this: Y" message,

MathlibTest/CalcQuestionMark.lean

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ When used from the widget that appears in VSCode, they insert correctly-indented
99

1010
/--
1111
info: Create calc tactic:
12-
calc
13-
1 = 1 := by sorry
12+
[apply] calc
13+
1 = 1 := by sorry
1414
---
1515
warning: declaration uses 'sorry'
1616
-/
@@ -22,8 +22,8 @@ example : 1 = 1 := by
2222

2323
/--
2424
info: Create calc tactic:
25-
calc
26-
a ≤ a := by sorry
25+
[apply] calc
26+
a ≤ a := by sorry
2727
---
2828
warning: declaration uses 'sorry'
2929
-/
@@ -35,8 +35,8 @@ example (a : Nat) : a ≤ a := by
3535

3636
/--
3737
info: Create calc tactic:
38-
calc
39-
a ≤ a := by sorry
38+
[apply] calc
39+
a ≤ a := by sorry
4040
---
4141
warning: declaration uses 'sorry'
4242
-/
@@ -48,18 +48,13 @@ example (a : Nat) : a ≤ a := by
4848
-- a deliberately long line
4949
/--
5050
info: Create calc tactic:
51-
• calc
52-
1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +
53-
1 +
54-
1 +
55-
1 +
56-
1 +
57-
1 +
58-
1 +
59-
1 +
60-
1 =
61-
8 + 8 + 8 + 8 :=
62-
by sorry
51+
[apply] calc
52+
1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +
53+
1 +
54+
1 +
55+
1 =
56+
8 + 8 + 8 + 8 :=
57+
by sorry
6358
---
6459
warning: declaration uses 'sorry'
6560
-/

MathlibTest/Change.lean

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ set_option autoImplicit true
66

77
/--
88
info: Try this:
9-
change 0 = 1
9+
[apply] change 0 = 1
1010
---
1111
info: Try this:
12-
change (fun x ↦ x) 0 = 1
12+
[apply] change (fun x ↦ x) 0 = 1
1313
---
1414
info: Try this:
15-
change (fun x ↦ x) 0 = 1
15+
[apply] change (fun x ↦ x) 0 = 1
1616
---
1717
error: The term
1818
1 = 0

MathlibTest/DeprecateTo.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ info: * Pairings:
77
88
Try this:
99
10-
/-- I also have a doc-string -/
10+
[apply] /-- I also have a doc-string -/
1111
@[to_additive /-- With its additive doc-string -/
1212
]
1313
theorem new_name_mul : True :=

0 commit comments

Comments
 (0)