/-
Copyright (c) 2024 Lean FRO. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Joe Hendrix
-/
module

prelude
public import Lean.Elab.Tactic.ElabTerm
public import Lean.Elab.Command
public import Lean.Elab.Tactic.Meta
public import Lean.Meta.CheckTactic

public section

/-!
Commands to validate tactic results.
-/

namespace Lean.Elab.CheckTactic

open Lean.Meta CheckTactic
open Lean.Elab.Tactic
open Lean.Elab.Term
open Lean.Elab.Command

@[builtin_command_elab Lean.Parser.checkTactic]
def elabCheckTactic : CommandElab := fun stx => do
  let `(#check_tactic $t ~> $result by $tac) := stx | throwUnsupportedSyntax
  withoutModifyingEnv $ do
    runTermElabM $ fun _vars => do
      let u ← withSynthesize (postpone := .no) <| Lean.Elab.Term.elabTerm t none
      let type ← inferType u
      let checkGoalType ← mkCheckGoalType u type
      let mvar ← mkFreshExprMVar (.some checkGoalType)
      let expTerm ← Lean.Elab.Term.elabTerm result (.some type)
      let (goals, _) ← Lean.Elab.runTactic mvar.mvarId! tac.raw
      match goals with
      | [] =>
        throwErrorAt stx
          m!"{tac} closed goal, but is expected to reduce to {indentExpr expTerm}."
      | [next] => do
        let (val, _, _) ← matchCheckGoalType stx (←next.getType)
        if !(← Meta.withReducible <| isDefEq val expTerm) then
          let (val, expTerm) ← addPPExplicitToExposeDiff val expTerm
          throwErrorAt stx
            m!"Term reduces to{indentExpr val}\nbut is expected to reduce to {indentExpr expTerm}"
      | _ => do
        throwErrorAt stx
          m!"{tac} produced multiple goals, but is expected to reduce to {indentExpr expTerm}."

@[builtin_command_elab Lean.Parser.checkTacticFailure]
def elabCheckTacticFailure : CommandElab := fun stx => do
  let `(#check_tactic_failure $t by $tactic) := stx | throwUnsupportedSyntax
  withoutModifyingEnv $ do
    runTermElabM $ fun _vars => do
      let val ← Lean.Elab.Term.elabTerm t none
      let type ← inferType val
      let checkGoalType ← mkCheckGoalType val type
      let mvar ← mkFreshExprMVar (.some checkGoalType)
      let act := Lean.Elab.runTactic mvar.mvarId! tactic.raw
      match ← try (Term.withoutErrToSorry (some <$> act)) catch _ => pure none with
        | none =>
          pure ()
        | some (gls, _) =>
          let ppGoal (g : MVarId) := do
                let (val, _type, _u) ← matchCheckGoalType stx (← g.getType)
                pure m!"{indentExpr val}"
          let msg ←
            match gls with
              | [] => pure m!"{tactic} expected to fail on {t}, but closed goal."
              | [g] =>
                pure <| m!"{tactic} expected to fail on {t}, but returned: {←ppGoal g}"
              | gls =>
                let app m g := do pure <| m ++ (←ppGoal g)
                let init := m!"{tactic} expected to fail on {t}, but returned goals:"
                gls.foldlM (init := init) app
          throwErrorAt stx msg

@[builtin_macro Lean.Parser.checkSimp]
def expandCheckSimp : Macro := fun stx => do
  let `(#check_simp $t ~> $exp) := stx | Macro.throwUnsupported
  `(command|#check_tactic $t ~> $exp by simp)

@[builtin_macro Lean.Parser.checkSimpFailure]
def expandCheckSimpFailure : Macro := fun stx => do
  let `(#check_simp $t !~>) := stx | Macro.throwUnsupported
  `(command|#check_tactic_failure $t by simp)

end Lean.Elab.CheckTactic
