/-
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.Basic
import Init.Lean.Meta.InferType

namespace Lean
namespace Meta

@[inline] private def checkFunInfoCache (fn : Expr) (maxArgs? : Option Nat) (k : MetaM FunInfo) : MetaM FunInfo :=
do s ← get;
   t ← getTransparency;
   match s.cache.funInfo.find ⟨t, fn, maxArgs?⟩ with
   | some finfo => pure finfo
   | none       => do
     finfo ← k;
     modify $ fun s => { cache := { funInfo := s.cache.funInfo.insert ⟨t, fn, maxArgs?⟩ finfo, .. s.cache }, .. s };
     pure finfo

@[inline] def whenHasVar {α} (e : Expr) (deps : α) (k : α → α) : α :=
if e.hasFVar then k deps else deps

def collectDepsAux (fvars : Array Expr) : Expr → Array Nat → Array Nat
| e@(Expr.app f a),         deps => whenHasVar e deps (collectDepsAux a ∘ collectDepsAux f)
| e@(Expr.forallE _ _ d b), deps => whenHasVar e deps (collectDepsAux b ∘ collectDepsAux d)
| e@(Expr.lam _ _ d b),     deps => whenHasVar e deps (collectDepsAux b ∘ collectDepsAux d)
| e@(Expr.letE _ t v b),    deps => whenHasVar e deps (collectDepsAux b ∘ collectDepsAux v ∘ collectDepsAux t)
| Expr.proj _ _ e,          deps => collectDepsAux e deps
| Expr.mdata _ e,           deps => collectDepsAux e deps
| e@(Expr.fvar _),          deps =>
  match fvars.indexOf e with
  | none   => deps
  | some i => if deps.contains i.val then deps else deps.push i.val
| _,                        deps => deps

def collectDeps (fvars : Array Expr) (e : Expr) : Array Nat :=
let deps := collectDepsAux fvars e #[];
deps.qsort (fun i j => i < j)

/-- Update `hasFwdDeps` fields using new `backDeps` -/
private def updateHasFwdDeps (pinfo : Array ParamInfo) (backDeps : Array Nat) : Array ParamInfo :=
if backDeps.size == 0 then
  pinfo
else
  -- update hasFwdDeps fields
  pinfo.mapIdx $ fun i info =>
    if info.hasFwdDeps then info
    else if backDeps.contains i then
      { hasFwdDeps := true, .. info }
    else
      info

@[specialize] def getFunInfoAuxAux
    (whnf      : Expr → MetaM Expr)
    (fn : Expr) (maxArgs? : Option Nat) : MetaM FunInfo :=
checkFunInfoCache fn maxArgs? $ do
  fnType ← inferTypeAux whnf fn;
  forallBoundedTelescope (usingDefault whnf) fnType maxArgs? $ fun fvars type => do
    pinfo ← fvars.size.foldM
      (fun (i : Nat) (pinfo : Array ParamInfo) => do
        let fvar := fvars.get! i;
        decl ← getLocalDecl fvar.fvarId!;
        prop ← isPropAux whnf decl.type;
        let backDeps := collectDeps fvars decl.type;
        let pinfo    := updateHasFwdDeps pinfo backDeps;
        pure $ pinfo.push {
          backDeps     := backDeps,
          prop         := prop,
          implicit     := decl.binderInfo == BinderInfo.implicit,
          instImplicit := decl.binderInfo == BinderInfo.instImplicit })
      #[];
    let resultDeps := collectDeps fvars type;
    let pinfo      := updateHasFwdDeps pinfo resultDeps;
    pure { resultDeps := resultDeps, paramInfo := pinfo }

@[inline] def getFunInfoAux
    (whnf      : Expr → MetaM Expr)
    (fn : Expr) : MetaM FunInfo :=
getFunInfoAuxAux whnf fn none

@[inline] def getFunInfoNArgsAux
    (whnf      : Expr → MetaM Expr)
    (fn : Expr) (nargs : Nat) : MetaM FunInfo :=
getFunInfoAuxAux whnf fn (some nargs)

end Meta
end Lean
