/- Copyright (c) 2023 Scott Morrison. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Scott Morrison -/ import Lean /-! Additional functions to deal with `InfoTree`. -/ open Lean Elab Meta namespace Lean.FileMap /-- Extract the range of a `Syntax` expressed as lines and columns. -/ -- Extracted from the private declaration `Lean.Elab.formatStxRange`, -- in `Lean.Elab.InfoTree.Main`. def stxRange (fileMap : FileMap) (stx : Syntax) : Position × Position := let pos := stx.getPos?.getD 0 let endPos := stx.getTailPos?.getD pos (fileMap.toPosition pos, fileMap.toPosition endPos) end Lean.FileMap namespace Lean.Syntax /-- Check if a `Syntax` is an explicit invocation of the `sorry` tactic. -/ def isSorryTactic (stx : Syntax) : Bool := s!"{stx}" = "(Tactic.tacticSorry \"sorry\")" /-- Check if a `Syntax` is an explicit `sorry` term. -/ def isSorryTerm (stx : Syntax) : Bool := s!"{stx}" = "(Term.sorry \"sorry\")" end Lean.Syntax namespace Lean.Elab /-- Extract the range of a `Syntax` expressed as lines and columns. -/ -- Extracted from the private declaration `Lean.Elab.formatStxRange`, -- in `Lean.Elab.InfoTree.Main`. def stxRange (fileMap : FileMap) (stx : Syntax) : Position × Position := let pos := stx.getPos?.getD 0 let endPos := stx.getTailPos?.getD pos (fileMap.toPosition pos, fileMap.toPosition endPos) end Lean.Elab namespace Lean.Elab.Info /-- The type of a `Lean.Elab.Info`, as a string. -/ def kind : Info → String | .ofTacticInfo _ => "TacticInfo" | .ofTermInfo _ => "TermInfo" | .ofCommandInfo _ => "CommmandInfo" | .ofMacroExpansionInfo _ => "MacroExpansionInfo" | .ofOptionInfo _ => "OptionInfo" | .ofFieldInfo _ => "FieldInfo" | .ofCompletionInfo _ => "CompletionInfo" | .ofUserWidgetInfo _ => "UserWidgetInfo" | .ofCustomInfo _ => "CustomInfo" | .ofFVarAliasInfo _ => "FVarAliasInfo" | .ofFieldRedeclInfo _ => "FieldRedeclInfo" | .ofOmissionInfo _ => "OmissionInfo" /-- The `Syntax` for a `Lean.Elab.Info`, if there is one. -/ def stx? : Info → Option Syntax | .ofTacticInfo info => info.stx | .ofTermInfo info => info.stx | .ofCommandInfo info => info.stx | .ofMacroExpansionInfo info => info.stx | .ofOptionInfo info => info.stx | .ofFieldInfo info => info.stx | .ofCompletionInfo info => info.stx | .ofUserWidgetInfo info => info.stx | .ofCustomInfo info => info.stx | .ofFVarAliasInfo _ => none | .ofFieldRedeclInfo info => info.stx | .ofOmissionInfo info => info.stx /-- Is the `Syntax` for this `Lean.Elab.Info` original, or synthetic? -/ def isOriginal (i : Info) : Bool := match i.stx? with | none => true -- Somewhat unclear what to do with `FVarAliasInfo`, so be conservative. | some stx => match stx.getHeadInfo with | .original .. => true | _ => false end Lean.Elab.Info namespace Lean.Elab.TacticInfo /-- Find the name for the outermost `Syntax` in this `TacticInfo`. -/ def name? (t : TacticInfo) : Option Name := match t.stx with | Syntax.node _ n _ => some n | _ => none /-- Decide whether a tactic is "substantive", or is merely a tactic combinator (e.g. `by`, `;`, multiline tactics, parenthesized tactics). -/ def isSubstantive (t : TacticInfo) : Bool := match t.name? with | none => false | some `null => false | some ``cdot => false | some ``cdotTk => false | some ``Lean.Parser.Term.byTactic => false | some ``Lean.Parser.Tactic.tacticSeq => false | some ``Lean.Parser.Tactic.tacticSeq1Indented => false | some ``Lean.Parser.Tactic.«tactic_<;>_» => false | some ``Lean.Parser.Tactic.paren => false | _ => true end Lean.Elab.TacticInfo namespace Lean.Elab.InfoTree /-- Keep `.node` nodes and `.hole` nodes satisfying predicates. Returns a `List InfoTree`, although in most situations this will be a singleton. -/ partial def filter (p : Info → Bool) (m : MVarId → Bool := fun _ => false) : InfoTree → List InfoTree | .context ctx tree => tree.filter p m |>.map (.context ctx) | .node info children => if p info then [.node info (children.toList.map (filter p m)).join.toPArray'] else (children.toList.map (filter p m)).join | .hole mvar => if m mvar then [.hole mvar] else [] /-- Discard all nodes besides `.context` nodes and `TacticInfo` nodes. -/ partial def retainTacticInfo (tree : InfoTree) : List InfoTree := tree.filter fun | .ofTacticInfo _ => true | _ => false /-- Retain only nodes with "original" syntax. -/ partial def retainOriginal (tree : InfoTree) : List InfoTree := tree.filter Info.isOriginal /-- Discard all TacticInfo nodes that are tactic combinators or structuring tactics. -/ -- There is considerable grey area here: what to do with `classical`? partial def retainSubstantive (tree : InfoTree) : List InfoTree := tree.filter fun | .ofTacticInfo i => i.isSubstantive | _ => true /-- Analogue of `Lean.Elab.InfoTree.findInfo?`, but that returns all results. -/ partial def findAllInfo (t : InfoTree) (ctx? : Option ContextInfo) (p : Info → Bool) : List (Info × Option ContextInfo) := match t with | context ctx t => t.findAllInfo (ctx.mergeIntoOuter? ctx?) p | node i ts => let info := if p i then [(i, ctx?)] else [] let rest := ts.toList.bind (fun t => t.findAllInfo ctx? p) info ++ rest | _ => [] /-- Return all `TacticInfo` nodes in an `InfoTree` with "original" syntax, each equipped with its relevant `ContextInfo`. -/ def findTacticNodes (t : InfoTree) : List (TacticInfo × ContextInfo) := let infos := t.findAllInfo none fun i => match i with | .ofTacticInfo i' => i.isOriginal && i'.isSubstantive | _ => false infos.filterMap fun p => match p with | (.ofTacticInfo i, some ctx) => (i, ctx) | _ => none /-- Return all `TacticInfo` nodes in an `InfoTree` corresponding to explicit invocations of the `sorry` tactic, each equipped with its relevant `ContextInfo`. -/ def findSorryTacticNodes (t : InfoTree) : List (TacticInfo × ContextInfo) := let infos := t.findAllInfo none fun i => match i with | .ofTacticInfo i => i.stx.isSorryTactic && !i.goalsBefore.isEmpty | _ => false infos.filterMap fun p => match p with | (.ofTacticInfo i, some ctx) => (i, ctx) | _ => none /-- Return all `TermInfo` nodes in an `InfoTree` corresponding to explicit `sorry` terms, each equipped with its relevant `ContextInfo`. -/ def findSorryTermNodes (t : InfoTree) : List (TermInfo × ContextInfo) := let infos := t.findAllInfo none fun i => match i with | .ofTermInfo i => i.stx.isSorryTerm | _ => false infos.filterMap fun p => match p with | (.ofTermInfo i, some ctx) => (i, ctx) | _ => none inductive SorryType | tactic : MVarId → SorryType | term : LocalContext → Option Expr → SorryType deriving Inhabited /-- Finds all appearances of `sorry` in an `InfoTree`, reporting * the `ContextInfo` at that point, * the `MVarId` for a goal that was closed by `sorry`, or the `Option Expr` expected type for a term supplied by `sorry` * and the start and end positions of the `sorry` in the file. -/ def sorries (t : InfoTree) : List (ContextInfo × SorryType × Position × Position) := (t.findSorryTacticNodes.map fun ⟨i, ctx⟩ => -- HACK: creating a child ngen ({ ctx with mctx := i.mctxBefore, ngen := ctx.ngen.mkChild.1 }, .tactic i.goalsBefore.head!, stxRange ctx.fileMap i.stx)) ++ (t.findSorryTermNodes.map fun ⟨i, ctx⟩ => (ctx, .term i.lctx i.expectedType?, stxRange ctx.fileMap i.stx)) def tactics (t : InfoTree) : List (ContextInfo × Syntax × List MVarId × Position × Position) := (t.findTacticNodes.map fun ⟨i, ctx⟩ => -- HACK: creating a child ngen ({ ctx with mctx := i.mctxBefore, ngen := ctx.ngen.mkChild.1 }, i.stx, i.goalsBefore, stxRange ctx.fileMap i.stx)) end Lean.Elab.InfoTree namespace Lean.Elab.TacticInfo /-- Return the range of the tactic, as a pair of file positions. -/ def range (info : TacticInfo) (ctx : ContextInfo) : Position × Position := ctx.fileMap.stxRange info.stx /-- Pretty print a tactic. -/ def pp (info : TacticInfo) (ctx : ContextInfo) : IO Format := ctx.runMetaM {} try Lean.PrettyPrinter.ppTactic ⟨info.stx⟩ catch _ => pure "" open Meta /-- Run a tactic on the goals stored in a `TacticInfo`. -/ def runMetaMGoalsBefore (info : TacticInfo) (ctx : ContextInfo) (x : List MVarId → MetaM α) : IO α := do ctx.runMetaM {} <| Meta.withMCtx info.mctxBefore <| x info.goalsBefore /-- Run a tactic on the after goals stored in a `TacticInfo`. -/ def runMetaMGoalsAfter (info : TacticInfo) (ctx : ContextInfo) (x : List MVarId → MetaM α) : IO α := do ctx.runMetaM {} <| Meta.withMCtx info.mctxAfter <| x info.goalsAfter /-- Run a tactic on the main goal stored in a `TacticInfo`. -/ def runMetaM (info : TacticInfo) (ctx : ContextInfo) (x : MVarId → MetaM α) : IO α := do match info.goalsBefore.head? with | none => throw <| IO.userError s!"No goals at {← info.pp ctx}" | some g => info.runMetaMGoalsBefore ctx fun _ => do g.withContext <| x g def mainGoal (info : TacticInfo) (ctx : ContextInfo) : IO Expr := info.runMetaM ctx (fun g => do instantiateMVars (← g.getType)) def formatMainGoal (info : TacticInfo) (ctx : ContextInfo) : IO Format := info.runMetaM ctx (fun g => do ppExpr (← instantiateMVars (← g.getType))) def goalState (info : TacticInfo) (ctx : ContextInfo) : IO (List Format) := do info.runMetaMGoalsBefore ctx (fun gs => gs.mapM fun g => do Meta.ppGoal g) def goalStateAfter (info : TacticInfo) (ctx : ContextInfo) : IO (List Format) := do info.runMetaMGoalsAfter ctx (fun gs => gs.mapM fun g => do Meta.ppGoal g) def ppExpr (info : TacticInfo) (ctx : ContextInfo) (e : Expr) : IO Format := info.runMetaM ctx (fun _ => do Meta.ppExpr (← instantiateMVars e)) end Lean.Elab.TacticInfo namespace Lean.Elab.InfoTree /-- Finds all tactic invocations in an `InfoTree`, ignoring structuring tactics (e.g. `by`, `;`, multiline tactics, parenthesized tactics). -/ def substantiveTactics (t : InfoTree) : List (TacticInfo × ContextInfo) := t.findTacticNodes.filter fun i => i.1.isSubstantive end Lean.Elab.InfoTree