@@ -46,6 +46,23 @@ register_option linter.tacticAnalysis : Bool := {
4646
4747namespace Mathlib.TacticAnalysis
4848
49+ /-- Information about a tactic in a sequence, parsed from infotrees and passed to a tactic
50+ analysis pass. -/
51+ structure TacticNode where
52+ /-- `ContextInfo` at the infotree node. -/
53+ ctxI : ContextInfo
54+ /-- `TacticInfo` at the infotree node. -/
55+ tacI : TacticInfo
56+ /-- This tactic is allowed to fail because it is in a `try`/`anyGoals`/etc block. -/
57+ mayFail : Bool
58+
59+ /-- Run tactic code, given by a piece of syntax, in the context of a tactic info node.
60+
61+ Convenience abbreviation for `ContextInfo.runTacticCode`. -/
62+ abbrev TacticNode.runTacticCode (i : TacticNode) :
63+ MVarId → Syntax → CommandElabM (List MVarId) :=
64+ i.ctxI.runTacticCode i.tacI
65+
4966/-- Stores the configuration for a tactic analysis pass.
5067
5168This provides the low-level interface into the tactic analysis framework.
@@ -55,7 +72,7 @@ structure Config where
5572 to a sequence of tactics from the source file. Should do all reporting itself,
5673 for example by `Lean.Linter.logLint`.
5774 -/
58- run : Array (ContextInfo × TacticInfo) → CommandElabM Unit
75+ run : Array TacticNode → CommandElabM Unit
5976
6077/-- The internal representation of a tactic analysis pass,
6178extending `Config` with some declaration meta-information.
@@ -145,8 +162,7 @@ would result in three sequences:
145162 Similarly, a declaration with multiple `by` blocks results in each of the blocks getting its
146163own sequence.
147164-/
148- def findTacticSeqs (tree : InfoTree) :
149- CommandElabM (Array (Array (ContextInfo × TacticInfo))) := do
165+ def findTacticSeqs (tree : InfoTree) : CommandElabM (Array (Array TacticNode)) := do
150166 -- Turn the CommandElabM into a surrounding context for traversing the tree.
151167 let ctx ← read
152168 let state ← get
@@ -177,7 +193,13 @@ def findTacticSeqs (tree : InfoTree) :
177193 -- We discard `childTactics` here, because those are either already picked up by a
178194 -- sequencing operator, or come from macros.
179195 if let .ofTacticInfo i := i then
180- return ((ctx, i), childSequences)
196+ let childSequences :=
197+ -- This tactic accepts the failure of its children.
198+ if stx.getKind ∈ [``Lean.Parser.Tactic.tacticTry_, ``Lean.Parser.Tactic.anyGoals] then
199+ childSequences.map (· |>.map fun i => { i with mayFail := true })
200+ else
201+ childSequences
202+ return (some ⟨ctx, i, false ⟩, childSequences)
181203 return (none, childSequences)
182204 else
183205 return (none, childSequences))
@@ -274,35 +296,36 @@ structure ComplexConfig where
274296/-- Test the `config` against a sequence of tactics, using the context info and tactic info
275297from the start of the sequence. -/
276298def testTacticSeq (config : ComplexConfig) (tacticSeq : Array (TSyntax `tactic))
277- (ctxI : ContextInfo) ( i : TacticInfo ) (ctx : config.ctx) :
299+ (i : TacticNode ) (ctx : config.ctx) :
278300 CommandElabM Unit := do
279301 /- Syntax quotations use the current ref's position info even for nodes which do not usually
280302 carry position info. We set the ref here to ensure we log messages on the correct range. -/
281303 withRef (mkNullNode tacticSeq) do
282304 let stx ← `(tactic| $tacticSeq;*)
283305 -- TODO: support more than 1 goal. Probably by requiring all tests to succeed in a row
284- if let [goal] := i.goalsBefore then
306+ if let [goal] := i.tacI. goalsBefore then
285307 let (oldGoals, oldHeartbeats) ← withHeartbeats <|
286308 try
287- ctxI .runTacticCode i goal stx
309+ i .runTacticCode goal stx
288310 catch e =>
289- logWarning m!"original tactic '{stx}' failed: {e.toMessageData}"
311+ if !i.mayFail then
312+ logWarning m!"original tactic '{stx}' failed: {e.toMessageData}"
290313 return [goal]
291- let (new, newHeartbeats) ← withHeartbeats <| ctxI.runTactic i goal <| config.test ctx
314+ let (new, newHeartbeats) ← withHeartbeats <| i. ctxI.runTactic i.tacI goal <| config.test ctx
292315 if let some msg ← config.tell stx oldGoals oldHeartbeats new newHeartbeats then
293316 logWarning msg
294317
295318/-- Run the `config` against a sequence of tactics, using the `trigger` to determine which
296319subsequences should be `test`ed. -/
297- def runPass (config : ComplexConfig) (seq : Array (ContextInfo × TacticInfo) ) :
320+ def runPass (config : ComplexConfig) (seq : Array TacticNode ) :
298321 CommandElabM Unit := do
299322 let mut acc := none
300323 let mut firstInfo := none
301324 let mut tacticSeq := #[]
302- for (ctxI, i) in seq do
325+ for i in seq do
303326 if firstInfo.isNone then
304- firstInfo := some (ctxI, i)
305- let stx : TSyntax `tactic := ⟨i.stx⟩
327+ firstInfo := some i
328+ let stx : TSyntax `tactic := ⟨i.tacI. stx⟩
306329 tacticSeq := tacticSeq.push stx
307330 match config.trigger acc stx with
308331 | .continue ctx =>
@@ -312,16 +335,16 @@ def runPass (config : ComplexConfig) (seq : Array (ContextInfo × TacticInfo)) :
312335 tacticSeq := #[]
313336 firstInfo := none
314337 | .accept ctx =>
315- if let some (ctxI, i) := firstInfo then
316- testTacticSeq config tacticSeq ctxI i ctx
338+ if let some i := firstInfo then
339+ testTacticSeq config tacticSeq i ctx
317340 else
318341 logWarningAt stx m!"internal error in tactic analysis: accepted an empty sequence."
319342 acc := none
320343 -- Insert a `done` at the end so we can handle a final `.continue` at the end.
321344 match config.trigger acc (← `(tactic| done)) with
322345 | .accept ctx =>
323- if let some (ctxI, i) := firstInfo then
324- testTacticSeq config tacticSeq ctxI i ctx
346+ if let some i := firstInfo then
347+ testTacticSeq config tacticSeq i ctx
325348 | _ => pure ()
326349
327350/-- Constructor for a `Config` which breaks the pass up into multiple pieces. -/
0 commit comments