/-
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Lean.Meta.InferType

/-
This is not the Kernel type checker, but an auxiliary method for checking
whether terms produced by tactics and `isDefEq` are type correct.
-/

namespace Lean
namespace Meta

@[specialize] private def ensureType
    (whnf : Expr → MetaM Expr)
    (e : Expr) : MetaM Unit :=
do getLevelAux whnf (inferTypeAux whnf) e;
   pure ()

@[specialize] private def checkLambdaLet
    (whnf    : Expr → MetaM Expr)
    (isDefEq : Expr → Expr → MetaM Bool)
    (check   : Expr → MetaM Unit)
    (e : Expr) : MetaM Unit :=
lambdaTelescope whnf e $ fun xs b => do
  xs.forM $ fun x => do {
    xDecl ← getFVarLocalDecl x;
    match xDecl with
    | LocalDecl.cdecl _ _ _ t _ => do
      ensureType whnf t;
      check t
    | LocalDecl.ldecl _ _ _ t v => do
      ensureType whnf t;
      check t;
      vType ← inferTypeAux whnf v;
      unlessM (isDefEq t vType) $ throwEx $ Exception.letTypeMismatch x.fvarId!;
      check v
  };
  check b

@[specialize] private def checkForall
    (whnf    : Expr → MetaM Expr)
    (check   : Expr → MetaM Unit)
    (e : Expr) : MetaM Unit :=
forallTelescope whnf e $ fun xs b => do
  xs.forM $ fun x => do {
    xDecl ← getFVarLocalDecl x;
    ensureType whnf xDecl.type;
    check xDecl.type
  };
  ensureType whnf b;
  check b

@[specialize] private def checkConstant
    (c : Name) (lvls : List Level) : MetaM Unit :=
do env ← getEnv;
   match env.find c with
   | none       => throwEx $ Exception.unknownConst c
   | some cinfo => unless (lvls.length != cinfo.lparams.length) $ throwEx $ Exception.incorrectNumOfLevels c lvls

@[specialize] private def checkApp
    (whnf    : Expr → MetaM Expr)
    (isDefEq : Expr → Expr → MetaM Bool)
    (check   : Expr → MetaM Unit)
    (f a : Expr) : MetaM Unit :=
do check f;
   check a;
   fType ← inferTypeAux whnf f;
   fType ← whnf fType;
   match fType with
   | Expr.forallE _ d _ _ => do
     aType ← inferTypeAux whnf a;
     unlessM (isDefEq d aType) $ throwEx $ Exception.appTypeMismatch f a
   | _ => unless fType.isForall $ throwEx $ Exception.functionExpected f a

@[specialize] private partial def checkAuxAux
    (whnf    : Expr → MetaM Expr)
    (isDefEq : Expr → Expr → MetaM Bool)
    : Expr → MetaM Unit
| e@(Expr.forallE _ _ _ _) => checkForall whnf checkAuxAux e
| e@(Expr.lam _ _ _ _)     => checkLambdaLet whnf isDefEq checkAuxAux e
| e@(Expr.letE _ _ _ _ _)  => checkLambdaLet whnf isDefEq checkAuxAux e
| Expr.const c lvls _      => checkConstant c lvls
| Expr.app f a _           => checkApp whnf isDefEq checkAuxAux f a
| Expr.mdata _ e _         => checkAuxAux e
| Expr.proj _ _ e _        => checkAuxAux e
| _                        => pure ()

@[specialize] def checkAux
    (whnf    : Expr → MetaM Expr)
    (isDefEq : Expr → Expr → MetaM Bool)
    (e : Expr) : MetaM Unit :=
usingTransparency TransparencyMode.all $
  checkAuxAux whnf isDefEq e

@[specialize] def isTypeCorrectAux
    (whnf    : Expr → MetaM Expr)
    (isDefEq : Expr → Expr → MetaM Bool)
    (e : Expr) : MetaM Bool :=
catch
  (do checkAux whnf isDefEq e; pure true)
  (fun _ => pure false)

end Meta
end Lean
