InfoTree Anatomy

InfoTree is a data structure in Lean 4 that records each tactic, the state before each tactic was applied (goals and hypotheses before) and the state after each tactic was applied (goals and hypotheses after). In Lean 4, parsing the InfoTree is the only way to get a list of tactics and their corresponding effect on the proof.

Taking a look into the general shape of the InfoTree

Suppose we're parsing the InfoTree of theorem obvs from the screenshot below. We can pretty-print the InfoTree of this proof by writing dbg_trace <- snapshot.infoTree.format, going to the VSCode's Output pane, and selecting the Lean:Editor output channel.

Pretty-printing the InfoTree will get you the following 79-line output:

Click to expand JSON
• command @ ⟨4, 0⟩-⟨6, 12⟩ @ Lean.Elab.Command.elabDeclaration
  • Prop : Type @ ⟨4, 20⟩-⟨4, 24⟩ @ Lean.Elab.Term.elabProp
  • a (isBinder := true) : Prop @ ⟨4, 14⟩-⟨4, 15⟩
  • Prop : Type @ ⟨4, 20⟩-⟨4, 24⟩ @ Lean.Elab.Term.elabProp
  • b (isBinder := true) : Prop @ ⟨4, 16⟩-⟨4, 17⟩
  • a ∧ b → b : Prop @ ⟨4, 28⟩-⟨4, 37⟩ @ Lean.Elab.Term.elabArrow
    • a ∧ b : Prop @ ⟨4, 28⟩-⟨4, 33⟩ @ «_aux_Init_Notation___macroRules_term_∧__1»
      • Macro expansion
        a ∧ b
        ===>
        And✝ a b
        • a ∧ b : Prop @ ⟨4, 28⟩†-⟨4, 33⟩† @ Lean.Elab.Term.elabApp
          • [.] And✝ : some Sort.{?_uniq.6} @ ⟨4, 28⟩†-⟨4, 33⟩†
          • And : Prop → Prop → Prop @ ⟨4, 28⟩†-⟨4, 33⟩†
          • a : Prop @ ⟨4, 28⟩-⟨4, 29⟩ @ Lean.Elab.Term.elabIdent
            • [.] a : some Prop @ ⟨4, 28⟩-⟨4, 29⟩
            • a : Prop @ ⟨4, 28⟩-⟨4, 29⟩
          • b : Prop @ ⟨4, 32⟩-⟨4, 33⟩ @ Lean.Elab.Term.elabIdent
            • [.] b : some Prop @ ⟨4, 32⟩-⟨4, 33⟩
            • b : Prop @ ⟨4, 32⟩-⟨4, 33⟩
    • b : Prop @ ⟨4, 36⟩-⟨4, 37⟩ @ Lean.Elab.Term.elabIdent
      • [.] b : some Sort.{?_uniq.7} @ ⟨4, 36⟩-⟨4, 37⟩
      • b : Prop @ ⟨4, 36⟩-⟨4, 37⟩
  • obvs (isBinder := true) : ∀ (a b : Prop), a ∧ b → b @ ⟨4, 8⟩-⟨4, 12⟩
  • a (isBinder := true) : Prop @ ⟨4, 14⟩-⟨4, 15⟩
  • b (isBinder := true) : Prop @ ⟨4, 16⟩-⟨4, 17⟩
  • Tactic @ ⟨4, 41⟩-⟨6, 12⟩
    (Term.byTactic
     "by"
     (Tactic.tacticSeq
      (Tactic.tacticSeq1Indented
       [(Tactic.intro "intro" [`ab]) [] (Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))])))
    before 
    a b : Prop
    ⊢ a ∧ b → b
    after no goals
    • Tactic @ ⟨4, 41⟩-⟨4, 43⟩
      "by"
      before 
      a b : Prop
      ⊢ a ∧ b → b
      after no goals
      • Tactic @ ⟨5, 2⟩-⟨6, 12⟩ @ Lean.Elab.Tactic.evalTacticSeq
        (Tactic.tacticSeq
         (Tactic.tacticSeq1Indented
          [(Tactic.intro "intro" [`ab]) [] (Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))]))
        before 
        a b : Prop
        ⊢ a ∧ b → b
        after no goals
        • Tactic @ ⟨5, 2⟩-⟨6, 12⟩ @ Lean.Elab.Tactic.evalTacticSeq1Indented
          (Tactic.tacticSeq1Indented
           [(Tactic.intro "intro" [`ab]) [] (Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))])
          before 
          a b : Prop
          ⊢ a ∧ b → b
          after no goals
          • Tactic @ ⟨5, 2⟩-⟨5, 10⟩ @ Lean.Elab.Tactic.evalIntro
            (Tactic.intro "intro" [`ab])
            before 
            a b : Prop
            ⊢ a ∧ b → b
            after 
            a b : Prop
            ab : a ∧ b
            ⊢ b
            • ab (isBinder := true) : a ∧ b @ ⟨5, 8⟩-⟨5, 10⟩
          • Tactic @ ⟨6, 2⟩-⟨6, 12⟩ @ Lean.Elab.Tactic.evalExact
            (Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))
            before 
            a b : Prop
            ab : a ∧ b
            ⊢ b
            after no goals
            • ab.right : b @ ⟨6, 8⟩-⟨6, 12⟩ @ Lean.Elab.Term.elabProj
              • [.] ab : some _uniq.10 @ ⟨6, 8⟩-⟨6, 10⟩
              • ab : a ∧ b @ ⟨6, 8⟩-⟨6, 10⟩
              • @And.right : ∀ {a b : Prop}, a ∧ b → b @ ⟨6, 11⟩-⟨6, 12⟩
  • obvs (isBinder := true) : ∀ (a b : Prop), a ∧ b → b @ ⟨4, 8⟩-⟨4, 12⟩

This output starts from the command @ ⟨4, 0⟩-⟨6, 12⟩ @ Lean.Elab.Command.elabDeclaration line. This is expected, because the command syntax category in Lean 4 includes traditional commands (#check, #explode, etc.), import statements, and, finally, theorem statements (our case).
As can be guessed, the numbers in brackets indicate line and character numbers - this line tells us our snapshot consists of a command, and this command spans from ⟨line 4, character 0⟩ to ⟨line 6, character 12⟩.

Nested in this command node you see the various constituents of this command, sometimes repeated, and in no particular order: Prop, a, Prop, b, a ∧ b → b, obvs, a, b, Tactic ["by", "intro ab", "exact ab.2"], obvs. Notice how all of the root node children have ⟨line, character⟩ pairs that are nested under the root's ⟨4, 0⟩-⟨6, 12⟩ position - this pattern will repeat throughout the InfoTree, but doesn't always hold.

Parsing each InfoTree.node

Below you see the InfoTree data structure as defined in Lean source code.

inductive InfoTree where
  | context (i : PartialContextInfo) (t : InfoTree)
  | node    (i : Info) (children : PersistentArray InfoTree)
  | hole    (mvarId : MVarId)

inductive Info where
  | ofTacticInfo      (i : TacticInfo)
  | ofTermInfo        (i : TermInfo)
  | ofCommandInfo     (i : CommandInfo)
  | ofMacroExpansionInfo (i : MacroExpansionInfo)
  | ofOptionInfo      (i : OptionInfo)
  | ofFieldInfo       (i : FieldInfo)
  | ofCompletionInfo  (i : CompletionInfo)
  | ofUserWidgetInfo  (i : UserWidgetInfo)
  | ofCustomInfo      (i : CustomInfo)
  | ofFVarAliasInfo   (i : FVarAliasInfo)
  | ofFieldRedeclInfo (i : FieldRedeclInfo)
  | ofOmissionInfo    (i : OmissionInfo)

Whenever you see a new • in the InfoTree pretty-printing output, this indicates a new InfoTree.node.
All types of nodes (Info.ofTacticInfo, Info.ofTermInfo, Info.ofCommandInfo) are pretty-printed in different ways, however there are some commonalities. Let's list some examples of how to parse this output, going from the first line in our pretty-printed InfoTree.

CommandInfo

command @ {position}     @ {?elaborator}
• command @ ⟨4, 0⟩-⟨6, 12⟩ @ Lean.Elab.Command.elabDeclaration

When the line starts from command, it means we're deadling with Info.ofCommandInfo.
The way CommandInfo is printed is as follows:

command @
{formatStxRange ctx info.stx} @
{if info.elaborator.isAnonymous then "" else info.elaborator}

TermInfo

• {expr} {?isBinder}   : {type} @ {position}      @ {?elaborator}
• Prop                 : Type   @ ⟨4, 20⟩-⟨4, 24⟩ @ Lean.Elab.Term.elabProp
• a (isBinder := true) : Prop   @ ⟨4, 14⟩-⟨4, 15⟩
• Prop                 : Type   @ ⟨4, 20⟩-⟨4, 24⟩ @ Lean.Elab.Term.elabProp  
• b (isBinder := true) : Prop   @ ⟨4, 16⟩-⟨4, 17⟩
• a ∧ b → b            : Prop   @ ⟨4, 28⟩-⟨4, 37⟩ @ Lean.Elab.Term.elabArrow
When the line starts from what it is (in our case these are, respectively, Prop, a, Prop, b), it means we're dealing with Info.ofTermInfo.
The way TermInfo is printed is as follows:

{← Meta.ppExpr info.expr}
{if info.isBinder then "(isBinder := true) " else ""}:
{inferredType} @
{formatStxRange ctx info.stx} @
{if info.elaborator.isAnonymous then "" else info.elaborator}"

TacticInfo

• Tactic @ {position} @ {?elaborator}
  {syntax}
  before
  {goalsBefore}
  after
  {goalsAfter}

• Tactic @ ⟨4, 41⟩-⟨6, 12⟩
  (Term.byTactic "by" (Tactic.tacticSeq (Tactic.tacticSeq1Indented [(Tactic.intro "intro" [`ab]) [] (Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))])))
  before 
  a b : Prop
  ⊢ a ∧ b → b
  after
  no goals

Finally, we come to the most interesting InfoTree.node - TacticInfo, we'll dedicate the next section to this node. 
The way TacticInfo is printed is as follows:

Tactic @
{formatStxRange ctx info.stx} @
{if info.elaborator.isAnonymous then "" else info.elaborator}" \n
{info.stx} \n
before \n
{goalsBefore} \n
after \n
{goalsAfter}

TacticInfo: the most interesting InfoTree node

As can be seen from the InfoTree inductive, each node consists of Info, and, optionally, children (an array of other InfoTrees).

inductive InfoTree where
  | context (i : PartialContextInfo) (t : InfoTree)
  | node    (i : Info) (children : PersistentArray InfoTree)
  | hole    (mvarId : MVarId)

In case with TacticInfo, this array of children is typically an array of other tactic nodes ([TacticInfo, TacticInfo, TacticInfo]).

Taking our first TacticInfo as an example (I shortened the {syntax} part to make it more explicit), we can see it represents some kind of a fake mega-tactic consisting of all of our real tactics, and telling us the proof state before and after all of our real tactics were applied.

Tactic @ ⟨4, 41⟩-⟨6, 12⟩
["by", "intro ab", "exact ab.2"]
before
a b : Prop
⊢ a ∧ b → b
after
no goals

Let's draw a tree indicating the children and descendants of this fake mega-tactic.

You can see that the real actual tactics that we are generally after are the leaf nodes in the InfoTree.
The obvious question is - should we always just go for the leaf nodes, are intermediate nodes useful for something?

Fancy proofs

Let complicate our proof a bit by inserting a have:

theorem obvs (a b : Prop) : a ∧ b → b := by
  intro ab
  have just_b := by
    exact ab.2
  exact just_b

Pretty-printing the InfoTree of this proof will get you the following 512-line output:

Click to expand JSON
• command @ ⟨6, 0⟩-⟨10, 14⟩ @ Lean.Elab.Command.elabDeclaration
  • Prop : Type @ ⟨6, 20⟩-⟨6, 24⟩ @ Lean.Elab.Term.elabProp
  • a (isBinder := true) : Prop @ ⟨6, 14⟩-⟨6, 15⟩
  • Prop : Type @ ⟨6, 20⟩-⟨6, 24⟩ @ Lean.Elab.Term.elabProp
  • b (isBinder := true) : Prop @ ⟨6, 16⟩-⟨6, 17⟩
  • a ∧ b → b : Prop @ ⟨6, 28⟩-⟨6, 37⟩ @ Lean.Elab.Term.elabArrow
    • a ∧ b : Prop @ ⟨6, 28⟩-⟨6, 33⟩ @ «_aux_Init_Notation___macroRules_term_∧__1»
      • Macro expansion
        a ∧ b
        ===>
        And✝ a b
        • a ∧ b : Prop @ ⟨6, 28⟩†-⟨6, 33⟩† @ Lean.Elab.Term.elabApp
          • [.] And✝ : some Sort.{?_uniq.6} @ ⟨6, 28⟩†-⟨6, 33⟩†
          • And : Prop → Prop → Prop @ ⟨6, 28⟩†-⟨6, 33⟩†
          • a : Prop @ ⟨6, 28⟩-⟨6, 29⟩ @ Lean.Elab.Term.elabIdent
            • [.] a : some Prop @ ⟨6, 28⟩-⟨6, 29⟩
            • a : Prop @ ⟨6, 28⟩-⟨6, 29⟩
          • b : Prop @ ⟨6, 32⟩-⟨6, 33⟩ @ Lean.Elab.Term.elabIdent
            • [.] b : some Prop @ ⟨6, 32⟩-⟨6, 33⟩
            • b : Prop @ ⟨6, 32⟩-⟨6, 33⟩
    • b : Prop @ ⟨6, 36⟩-⟨6, 37⟩ @ Lean.Elab.Term.elabIdent
      • [.] b : some Sort.{?_uniq.7} @ ⟨6, 36⟩-⟨6, 37⟩
      • b : Prop @ ⟨6, 36⟩-⟨6, 37⟩
  • obvs (isBinder := true) : ∀ (a b : Prop), a ∧ b → b @ ⟨6, 8⟩-⟨6, 12⟩
  • a (isBinder := true) : Prop @ ⟨6, 14⟩-⟨6, 15⟩
  • b (isBinder := true) : Prop @ ⟨6, 16⟩-⟨6, 17⟩
  • Tactic @ ⟨6, 41⟩-⟨10, 14⟩
    (Term.byTactic
     "by"
     (Tactic.tacticSeq
      (Tactic.tacticSeq1Indented
       [(Tactic.intro "intro" [`ab])
        []
        (Tactic.tacticHave_
         "have"
         (Term.haveDecl
          (Term.haveIdDecl
           (Term.haveId `just_b)
           []
           []
           ":="
           (Term.byTactic
            "by"
            (Tactic.tacticSeq
             (Tactic.tacticSeq1Indented [(Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))]))))))
        []
        (Tactic.exact "exact" `just_b)])))
    before 
    a b : Prop
    ⊢ a ∧ b → b
    after no goals
    • Tactic @ ⟨6, 41⟩-⟨6, 43⟩
      "by"
      before 
      a b : Prop
      ⊢ a ∧ b → b
      after no goals
      • Tactic @ ⟨7, 2⟩-⟨10, 14⟩ @ Lean.Elab.Tactic.evalTacticSeq
        (Tactic.tacticSeq
         (Tactic.tacticSeq1Indented
          [(Tactic.intro "intro" [`ab])
           []
           (Tactic.tacticHave_
            "have"
            (Term.haveDecl
             (Term.haveIdDecl
              (Term.haveId `just_b)
              []
              []
              ":="
              (Term.byTactic
               "by"
               (Tactic.tacticSeq
                (Tactic.tacticSeq1Indented [(Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))]))))))
           []
           (Tactic.exact "exact" `just_b)]))
        before 
        a b : Prop
        ⊢ a ∧ b → b
        after no goals
        • Tactic @ ⟨7, 2⟩-⟨10, 14⟩ @ Lean.Elab.Tactic.evalTacticSeq1Indented
          (Tactic.tacticSeq1Indented
           [(Tactic.intro "intro" [`ab])
            []
            (Tactic.tacticHave_
             "have"
             (Term.haveDecl
              (Term.haveIdDecl
               (Term.haveId `just_b)
               []
               []
               ":="
               (Term.byTactic
                "by"
                (Tactic.tacticSeq
                 (Tactic.tacticSeq1Indented [(Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))]))))))
            []
            (Tactic.exact "exact" `just_b)])
          before 
          a b : Prop
          ⊢ a ∧ b → b
          after no goals
          • Tactic @ ⟨7, 2⟩-⟨7, 10⟩ @ Lean.Elab.Tactic.evalIntro
            (Tactic.intro "intro" [`ab])
            before 
            a b : Prop
            ⊢ a ∧ b → b
            after 
            a b : Prop
            ab : a ∧ b
            ⊢ b
            • ab (isBinder := true) : a ∧ b @ ⟨7, 8⟩-⟨7, 10⟩
          • Tactic @ ⟨8, 2⟩-⟨9, 14⟩ @ Lean.Parser.Tactic._aux_Init_Tactics___macroRules_Lean_Parser_Tactic_tacticHave__1
            (Tactic.tacticHave_
             "have"
             (Term.haveDecl
              (Term.haveIdDecl
               (Term.haveId `just_b)
               []
               []
               ":="
               (Term.byTactic
                "by"
                (Tactic.tacticSeq
                 (Tactic.tacticSeq1Indented [(Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))]))))))
            before 
            a b : Prop
            ab : a ∧ b
            ⊢ b
            after 
            a b : Prop
            ab : a ∧ b
            just_b : b
            ⊢ b
            • Tactic @ ⟨8, 2⟩†-⟨9, 14⟩† @ Lean.Parser.Tactic._aux_Init_Tactics___macroRules_Lean_Parser_Tactic_tacticRefine_lift__1
              (Tactic.tacticRefine_lift_
               "refine_lift"
               (Term.have
                "have"
                (Term.haveDecl
                 (Term.haveIdDecl
                  (Term.haveId `just_b)
                  []
                  []
                  ":="
                  (Term.byTactic
                   "by"
                   (Tactic.tacticSeq
                    (Tactic.tacticSeq1Indented [(Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))])))))
                ";"
                (Term.syntheticHole "?" "_")))
              before 
              a b : Prop
              ab : a ∧ b
              ⊢ b
              after 
              a b : Prop
              ab : a ∧ b
              just_b : b
              ⊢ b
              • Tactic @ ⟨8, 2⟩†-⟨9, 14⟩† @ Lean.Elab.Tactic.evalFocus
                (Tactic.focus
                 "focus"
                 (Tactic.tacticSeq
                  (Tactic.tacticSeq1Indented
                   [(Tactic.paren
                     "("
                     (Tactic.tacticSeq
                      (Tactic.tacticSeq1Indented
                       [(Tactic.refine
                         "refine"
                         (Term.noImplicitLambda
                          "no_implicit_lambda%"
                          (Term.have
                           "have"
                           (Term.haveDecl
                            (Term.haveIdDecl
                             (Term.haveId `just_b)
                             []
                             []
                             ":="
                             (Term.byTactic
                              "by"
                              (Tactic.tacticSeq
                               (Tactic.tacticSeq1Indented
                                [(Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))])))))
                           ";"
                           (Term.syntheticHole "?" "_"))))
                        ";"
                        (Tactic.rotateRight "rotate_right" [])]))
                     ")")])))
                before 
                a b : Prop
                ab : a ∧ b
                ⊢ b
                after 
                a b : Prop
                ab : a ∧ b
                just_b : b
                ⊢ b
                • Tactic @ ⟨8, 2⟩†-⟨9, 14⟩† @ Lean.Elab.Tactic.evalFocus
                  "focus"
                  before 
                  a b : Prop
                  ab : a ∧ b
                  ⊢ b
                  after 
                  a b : Prop
                  ab : a ∧ b
                  ⊢ b
                • Tactic @ ⟨8, 2⟩†-⟨9, 14⟩† @ Lean.Elab.Tactic.evalTacticSeq
                  (Tactic.tacticSeq
                   (Tactic.tacticSeq1Indented
                    [(Tactic.paren
                      "("
                      (Tactic.tacticSeq
                       (Tactic.tacticSeq1Indented
                        [(Tactic.refine
                          "refine"
                          (Term.noImplicitLambda
                           "no_implicit_lambda%"
                           (Term.have
                            "have"
                            (Term.haveDecl
                             (Term.haveIdDecl
                              (Term.haveId `just_b)
                              []
                              []
                              ":="
                              (Term.byTactic
                               "by"
                               (Tactic.tacticSeq
                                (Tactic.tacticSeq1Indented
                                 [(Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))])))))
                            ";"
                            (Term.syntheticHole "?" "_"))))
                         ";"
                         (Tactic.rotateRight "rotate_right" [])]))
                      ")")]))
                  before 
                  a b : Prop
                  ab : a ∧ b
                  ⊢ b
                  after 
                  a b : Prop
                  ab : a ∧ b
                  just_b : b
                  ⊢ b
                  • Tactic @ ⟨8, 2⟩†-⟨9, 14⟩† @ Lean.Elab.Tactic.evalTacticSeq1Indented
                    (Tactic.tacticSeq1Indented
                     [(Tactic.paren
                       "("
                       (Tactic.tacticSeq
                        (Tactic.tacticSeq1Indented
                         [(Tactic.refine
                           "refine"
                           (Term.noImplicitLambda
                            "no_implicit_lambda%"
                            (Term.have
                             "have"
                             (Term.haveDecl
                              (Term.haveIdDecl
                               (Term.haveId `just_b)
                               []
                               []
                               ":="
                               (Term.byTactic
                                "by"
                                (Tactic.tacticSeq
                                 (Tactic.tacticSeq1Indented
                                  [(Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))])))))
                             ";"
                             (Term.syntheticHole "?" "_"))))
                          ";"
                          (Tactic.rotateRight "rotate_right" [])]))
                       ")")])
                    before 
                    a b : Prop
                    ab : a ∧ b
                    ⊢ b
                    after 
                    a b : Prop
                    ab : a ∧ b
                    just_b : b
                    ⊢ b
                    • Tactic @ ⟨8, 2⟩†-⟨9, 14⟩† @ Lean.Elab.Tactic.evalParen
                      (Tactic.paren
                       "("
                       (Tactic.tacticSeq
                        (Tactic.tacticSeq1Indented
                         [(Tactic.refine
                           "refine"
                           (Term.noImplicitLambda
                            "no_implicit_lambda%"
                            (Term.have
                             "have"
                             (Term.haveDecl
                              (Term.haveIdDecl
                               (Term.haveId `just_b)
                               []
                               []
                               ":="
                               (Term.byTactic
                                "by"
                                (Tactic.tacticSeq
                                 (Tactic.tacticSeq1Indented
                                  [(Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))])))))
                             ";"
                             (Term.syntheticHole "?" "_"))))
                          ";"
                          (Tactic.rotateRight "rotate_right" [])]))
                       ")")
                      before 
                      a b : Prop
                      ab : a ∧ b
                      ⊢ b
                      after 
                      a b : Prop
                      ab : a ∧ b
                      just_b : b
                      ⊢ b
                      • Tactic @ ⟨8, 2⟩†-⟨9, 14⟩† @ Lean.Elab.Tactic.evalTacticSeq
                        (Tactic.tacticSeq
                         (Tactic.tacticSeq1Indented
                          [(Tactic.refine
                            "refine"
                            (Term.noImplicitLambda
                             "no_implicit_lambda%"
                             (Term.have
                              "have"
                              (Term.haveDecl
                               (Term.haveIdDecl
                                (Term.haveId `just_b)
                                []
                                []
                                ":="
                                (Term.byTactic
                                 "by"
                                 (Tactic.tacticSeq
                                  (Tactic.tacticSeq1Indented
                                   [(Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))])))))
                              ";"
                              (Term.syntheticHole "?" "_"))))
                           ";"
                           (Tactic.rotateRight "rotate_right" [])]))
                        before 
                        a b : Prop
                        ab : a ∧ b
                        ⊢ b
                        after 
                        a b : Prop
                        ab : a ∧ b
                        just_b : b
                        ⊢ b
                        • Tactic @ ⟨8, 2⟩†-⟨9, 14⟩† @ Lean.Elab.Tactic.evalTacticSeq1Indented
                          (Tactic.tacticSeq1Indented
                           [(Tactic.refine
                             "refine"
                             (Term.noImplicitLambda
                              "no_implicit_lambda%"
                              (Term.have
                               "have"
                               (Term.haveDecl
                                (Term.haveIdDecl
                                 (Term.haveId `just_b)
                                 []
                                 []
                                 ":="
                                 (Term.byTactic
                                  "by"
                                  (Tactic.tacticSeq
                                   (Tactic.tacticSeq1Indented
                                    [(Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))])))))
                               ";"
                               (Term.syntheticHole "?" "_"))))
                            ";"
                            (Tactic.rotateRight "rotate_right" [])])
                          before 
                          a b : Prop
                          ab : a ∧ b
                          ⊢ b
                          after 
                          a b : Prop
                          ab : a ∧ b
                          just_b : b
                          ⊢ b
                          • Tactic @ ⟨8, 2⟩†-⟨9, 14⟩† @ Lean.Elab.Tactic.evalRefine
                            (Tactic.refine
                             "refine"
                             (Term.noImplicitLambda
                              "no_implicit_lambda%"
                              (Term.have
                               "have"
                               (Term.haveDecl
                                (Term.haveIdDecl
                                 (Term.haveId `just_b)
                                 []
                                 []
                                 ":="
                                 (Term.byTactic
                                  "by"
                                  (Tactic.tacticSeq
                                   (Tactic.tacticSeq1Indented
                                    [(Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))])))))
                               ";"
                               (Term.syntheticHole "?" "_"))))
                            before 
                            a b : Prop
                            ab : a ∧ b
                            ⊢ b
                            after 
                            a b : Prop
                            ab : a ∧ b
                            just_b : b
                            ⊢ b
                            • let_fun just_b := ab.right;
                              ?m.20 just_b : b @ ⟨8, 2⟩†-⟨9, 14⟩† @ Lean.Elab.Term.elabNoImplicitLambda
                              • let_fun just_b := ab.right;
                                ?m.20 just_b : b @ ⟨8, 2⟩†-⟨9, 14⟩† @ Lean.Elab.Term.expandHave
                                • Macro expansion
                                  have just_b := by exact ab.2;
                                  ?_
                                  ===>
                                  let_fun just_b := by exact ab.2;
                                  ?_
                                  • let_fun just_b := ab.right;
                                    ?m.20 just_b : b @ ⟨8, 2⟩†-⟨9, 14⟩† @ Lean.Elab.Term.elabLetFunDecl
                                    • b : Prop @ ⟨8, 7⟩†-⟨8, 13⟩† @ Lean.Elab.Term.elabHole
                                    • Tactic @ ⟨8, 17⟩-⟨9, 14⟩
                                      (Term.byTactic
                                       "by"
                                       (Tactic.tacticSeq
                                        (Tactic.tacticSeq1Indented
                                         [(Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))])))
                                      before 
                                      a b : Prop
                                      ab : a ∧ b
                                      ⊢ ?m.16
                                      after no goals
                                      • Tactic @ ⟨8, 17⟩-⟨8, 19⟩
                                        "by"
                                        before 
                                        a b : Prop
                                        ab : a ∧ b
                                        ⊢ ?m.16
                                        after no goals
                                        • Tactic @ ⟨9, 4⟩-⟨9, 14⟩ @ Lean.Elab.Tactic.evalTacticSeq
                                          (Tactic.tacticSeq
                                           (Tactic.tacticSeq1Indented
                                            [(Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))]))
                                          before 
                                          a b : Prop
                                          ab : a ∧ b
                                          ⊢ ?m.16
                                          after no goals
                                          • Tactic @ ⟨9, 4⟩-⟨9, 14⟩ @ Lean.Elab.Tactic.evalTacticSeq1Indented
                                            (Tactic.tacticSeq1Indented
                                             [(Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))])
                                            before 
                                            a b : Prop
                                            ab : a ∧ b
                                            ⊢ ?m.16
                                            after no goals
                                            • Tactic @ ⟨9, 4⟩-⟨9, 14⟩ @ Lean.Elab.Tactic.evalExact
                                              (Tactic.exact "exact" (Term.proj `ab "." (fieldIdx "2")))
                                              before 
                                              a b : Prop
                                              ab : a ∧ b
                                              ⊢ ?m.16
                                              after no goals
                                              • ab.right : b @ ⟨9, 10⟩-⟨9, 14⟩ @ Lean.Elab.Term.elabProj
                                                • [.] ab : some ?_uniq.16 @ ⟨9, 10⟩-⟨9, 12⟩
                                                • ab : a ∧ b @ ⟨9, 10⟩-⟨9, 12⟩
                                                • @And.right : ∀ {a b : Prop}, a ∧ b → b @ ⟨9, 13⟩-⟨9, 14⟩
                                    • just_b (isBinder := true) : b @ ⟨8, 7⟩-⟨8, 13⟩
                                    • ?m.19 : b @ ⟨8, 2⟩†-⟨9, 14⟩† @ Lean.Elab.Term.elabSyntheticHole
                          • Tactic @ ⟨8, 2⟩†-⟨9, 14⟩† @ Lean.Elab.Tactic.evalTacticSeq1Indented
                            ";"
                            before 
                            a b : Prop
                            ab : a ∧ b
                            just_b : b
                            ⊢ b
                            after 
                            a b : Prop
                            ab : a ∧ b
                            just_b : b
                            ⊢ b
                          • Tactic @ ⟨8, 2⟩†-⟨9, 14⟩† @ Lean.Elab.Tactic.evalRotateRight
                            (Tactic.rotateRight "rotate_right" [])
                            before 
                            a b : Prop
                            ab : a ∧ b
                            just_b : b
                            ⊢ b
                            after 
                            a b : Prop
                            ab : a ∧ b
                            just_b : b
                            ⊢ b
          • Tactic @ ⟨10, 2⟩-⟨10, 14⟩ @ Lean.Elab.Tactic.evalExact
            (Tactic.exact "exact" `just_b)
            before 
            a b : Prop
            ab : a ∧ b
            just_b : b
            ⊢ b
            after no goals
            • just_b : b @ ⟨10, 8⟩-⟨10, 14⟩ @ Lean.Elab.Term.elabIdent
              • [.] just_b : some [mdata noImplicitLambda:1 _uniq.10] @ ⟨10, 8⟩-⟨10, 14⟩
              • just_b : b @ ⟨10, 8⟩-⟨10, 14⟩
  • obvs (isBinder := true) : ∀ (a b : Prop), a ∧ b → b @ ⟨6, 8⟩-⟨6, 12⟩

Stripping away excessive nesting, we get to the following point - the fake all-encompassing mega-tactic has 3 TacticInfo children: intro ab, have just_b := ..., and exact just_b.

You can see the cleaned-up data structure here:

Click to expand JSON
• Tactic @ ⟨7, 2⟩-⟨10, 14⟩ @ [intro ab, have just_b := by exact ab.2; exact just_b] @ Lean.Elab.Tactic.evalTacticSeq1Indented
  before 
  a b : Prop
  ⊢ a ∧ b → b
  after
  no goals
  • Tactic @ ⟨7, 2⟩-⟨7, 10⟩   @ intro ab                     @ Lean.Elab.Tactic.evalIntro
    before 
    a b : Prop
    ⊢ a ∧ b → b
    after 
    a b : Prop
    ab : a ∧ b
    ⊢ b
  • Tactic @ ⟨8, 2⟩-⟨9, 14⟩   @ have just_b := by exact ab.2 @ Lean.Parser.Tactic._aux_Init_Tactics___macroRules_Lean_Parser_Tactic_tacticHave__1
    before 
    a b : Prop
    ab : a ∧ b
    ⊢ b
    after 
    a b : Prop
    ab : a ∧ b
    just_b : b
    ⊢ b
    • Tactic @ ⟨8, 2⟩-⟨9, 14⟩ @ [refine_lift have just_b := by exact ab.2] @ Lean.Parser.Tactic._aux_Init_Tactics___macroRules_Lean_Parser_Tactic_tacticRefine_lift__1
      before 
      a b : Prop
      ab : a ∧ b
      ⊢ b
      after 
      a b : Prop
      ab : a ∧ b
      just_b : b
      ⊢ b
      • Tactic @ ⟨8, 2⟩-⟨9, 14⟩ @ focus [(refine no_implicit_lambda have just_b := by exact ab.2 _, rotate_right] @ Lean.Elab.Tactic.evalFocus
        before 
        a b : Prop
        ab : a ∧ b
        ⊢ b
        after 
        a b : Prop
        ab : a ∧ b
        just_b : b
        ⊢ b
        • Tactic @ ⟨8, 2⟩-⟨9, 14⟩ @ focus @ Lean.Elab.Tactic.evalFocus
          before 
          a b : Prop
          ab : a ∧ b
          ⊢ b
          after 
          a b : Prop
          ab : a ∧ b
          ⊢ b
        • Tactic @ ⟨8, 2⟩-⟨9, 14⟩ @ [refine no_implicit_lambda, have just_b := by exact ab.2; rotate_right] @ Lean.Elab.Tactic.evalTacticSeq
          before 
          a b : Prop
          ab : a ∧ b
          ⊢ b
          after 
          a b : Prop
          ab : a ∧ b
          just_b : b
          ⊢ b
          • Tactic @ ⟨8, 2⟩-⟨9, 14⟩ @ [(refine no_imlicit_lambda have just_b := by exact ab.2 _, rotate_right)] @ Lean.Elab.Tactic.evalTacticSeq1Indented
            before 
            a b : Prop
            ab : a ∧ b
            ⊢ b
            after 
            a b : Prop
            ab : a ∧ b
            just_b : b
            ⊢ b
            • Tactic @ ⟨8, 2⟩-⟨9, 14⟩ @ [(refine no_implicit_lambda have just_b := by exact ab.2 _, rotate_right)] @ Lean.Elab.Tactic.evalParen
              before 
              a b : Prop
              ab : a ∧ b
              ⊢ b
              after 
              a b : Prop
              ab : a ∧ b
              just_b : b
              ⊢ b
              • Tactic @ ⟨8, 2⟩-⟨9, 14⟩ @ [refine no_implicit_lambda have just_b := by exact ab.2 _, rotate_right] @ Lean.Elab.Tactic.evalTacticSeq
                before 
                a b : Prop
                ab : a ∧ b
                ⊢ b
                after 
                a b : Prop
                ab : a ∧ b
                just_b : b
                ⊢ b
                • Tactic @ ⟨8, 2⟩-⟨9, 14⟩ @ [refine no_implicit_lambda have just_b := by exact ab.2 _, rotate_right] @ Lean.Elab.Tactic.evalTacticSeq1Indented
                  before 
                  a b : Prop
                  ab : a ∧ b
                  ⊢ b
                  after 
                  a b : Prop
                  ab : a ∧ b
                  just_b : b
                  ⊢ b
                  • Tactic @ ⟨8, 2⟩-⟨9, 14⟩ @ [refine no_implicit_lambda have := by exact ab.2 _] @ Lean.Elab.Tactic.evalRefine
                    before 
                    a b : Prop
                    ab : a ∧ b
                    ⊢ b
                    after 
                    a b : Prop
                    ab : a ∧ b
                    just_b : b
                    ⊢ b
                    • Tactic @ ⟨8, 17⟩-⟨9, 14⟩ @  [by exact ab.2] 
                      before 
                      a b : Prop
                      ab : a ∧ b
                      ⊢ ?m.16
                      after
                      no goals
                      • Tactic @ ⟨8, 17⟩-⟨8, 19⟩ @  by 
                        before 
                        a b : Prop
                        ab : a ∧ b
                        ⊢ ?m.16
                        after
                        no goals
                        • Tactic @ ⟨9, 4⟩-⟨9, 14⟩ @ [exact ab.2] @ Lean.Elab.Tactic.evalTacticSeq
                          before 
                          a b : Prop
                          ab : a ∧ b
                          ⊢ ?m.16
                          after
                          no goals
                          • Tactic @ ⟨9, 4⟩-⟨9, 14⟩ @ [exact ab.2] @ Lean.Elab.Tactic.evalTacticSeq1Indented
                            before 
                            a b : Prop
                            ab : a ∧ b
                            ⊢ ?m.16
                            after
                            no goals
                            • Tactic @ ⟨9, 4⟩-⟨9, 14⟩ @ exact ab.2 @ Lean.Elab.Tactic.evalExact
                              before 
                              a b : Prop
                              ab : a ∧ b
                              ⊢ ?m.16
                              after
                              no goals
                  • Tactic @ ⟨8, 2⟩-⟨9, 14⟩ @ rotate_right @ Lean.Elab.Tactic.evalRotateRight
                    before 
                    a b : Prop
                    ab : a ∧ b
                    just_b : b
                    ⊢ b
                    after 
                    a b : Prop
                    ab : a ∧ b
                    just_b : b
                    ⊢ b
  • Tactic @ ⟨10, 2⟩-⟨10, 14⟩ @ exact just_b                 @ Lean.Elab.Tactic.evalExact
    before 
    a b : Prop
    ab : a ∧ b
    just_b : b
    ⊢ b
    after
    no goals

The first node (intro ab) and the third node (exact just_b) are leaf nodes, and give us clear before/after states.
The second node (have just_b := ...) is an intermediate node, and contains many descendants - of which the leaf nodes are the most useful.

Where is the InfoTree created?

We are most interested in Info.ofTacticInfo InfoTrees, so let's look into the place where those are created.

What you should attend to is the withTacticInfoContext stx doBlock function. You can see its implementation below:

def withTacticInfoContext (stx : Syntax) (doBlock : TacticM α) : TacticM α := do
  let mctxBefore  ← getMCtx
  let goalsBefore ← getUnsolvedGoals
  let initialTacticInfo : TacticM Info := do
    return Info.ofTacticInfo {
      elaborator    := (← read).elaborator
      mctxBefore    := mctxBefore
      goalsBefore   := goalsBefore
      stx           := stx
      mctxAfter     := (← getMCtx)
      goalsAfter    := (← getUnsolvedGoals)
    }
  withInfoContext doBlock initialTacticInfo

What withTacticInfoContent stx doBlock function is doing is:
1. retrieve the current getUnsolvedGoals (and put them into .goalsBefore)
2. execute the doBlock
3. retrieve the current getUnsolvedGoals (and put them into .goalsAfter).

There are two places where this function gets executed - the default place that's shared by all tactics (evalTactic function), and particular tactic implementation that do some handling on top of that.

Where is the InfoTree created: Default Place

If you read the "Overview" chapter in "Metaprogramming In Lean" textbook (link), you might remember the process of elaboration in Lean is as follows:
1. apply a relevant syntax rule ("let a := 2"Syntax)
2. apply all macros in a loop (SyntaxSyntax)
3. apply a single elab (SyntaxExpr)

Function evalTactic() is where the tactic nodes in our InfoTree get created.
Coincidentally, function evalTactic() is a function where the elaboration process described above happens.

You can check out the full implementation of this function in Lean 4 repo (link).
Here is a shorter version of that function, where I excluded all macro handling:

partial def evalTactic (stx : Syntax) : TacticM Unit := do
  match stx with
    | .node _ k _    =>
      ...
      expandEval s macros evalFns #[]
    | .missing => pure ()
    | _ => throwError m!"unexpected tactic{indentD stx}"
where
    ...
    expandEval (s : SavedState) (macros : List _) (evalFns : List _) (failures : Array EvalTacticFailure) : TacticM Unit :=
      match macros with
      | [] => eval s evalFns failures
      | m :: ms => ...

    eval (s : SavedState) (evalFns : List _) (failures : Array EvalTacticFailure) : TacticM Unit := do
      match evalFns with
      | []              => throwExs failures
      | evalFn::evalFns => do
        withTacticInfoContext stx do
          evalFn.value stx

Even if you create your own tactic, it will be producing InfoTrees by default, because all tactics go through the elaboration process in def evalTactics, and def evalTactics does create InfoTrees internally.


In the evalTactic above I removed all the macro handling, but macros are everywhere, and it's interesting how macros are handled in the InfoTree.
Let's create a brand new tactic of our own, and two macros: 

open Lean Elab Tactic Meta

syntax (name := xxx) "red" : tactic
syntax (name := yyy) "green" : tactic
syntax (name := zzz) "blue" : tactic

@[macro xxx] def redMacro : Macro := λ stx =>
  match stx with
  | _ => `(tactic| green)

@[macro yyy] def greenMacro : Macro := λ stx =>
  match stx with
  | _ => `(tactic| blue)

-- This is our tactic.
-- All it does is it creates a new hypothesis `cute: True`.
@[tactic zzz] def blueElab : Lean.Elab.Tactic.Tactic := λ stx =>
  withMainContext do
    let trueExpr := mkConst `True
    liftMetaTactic fun mvarId => do
      -- Create a new hypothesis `cute: True`
      let mvarIdNew ← mvarId.assert `cute trueExpr (mkConst `True.intro)
      let (_, mvarIdFinal) ← mvarIdNew.intro1P
      return [mvarIdFinal]

As a reminder about how elaboration in Lean works:

  • we write "red" in our proof
  • def redMacro turns "red" into "green"def greenMacro turns "green" into "blue" 
  • def blueElab gets executed
theorem hello : False := by
  red

Now, let's see what the InfoTree  that we get by hovering above the theorem hello looks like. Here is the following 53-line InfoTree output:

Click to expand JSON
• command @ ⟨40, 0⟩-⟨41, 5⟩ @ Lean.Elab.Command.elabDeclaration
  • False : Prop @ ⟨40, 16⟩-⟨40, 21⟩ @ Lean.Elab.Term.elabIdent
    • [.] False : some Sort.{?_uniq.1387} @ ⟨40, 16⟩-⟨40, 21⟩
    • False : Prop @ ⟨40, 16⟩-⟨40, 21⟩
  • hello (isBinder := true) : False @ ⟨40, 8⟩-⟨40, 13⟩
  • Tactic @ ⟨40, 25⟩-⟨41, 5⟩
    (Term.byTactic "by" (Tactic.tacticSeq (Tactic.tacticSeq1Indented [(xxx "red")])))
    before 
    ⊢ False
    after 
    cute : True
    ⊢ False
    • Tactic @ ⟨40, 25⟩-⟨40, 27⟩
      "by"
      before 
      ⊢ False
      after 
      cute : True
      ⊢ False
      • Tactic @ ⟨41, 2⟩-⟨41, 5⟩ @ Lean.Elab.Tactic.evalTacticSeq
        (Tactic.tacticSeq (Tactic.tacticSeq1Indented [(xxx "red")]))
        before 
        ⊢ False
        after 
        cute : True
        ⊢ False
        • Tactic @ ⟨41, 2⟩-⟨41, 5⟩ @ Lean.Elab.Tactic.evalTacticSeq1Indented
          (Tactic.tacticSeq1Indented [(xxx "red")])
          before 
          ⊢ False
          after 
          cute : True
          ⊢ False
          • Tactic @ ⟨41, 2⟩-⟨41, 5⟩ @ redMacro
            (xxx "red")
            before 
            ⊢ False
            after 
            cute : True
            ⊢ False
            • Tactic @ ⟨41, 2⟩†-⟨41, 5⟩† @ greenMacro
              (yyy "green")
              before 
              ⊢ False
              after 
              cute : True
              ⊢ False
              • Tactic @ ⟨41, 2⟩†-⟨41, 5⟩† @ blueElab
                (zzz "blue")
                before 
                ⊢ False
                after 
                cute : True ⊢ False

To shorten the output, here is the InfoTree that we get. 

• Tactic @ ⟨41, 2⟩-⟨41, 5⟩ @ Lean.Elab.Tactic.evalTacticSeq @ (Tactic.tacticSeq (Tactic.tacticSeq1Indented [(xxx "red")]))
  • Tactic @ ⟨41, 2⟩-⟨41, 5⟩ @ Lean.Elab.Tactic.evalTacticSeq1Indented @ (Tactic.tacticSeq1Indented [(xxx "red")])
    • Tactic @ ⟨41, 2⟩-⟨41, 5⟩ @ redMacro @ (xxx "red")
      • Tactic @ ⟨41, 2⟩†-⟨41, 5⟩† @ greenMacro @ (yyy "green")
        • Tactic @ ⟨41, 2⟩†-⟨41, 5⟩† @ blueElab @ (zzz "blue")

We can see it evalTactic creates a separate InfoTree  tactic node per each macro transformation. The tactic we wrote ("red") is the parent node, while the tactic that was actually applied after all the macros ("blue") is its innermost child!
This is, again, the case where leaf nodes of the InfoTree carry the real weight.

Where is the InfoTree created: Additional Places

Some tactics like to add their own InfoTree nodes on top of the default InfoTree creation that we get from the elaboration.

Consider the implementation of the rw tactic (link):

def withRWRulesSeq (token : Syntax) (rwRulesSeqStx : Syntax) (x : (symm : Bool) → (term : Syntax) → TacticM Unit) : TacticM Unit := do
  let lbrak := rwRulesSeqStx[0]
  let rules := rwRulesSeqStx[1].getArgs
  -- show initial state up to (incl.) `[`
  withTacticInfoContext (mkNullNode #[token, lbrak]) (pure ())
  let numRules := (rules.size + 1) / 2
  for i in [:numRules] do
    let rule := rules[i * 2]!
    let sep  := rules.getD (i * 2 + 1) Syntax.missing
    -- show rule state up to (incl.) next `,`
    withTacticInfoContext (mkNullNode #[rule, sep]) do
      ...
      x symm term
      ...

Given the theorem below, we sould normally only see the before and after state for the full rw [add_assoc, add_comm] line.

theorem ok : 2 + 3 + 4 = 666 := by
  rw [add_assoc, add_comm]

But because rw adds additional withTacticInfoContext stx doBlock after each rule is applied, we get rw [add_assoc] (and proof state before and after) and rw [ass_comm] (and proof state before and after):

Click to expand JSON
• Tactic @ ⟨184, 2⟩-⟨184, 26⟩ @ Lean.Parser.Tactic._aux_Init_Tactics___macroRules_Lean_Parser_Tactic_rwSeq_1
  rw [add_assoc, add_comm]
  before 
  ⊢ 2 + 3 + 4 = 666
  after 
  ⊢ 3 + 4 + 2 = 666
  • Tactic @ ⟨184, 2⟩†-⟨184, 26⟩† @ Lean.Elab.Tactic.evalParen
    (rewrite [add_assoc, add_comm]; with_annotate_state (try (with_reducible rfl)))
    before 
    ⊢ 2 + 3 + 4 = 666
    after 
    ⊢ 3 + 4 + 2 = 666
    • Tactic @ ⟨184, 2⟩†-⟨184, 26⟩† @ Lean.Elab.Tactic.evalTacticSeq
      rewrite [add_assoc, add_comm]; with_annotate_state (try (with_reducible rfl))
      before 
      ⊢ 2 + 3 + 4 = 666
      after 
      ⊢ 3 + 4 + 2 = 666
      • Tactic @ ⟨184, 2⟩†-⟨184, 26⟩† @ Lean.Elab.Tactic.evalTacticSeq1Indented
        rewrite [add_assoc, add_comm]; with_annotate_state (try (with_reducible rfl))
        before 
        ⊢ 2 + 3 + 4 = 666
        after 
        ⊢ 3 + 4 + 2 = 666
        • Tactic @ ⟨184, 2⟩†-⟨184, 26⟩† @ Lean.Elab.Tactic.evalRewriteSeq
          rewrite [add_assoc, add_comm]
          before 
          ⊢ 2 + 3 + 4 = 666
          after 
          ⊢ 3 + 4 + 2 = 666
          • Tactic @ ⟨184, 2⟩†-⟨184, 26⟩† @ Lean.Elab.Tactic.evalRewriteSeq
            ["rewrite" "["]
            before 
            ⊢ 2 + 3 + 4 = 666
            after 
            ⊢ 2 + 3 + 4 = 666
          • Tactic @ ⟨184, 6⟩-⟨184, 16⟩ @ Lean.Elab.Tactic.evalRewriteSeq
            [add_assoc, ]
            before 
            ⊢ 2 + 3 + 4 = 666
            after 
            ⊢ 2 + (3 + 4) = 666
          • Tactic @ ⟨184, 17⟩-⟨184, 25⟩ @ Lean.Elab.Tactic.evalRewriteSeq
            [add_comm]
            before 
            ⊢ 2 + (3 + 4) = 666
            after 
            ⊢ 3 + 4 + 2 = 666
        • Tactic @ ⟨184, 2⟩†-⟨184, 26⟩† @ Lean.Elab.Tactic.evalTacticSeq1Indented
          ";"
          before 
          ⊢ 3 + 4 + 2 = 666
          after 
          ⊢ 3 + 4 + 2 = 666
        • ...attempt at handling rw [rlf]