/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Joachim Breitner
-/

module
prelude
public import Lean.Meta.Basic
import Lean.Util.CollectLevelParams
import Lean.AddDecl
import Lean.Meta.AppBuilder
import Lean.Elab.DeclarationRange

open Lean Meta

namespace Lean.Meta

/-!
This module contains infrastructure for proofs by native evaluation (`native decide`, `bv_decide`).
Such proofs involve a native computation using the Lean kernel, and then asserting the result
of that computation as an axiom towards the logic.
-/

public inductive NativeEqTrueResult where
  /-- The given expression `e` evalutes to true. `prf` is a proof of `e = true`. -/
  | success (prf : Expr)
  /-- The given expression `e` evalutes to false. -/
  | notTrue

/--
A call to `nativeEqTrue tacName e`, where `e` is a closed value of type `Bool`, will compile and run
that value, check that it evaluates to `true`, and if so, will add an axiom asserting `e = true` and
return that axiom.

It is the basis for `native_decide` and `bv_decide` tactics.
-/
public def nativeEqTrue (tacticName : Name) (e : Expr) (axiomDeclRange? : Option Syntax := none) : MetaM NativeEqTrueResult  := do
  let e ← instantiateMVars e
  if e.hasFVar then
    throwError m!"Tactic `{tacticName}` failed: Cannot native decide proposition with free variables:{indentExpr e}"
  if e.hasMVar then
    throwError m!"Tactic `{tacticName}` failed: Cannot native decide proposition with metavariables:{indentExpr e}"
  let levels := (collectLevelParams {} e).params.toList
  let isTrue ← withoutModifyingEnv do
    let auxDeclName ← mkAuxDeclName <| `_native ++ tacticName ++ `decl
    let decl := Declaration.defnDecl {
      name := auxDeclName
      levelParams := levels
      type := mkConst ``Bool
      value := e
      hints := .abbrev
      safety := .safe
    }
    try
      -- disable async codegen so we can catch its exceptions; we don't want to report `evalConst`
      -- failures below when the actual reason was a codegen failure
      withOptions (Elab.async.set · false) do
        addAndCompile decl
    catch ex =>
      throwError m!"Tactic `{tacticName}` failed. Error: {ex.toMessageData}"

    -- Now evaluate the constant, and check that it is true.
    try
      unsafe evalConst Bool auxDeclName
    catch ex =>
      throwError m!"\
        Tactic `{tacticName}` failed: Could not evaluate decidable instance. \
        Error: {ex.toMessageData}"

  unless isTrue do return .notTrue

  let auxAxiomName ← mkAuxDeclName <| `_native ++ tacticName ++ `ax
  let axDecl := Declaration.axiomDecl {
    name := auxAxiomName
    levelParams := levels
    type := mkApp3 (mkConst ``Eq [1]) (mkConst ``Bool) e (mkConst ``Bool.true)
    isUnsafe := false
  }
  addDecl axDecl
  if let some ref := axiomDeclRange? then
    Elab.addDeclarationRangesFromSyntax auxAxiomName ref

  let levelParams := levels.map mkLevelParam
  return .success <| mkConst auxAxiomName levelParams
