packages feed

agda2hs-1.4: src/Agda2Hs/Compile/Term.hs

module Agda2Hs.Compile.Term where

import Control.Arrow ( (>>>), (&&&), second )
import Control.Monad ( zipWithM )
import Control.Monad.Except
import Control.Monad.Reader

import Data.Foldable ( toList )
import Data.Functor ( ($>) )
import Data.List ( isPrefixOf )
import Data.Maybe ( fromMaybe, isJust )
import qualified Data.Text as Text ( unpack )
import qualified Data.Set as Set ( singleton )

import Agda.Syntax.Common.Pretty ( prettyShow )
import qualified Agda.Syntax.Common.Pretty as P
import Agda.Syntax.Common
import Agda.Syntax.Literal
import Agda.Syntax.Internal

import Agda.TypeChecking.CheckInternal ( infer )
import Agda.TypeChecking.Constraints ( noConstraints )
import Agda.TypeChecking.Conversion ( equalTerm )
import Agda.TypeChecking.InstanceArguments ( findInstance )
import Agda.TypeChecking.MetaVars ( newInstanceMeta )
import Agda.TypeChecking.Monad
import Agda.TypeChecking.Pretty
import Agda.TypeChecking.Records ( shouldBeProjectible, isRecordType, recordFieldNames )
import Agda.TypeChecking.Datatypes ( getConType )
import Agda.TypeChecking.Reduce ( unfoldDefinitionStep, instantiate, reduce )
import Agda.TypeChecking.Substitute
import Agda.TypeChecking.Telescope ( telView, mustBePi, piApplyM, flattenTel )
import Agda.TypeChecking.ProjectionLike ( reduceProjectionLike )

import Agda.Utils.Lens
import Agda.Utils.Impossible ( __IMPOSSIBLE__ )
import Agda.Utils.Maybe
import Agda.Utils.Monad
import Agda.Utils.Size

import Agda2Hs.AgdaUtils
import Agda2Hs.Compile.Name ( compileQName, importInstance )

import Agda2Hs.Compile.Type ( compileType, compileDom, DomOutput(..), compileTelSize )
import Agda2Hs.Compile.Types
import Agda2Hs.Compile.Utils
import Agda2Hs.Compile.Var ( compileDBVar )

import qualified Agda2Hs.Language.Haskell as Hs
import Agda2Hs.Language.Haskell.Utils
  ( hsName, pp, eApp, hsLambda, hsUnqualName, hsVar )

import {-# SOURCE #-} Agda2Hs.Compile.Function ( compileClause' )
import qualified Data.List as L


-- * Compilation of special definitions

type DefCompileRule = Type -> [Term] -> C (Hs.Exp ())

isSpecialDef :: QName -> Maybe DefCompileRule
isSpecialDef q = case prettyShow q of
  _ | isExtendedLambdaName q     -> Just (lambdaCase q)
  "Haskell.Prim.if_then_else_"   -> Just ifThenElse
  "Haskell.Prim.case_of_"        -> Just caseOf
  "Haskell.Prim.the"             -> Just expTypeSig
  "Haskell.Extra.Delay.runDelay" -> Just compileErasedApp
  "Agda.Builtin.Word.primWord64FromNat" -> Just primWord64FromNat
  _                              -> Nothing


-- | Compile a @\where@ to the equivalent @\case@ expression.
lambdaCase :: QName -> DefCompileRule
lambdaCase q ty args = compileLocal $ setCurrentRangeQ q $ do
  Function
    { funClauses = cls
    , funExtLam  = Just ExtLamInfo {extLamModule = mname}
    } <- theDef <$> getConstInfo q

  npars <- size <$> lookupSection mname

  let (pars, rest) = splitAt npars args
      cs           = applys cls pars

  ty' <- piApplyM ty pars

  cs <- mapMaybeM (compileClause' mname (Just q) (hsName "(lambdaCase)") ty') cs

  case cs of
    -- If there is a single clause and all proper patterns got erased,
    -- we turn the remaining arguments into normal lambdas.
    [Hs.Match _ _ ps (Hs.UnGuardedRhs _ rhs) _]
      | null ps -> return rhs
      | all isVarPat ps -> return $ Hs.Lambda () ps rhs
    _ -> do
      lcase <- hsLCase =<< mapM clauseToAlt cs -- Pattern lambdas cannot have where blocks
      eApp lcase <$> compileArgs ty' rest
      -- undefined -- compileApp lcase (undefined, undefined, rest)

  where
    isVarPat :: Hs.Pat () -> Bool
    isVarPat Hs.PVar{} = True
    isVarPat _ = False

-- | Compile @if_then_else_@ to a Haskell @if ... then ... else ... @ expression.
ifThenElse :: DefCompileRule
ifThenElse ty args = compileArgs ty args >>= \case
  -- fully applied
  b : t : f : es' -> return $ Hs.If () b t f `eApp` es'
  -- partially applied
  _               -> agda2hsError "if_then_else_ must be fully applied"


-- | Compile @case_of_@ to Haskell @\case@ expression.
caseOf :: DefCompileRule
caseOf ty args = compileArgs ty args >>= \case
  -- applied to pattern lambda (that we remove, hence decrementLCase)
  e : Hs.LCase _ alts : es' -> decrementLCase $> eApp (Hs.Case () e alts) es'
  -- applied to regular lambda
  e : Hs.Lambda _ (p : ps) b : es' ->
    let lam [] = id
        lam qs = Hs.Lambda () qs
    in return $ eApp (Hs.Case () e [Hs.Alt () p (Hs.UnGuardedRhs () $ lam ps b) Nothing]) es'
  _ -> agda2hsError "case_of_ must be fully applied to a lambda term"


-- | Compile @the@ to an explicitly-annotated Haskell expression.
expTypeSig :: DefCompileRule
expTypeSig ty args@(_:typ:_:_) = do
    annot    <- compileType typ
    exp:args <- compileArgs ty args
    pure (Hs.ExpTypeSig () exp annot `eApp` args)
expTypeSig _ _ = agda2hsError "`the` must be fully applied"

primWord64FromNat :: DefCompileRule
primWord64FromNat ty args = compileArgs ty args >>= \case
  -- literal
  n@Hs.Lit{} : _ -> return n
  -- anything else
  _ -> agda2hsError "primWord64FromNat must be applied to a literal"


compileVar :: Int -> Type -> [Term] -> C (Hs.Exp ())
compileVar i ty es = do
  reportSDoc "agda2hs.compile.term" 15 $ text "Reached variable"
  name <- compileDBVar i
  compileApp (hsVar name) ty es

-- | Compile constructors, defs and vars by
-- carefully moving projections out of elims and calling compileProj.
compileSpined
      :: ([Term] -> C (Hs.Exp ()))  -- Compilation continuation
      -> (Elims -> Term)            -- Term begin constructed
      -> Type                       -- Type of term
      -> Elims                      -- Elims the term is applied to
      -> C (Hs.Exp ())
compileSpined c tm ty [] = c []
compileSpined c tm ty (e@(Proj o q):es) = do
  let t = tm []
  ty' <- shouldBeProjectible t ty o q
  compileSpined (compileProj q ty t ty') (tm . (e:)) ty' es
compileSpined c tm ty (e@(Apply (unArg -> x)):es) = do
  (a, b) <- mustBePi ty
  compileSpined (c . (x:)) (tm . (e:)) (absApp b x) es
compileSpined _ _ _ _ = __IMPOSSIBLE__

-- | Compile a definition.
compileDef :: QName -> Type -> [Term] -> C (Hs.Exp ())
compileDef f ty args | Just sem <- isSpecialDef f = do
  reportSDoc "agda2hs.compile.term" 12 $ text "Compiling application of special function"
  sem ty args

compileDef f ty args =
  ifM (isTransparentFunction f) (compileErasedApp ty args) $
  ifM (isInlinedFunction f) (compileInlineFunctionApp f ty args) $ do
    reportSDoc "agda2hs.compile.term" 12 $ text "Compiling application of regular function:" <+> prettyTCM f

    let defMod = qnameModule f

    minRecord <- asks minRecordName
    -- TODO: simplify this when Agda exposes where-provenance in 'Internal' syntax
    outerWhereModules <- asks whereModules

    (ty', args') <-

      -- if the function comes from a where-clause
      -- or is a class-method for the class we are currently defining,
      -- we drop the module parameters
      if defMod `elem` outerWhereModules || Just defMod == minRecord then do
        npars <- size <$> lookupSection defMod
        let (pars, rest) = splitAt npars args
        ty' <- piApplyM ty pars
        pure (ty', rest)
      else pure (ty, args)

    reportSDoc "agda2hs.compile.term" 15 $ text "module args" <+> prettyTCM ty'
    reportSDoc "agda2hs.compile.term" 15 $ text "args to def: " <+> prettyTCM args'

    hsName <- compileQName f

    compileApp (Hs.Var () hsName) ty' args'


-- * Compilation of projection(-like) definitions

type ProjCompileRule = Type -> Term -> Type -> [Term] -> C (Hs.Exp ())


isSpecialProj :: QName -> Maybe ProjCompileRule
isSpecialProj q = case prettyShow q of
  "Agda.Builtin.FromNat.Number.fromNat"         -> Just fromNat
  "Haskell.Prim.Enum.Enum.enumFrom"             -> Just mkEnumFrom
  "Haskell.Prim.Enum.Enum.enumFromTo"           -> Just mkEnumFromTo
  "Haskell.Prim.Enum.Enum.enumFromThen"         -> Just mkEnumFromThen
  "Haskell.Prim.Enum.Enum.enumFromThenTo"       -> Just mkEnumFromThenTo
  "Haskell.Prim.Monad.Do.Monad._>>=_"           -> Just monadBind
  "Haskell.Prim.Monad.Do.Monad._>>_"            -> Just monadSeq
  "Agda.Builtin.FromNeg.Negative.fromNeg"       -> Just fromNeg
  "Agda.Builtin.FromString.IsString.fromString" -> Just fromString
  _                                             -> Nothing


compileClassFun :: QName -> ProjCompileRule
compileClassFun q _ w ty args = do
  hf     <- compileQName q
  curMod <- currentModule
  unless (curMod `isLeChildModuleOf` qnameModule q) $ checkInstance w
  eApp (Hs.Var () hf) <$> compileArgs ty args

compileTupleProjection :: QName -> Hs.Boxed -> ProjCompileRule
compileTupleProjection f b wty w ty args = do
  -- TODO: avoid redoing all of this work each time
  -- by storing the fields of each tuple type somewhere
  when (b == Hs.Unboxed) $ agda2hsError "projecting from unboxed tuples is not allowed"
  reportSDoc "agda2hs.term.proj" 12 $ text "compiling tuple projection"
  (r, pars, def) <- lift $ fromMaybe __IMPOSSIBLE__ <$> isRecordType wty
  let fields = map unDom $ _recFields def
      fieldTypes = flattenTel $ _recTel def `apply` pars
  fname <- compileTupleFields fields fieldTypes >>= \case
    [f1,f2] | f == f1 -> return $ hsName "fst"
            | f == f2 -> return $ hsName "snd"
            | otherwise -> __IMPOSSIBLE__
    fs' -> agda2hsStringError $
      "cannot project from tuple with " ++ show (size fs') ++ " fields"
  cw <- compileTerm wty w
  cargs <- compileArgs ty args
  return $ eApp (Hs.Var () $ Hs.UnQual () fname) (cw:cargs)

  where
    compileTupleFields :: [QName] -> [Dom Type] -> C [QName]
    compileTupleFields fs tys = catMaybes <$> zipWithM compileTupleField fs tys

    compileTupleField :: QName -> Dom Type -> C (Maybe QName)
    compileTupleField f ty = compileDom ty >>= \case
      DODropped -> return Nothing
      DOTerm -> return (Just f)
      DOType -> agda2hsErrorM $ "illegal type field in tuple record:" <+> prettyTCM f
      DOInstance -> agda2hsErrorM $ "illegal instance field in tuple record:" <+> prettyTCM f


-- | Compile a projection(-like) definition
compileProj
  :: QName  -- ^ Name of the projection
  -> Type   -- ^ Type of the term the projection is being applied to
  -> Term   -- ^ Term the projection is being applied to
  -> Type   -- ^ Return type of the projection
  -> [Term] -- ^ Arguments the projection of the term is applied to
  -> C (Hs.Exp ())
compileProj q tty t ty args | Just rule <- isSpecialProj q = rule tty t ty args
compileProj q tty t ty args =
  -- unboxed projection: we drop the projection
  ifM (isJust <$> isUnboxProjection q) (eApp <$> compileTerm tty t <*> compileArgs ty args) $
  -- class projection: we check the instance and drop it
  ifM (isClassFunction q) (compileClassFun q tty t ty args) $
  ifJustM (isTupleProjection q) (\b -> compileTupleProjection q b tty t ty args) $
  do
  -- NOTE(flupe): maybe we want Dom Type for the record arg
    name <- compileQName q
    arg  <- compileTerm tty t
    compileApp (Hs.Var () name `eApp` [arg]) ty args

-- | Utility for translating class methods to special Haskell counterpart.
-- This runs an instance check.
specialClassFunction :: ([Hs.Exp ()] -> Hs.Exp ()) -> ProjCompileRule
specialClassFunction f = specialClassFunctionM (pure . f)

specialClassFunctionM :: ([Hs.Exp ()] -> C (Hs.Exp ())) -> ProjCompileRule
specialClassFunctionM f _ w ty args = checkInstance w >> (f =<< compileArgs ty args)

specialClassFunction1 :: Hs.Exp () -> (Hs.Exp () -> Hs.Exp ()) -> ProjCompileRule
specialClassFunction1 v f = specialClassFunction $ \case
  (a : es) -> f a `eApp` es
  []       -> v

specialClassFunction2 :: Hs.Exp () -> (Hs.Exp () -> Hs.Exp () -> Hs.Exp ()) -> ProjCompileRule
specialClassFunction2 v f = specialClassFunction $ \case
  (a : b : es) -> f a b `eApp` es
  es           -> v `eApp` es

specialClassFunction3 :: Hs.Exp () -> (Hs.Exp () -> Hs.Exp () -> Hs.Exp () -> Hs.Exp ()) -> ProjCompileRule
specialClassFunction3 v f = specialClassFunction $ \case
  (a : b : c : es) -> f a b c `eApp` es
  es               -> v `eApp` es

-- Note: currently the second (instance) argument {{_ : Constraint n}}
-- is compiled and then dropped here, ideally it would not be compiled
-- at all.
fromNat :: ProjCompileRule
fromNat = specialClassFunction2 (hsVar "fromIntegral") $ \v _ -> case v of
  n@Hs.Lit{} -> n
  v          -> hsVar "fromIntegral" `eApp` [v]

mkEnumFrom :: ProjCompileRule
mkEnumFrom = specialClassFunction1 (hsVar "enumFrom") $ Hs.EnumFrom ()

mkEnumFromTo :: ProjCompileRule
mkEnumFromTo = specialClassFunction2 (hsVar "enumFromTo") $ Hs.EnumFromTo ()

mkEnumFromThen :: ProjCompileRule
mkEnumFromThen = specialClassFunction2 (hsVar "enumFromThen") $ Hs.EnumFromThen ()

mkEnumFromThenTo :: ProjCompileRule
mkEnumFromThenTo = specialClassFunction3 (hsVar "enumFromThenTo") $ Hs.EnumFromThenTo ()

-- Same comment as for fromNat
fromNeg :: ProjCompileRule
fromNeg = specialClassFunction2 negFromIntegral $ \v _ -> case v of
  n@Hs.Lit{} -> Hs.NegApp () n
  v          -> negFromIntegral `eApp` [v]
  where
    negFromIntegral = hsVar "negate" `o` hsVar "fromIntegral"
    -- TODO: move this to HsUtils
    f `o` g = Hs.InfixApp () f (Hs.QVarOp () $ hsUnqualName "_._") g

-- Same comment as for fromNat
fromString :: ProjCompileRule
fromString = specialClassFunction2 (hsVar "fromString") $ \v _ -> case v of
  s@Hs.Lit{} -> s
  v          -> hsVar "fromString" `eApp` [v]

-- | Compile monadic bind operator _>>=_ to Haskell do notation.
monadBind :: ProjCompileRule
monadBind = specialClassFunctionM $ \case
  [u, Hs.Lambda _ [p] v] -> pure $ bind' u p v
  [u, Hs.LCase () [Hs.Alt () p (Hs.UnGuardedRhs () v) Nothing]] ->
    decrementLCase >> return (bind' u p v)
  vs -> pure $ hsVar "_>>=_" `eApp` vs
  where
    bind' :: Hs.Exp () -> Hs.Pat () -> Hs.Exp () -> Hs.Exp ()
    bind' u p v =
      let stmt1 = Hs.Generator () p u in
      case v of
        Hs.Do _ stmts -> Hs.Do () (stmt1 : stmts)
        _             -> Hs.Do () [stmt1, Hs.Qualifier () v]

-- | Compile monadic bind operator _>>_ to Haskell do notation.
monadSeq :: ProjCompileRule-- TElims -> C (Hs.Exp ())
monadSeq = specialClassFunction $ \case
  (u : v : vs) -> do
    let stmt1 = Hs.Qualifier () u
    case v of
      Hs.Do _ stmts -> Hs.Do () (stmt1 : stmts)
      _             -> Hs.Do () [stmt1, Hs.Qualifier () v]
  vs -> hsVar "_>>_" `eApp` vs


-- * Compilation of constructors

type ConCompileRule = Type -> [Term] -> C (Hs.Exp ())

-- | Custom compilation rules for special constructors.
isSpecialCon :: QName -> Maybe ConCompileRule
isSpecialCon = prettyShow >>> \case
  "Haskell.Prim.Tuple._,_"          -> Just tupleTerm
  "Haskell.Prim.Tuple._×_×_._,_,_"  -> Just tupleTerm
  "Haskell.Prim.Int.Int.int64"      -> Just int64Term
  "Haskell.Extra.Sigma._,_"         -> Just tupleTerm
  "Haskell.Extra.Erase.Erased"      -> Just erasedTerm
  "Haskell.Extra.Delay.Delay.now"   -> Just compileErasedApp
  "Haskell.Extra.Delay.Delay.later" -> Just compileErasedApp
  _                                 -> Nothing

tupleTerm :: ConCompileRule
tupleTerm = compileApp' (Hs.Tuple () Hs.Boxed)

erasedTerm :: ConCompileRule
erasedTerm _ _ = pure (Hs.Tuple () Hs.Boxed [])

int64Term :: ConCompileRule
int64Term ty args = compileArgs ty args >>= \case
  n@Hs.Lit{} : _ -> return n
  _ -> agda2hsError "int64 must be applied to a literal"

-- | @compileErasedApp@ compiles the application of unboxed constructors
-- and transparent functions.
-- Precondition: at most one argument is preserved.
compileErasedApp :: Type -> [Term] -> C (Hs.Exp ())
compileErasedApp ty args = do
  reportSDoc "agda2hs.compile.term" 12 $ text "Compiling application of transparent function or erased unboxed constructor"
  reportSDoc "agda2hs.compile.term" 12 $ text "Args" <+> prettyTCM args
  reportSDoc "agda2hs.compile.term" 12 $ text "Type" <+> prettyTCM ty
  compileArgs ty args >>= \case
    []  -> return $ hsVar "id"
    [v] -> return v
    _   -> __IMPOSSIBLE__


compileCon :: ConHead -> ConInfo -> Type -> [Term] -> C (Hs.Exp ())
compileCon h i ty args = do
  let c = conName h
  ifJust  (isSpecialCon c)       (\semantics -> semantics ty args) $ do
  ifJustM (isUnboxConstructor c) (\_ -> compileErasedApp ty args)  $ do
  ifJustM (isTupleConstructor c) (\b -> compileTuple ty b args)    $ do
    info <- getConstInfo c
    -- the constructor may be a copy introduced by module application,
    -- therefore we need to find the original constructor
    if defCopy info then
      let Constructor{conSrcCon = ch'} = theDef info in
      compileCon ch' i ty args
    else do
      con <- Hs.Con () <$> compileQName c
      compileApp con ty args

compileTuple :: Type -> Hs.Boxed -> [Term] -> C (Hs.Exp ())
compileTuple ty b args = do
  tellUnboxedTuples b
  (ty', vs) <- compileArgs' ty args
  TelV tel _ <- telView ty'
  missing <- compileTelSize tel
  let given = size vs
  if -- No arguments: return unit constructor () or (# #)
     | given == 0 && missing == 0 -> return $
         Hs.Con () $ Hs.Special () $ case b of
           Hs.Boxed -> Hs.UnitCon ()
           Hs.Unboxed -> Hs.UnboxedSingleCon ()
     -- All arguments missing: return tuple constructor
     -- e.g. (,) or (#,#)
     | given == 0 -> return $
         Hs.Con () $ Hs.Special () $ Hs.TupleCon () b missing
     -- All arguments given: return tuple
     -- e.g. (v1 , v2) or (# v1 , v2 #)
     | missing == 0 -> return $ Hs.Tuple () b vs
     -- Some arguments given, some missing: return tuple section
     -- e.g. (v1 ,) or (# v1, #)
     | otherwise -> do
         tellExtension $ Hs.TupleSections
         return $ Hs.TupleSection () b $
           map Just vs ++ replicate missing Nothing



-- * Term compilation

compileTerm :: Type -> Term -> C (Hs.Exp ())
compileTerm ty v = do

  reportSDoc "agda2hs.compile.term" 10  $ text "compiling term:" <+> prettyTCM v

  v <- instantiate v

  let bad s t = agda2hsErrorM $ vcat
        [ text "cannot compile" <+> text (s ++ ":")
        , nest 2 $ prettyTCM t
        ]

  reduceProjectionLike v >>= \case

    Def f es -> do
      ty <- defType <$> getConstInfo f
      compileSpined (compileDef f ty) (Def f) ty es

    Con ch ci es -> do
      Just ((_, _, _), ty) <- getConType ch ty
      compileSpined (compileCon ch ci ty) (Con ch ci) ty es

    Var i es     -> do
      ty <- typeOfBV i
      compileSpined (compileVar i ty) (Var i) ty es

    Lit l   -> compileLiteral l

    Lam v b -> compileLam ty v b

    v@Pi{} -> bad "function type" v
    v@Sort{} -> bad "sort type" v
    v@Level{} -> bad "level term" v
    v@MetaV{} -> bad "unsolved metavariable" v
    v@DontCare{} -> bad "irrelevant term" v
    v@Dummy{} -> bad "dummy term" v

-- | Check whether a domain is usable on the Haskell side.
--
-- That is the case if:
-- * it is usable on the Agda side (i.e neither erased nor irrelevant).
-- * is not of sort Prop.
usableDom :: Dom Type -> Bool
usableDom dom | Prop _ <- getSort dom = False
usableDom dom = usableModality dom


compileLam :: Type -> ArgInfo -> Abs Term -> C (Hs.Exp ())
compileLam ty argi abs = do
  reportSDoc "agda2hs.compile.term" 50 $ text "Reached lambda"
  (dom, cod) <- mustBePi ty

  -- unusable domain, we remove the lambda and compile the body only
  if not (usableDom dom) then
    addContext dom $ compileTerm (absBody cod) (absBody abs)

  -- usable domain, user-written lambda is preserved
  else if getOrigin argi == UserWritten then do

    when (patternInTeleName `isPrefixOf` absName abs) $ agda2hsError $
      "Record pattern translation not supported. Use a pattern matching lambda instead."

    reportSDoc "agda2hs.compile" 17 $ text "compiling regular lambda"

    let varName = absName abs
        ctxElt  = (varName,) <$> dom

    hsLambda varName <$> addContext ctxElt (compileTerm (absBody cod) (absBody abs))

  -- usable domain, generated lambda means we introduce a section
  else do

    let varName = absName abs
        ctxElt  = (varName,) <$> dom

    addContext ctxElt $ do
      x <- compileDBVar 0
      compileTerm (absBody cod) (absBody abs) <&> \case
        Hs.InfixApp () a op b | a == hsVar x ->
          if pp op == "-" then -- Jesper: no right section for minus, as Haskell parses this as negation!
            Hs.LeftSection () b (Hs.QConOp () $ Hs.UnQual () $ hsName "subtract")
          else
            Hs.RightSection () op b -- System-inserted visible lambdas can only come from sections
        body -> hsLambda x body


-- | Compile the application of a function definition marked as inlinable.
-- The provided arguments will get substituted in the function body, and the missing arguments
-- will get quantified with lambdas.
compileInlineFunctionApp :: QName -> Type -> [Term] -> C (Hs.Exp ())
compileInlineFunctionApp f ty args = do
  reportSDoc "agda2hs.compile.term" 12 $ text "Compiling application of inline function"

  def <- getConstInfo f

  let ty' = defType def
  let Function{funClauses = cs} = theDef def
  let [Clause{namedClausePats = pats}] = filter (isJust . clauseBody) cs

  ty'' <- piApplyM ty args
  -- NOTE(flupe): very flimsy, there has to be a better way
  etaExpand (drop (length args) pats) ty' args >>= compileTerm ty''

  where
    -- inline functions can only have transparent constructor patterns and variable patterns
    extractPatName :: DeBruijnPattern -> ArgName
    extractPatName (VarP _ v) = dbPatVarName v
    extractPatName (ConP _ _ args) =
      let arg = namedThing $ unArg $ maybe __IMPOSSIBLE__ fst $ L.uncons $ filter (usableModality `and2M` visible) args
      in extractPatName arg
    extractPatName _ = __IMPOSSIBLE__

    extractName :: NamedArg DeBruijnPattern -> ArgName
    extractName (unArg -> np)
      | Just n <- nameOf np = rangedThing (woThing n)
      | otherwise = extractPatName (namedThing np)

    etaExpand :: NAPs -> Type -> [Term] -> C Term
    etaExpand [] ty args = do
      r <- liftReduce
            $ locallyReduceDefs (OnlyReduceDefs $ Set.singleton f)
            $ unfoldDefinitionStep (Def f [] `applys` args) f (Apply . defaultArg <$> args)
      case r of
        YesReduction _ t -> pure t
        _ -> agda2hsErrorM $ text "Could not reduce inline function" <+> prettyTCM f

    etaExpand (p:ps) ty args = do
      (dom, cod) <- mustBePi ty
      let ai = domInfo dom
      Lam ai . mkAbs (extractName p) <$> etaExpand ps (absBody cod) (raise 1 args ++ [ var 0 ])


compileApp :: Hs.Exp () -> Type -> [Term] -> C (Hs.Exp ())
compileApp = compileApp' . eApp

compileApp' :: ([Hs.Exp ()] -> Hs.Exp ()) -> Type -> [Term] -> C (Hs.Exp ())
compileApp' acc ty args = acc <$> compileArgs ty args

-- | Compile a list of arguments applied to a function of the given type.
compileArgs :: Type -> [Term] -> C [Hs.Exp ()]
compileArgs ty args = snd <$> compileArgs' ty args

compileArgs' :: Type -> [Term] -> C (Type, [Hs.Exp ()])
compileArgs' ty [] = pure (ty, [])
compileArgs' ty (x:xs) = do
  (a, b) <- mustBePi ty
  let rest = compileArgs' (absApp b x) xs
  compileDom a >>= \case
    DODropped  -> rest
    DOInstance -> checkInstance x *> rest
    DOType     -> checkValidType x *> rest
    DOTerm     -> second . (:) <$> compileTerm (unDom a) x <*> rest

-- We check that type arguments compile to a valid Haskell type
-- before dropping them, see issue #357.
checkValidType :: Term -> C ()
checkValidType x = noWriteImports (compileType x) *> return ()

clauseToAlt :: Hs.Match () -> C (Hs.Alt ())
clauseToAlt (Hs.Match _ _ [p] rhs wh) = pure $ Hs.Alt () p rhs wh
clauseToAlt (Hs.Match _ _ ps _ _)     = agda2hsError "Pattern matching lambdas must take a single argument"
clauseToAlt Hs.InfixMatch{}           = __IMPOSSIBLE__

compileLiteral :: Literal -> C (Hs.Exp ())
compileLiteral (LitNat n)    = return $ Hs.intE n
compileLiteral (LitFloat d)  = return $ Hs.Lit () $ Hs.Frac () (toRational d) (show d)
compileLiteral (LitWord64 w) = return $ Hs.Lit () $ Hs.PrimWord () (fromIntegral w) (show w)
compileLiteral (LitChar c)   = return $ Hs.charE c
compileLiteral (LitString t) = return $ Hs.Lit () $ Hs.String () s s
  where s = Text.unpack t
compileLiteral l             = agda2hsErrorM $ text "bad term:" <?> prettyTCM (Lit l)


checkInstance :: Term -> C ()
checkInstance u = do
  reportSDoc "agda2hs.checkInstance" 12 $ text "checkInstance" <+> prettyTCM u
  reduce u >>= \case
    Var x es -> do
      unlessM (isInstance <$> domOfBV x) illegalInstance
      checkInstanceElims es
    Def f es -> do
      unlessM (isJust . defInstance <$> getConstInfo f) illegalInstance
      importInstance f
      checkInstanceElims es
  -- We need to compile applications of `fromNat`, `fromNeg`, and
  -- `fromString` where the constraint type is ⊤ or IsTrue .... Ideally
  -- this constraint would be marked as erased but this would involve
  -- changing Agda builtins.
    Con c _ _
      | prettyShow (conName c) == "Agda.Builtin.Unit.tt" ||
        prettyShow (conName c) == "Haskell.Prim.IsTrue.itsTrue" ||
        prettyShow (conName c) == "Haskell.Prim.IsFalse.itsFalse" -> return ()
    _ -> illegalInstance

  where
    illegalInstance :: C ()
    illegalInstance = do
      reportSDoc "agda2hs.checkInstance" 15 $ text "illegal instance: " <+> pretty u
      agda2hsErrorM $ text "illegal instance: " <+> prettyTCM u

    checkInstanceElims :: Elims -> C ()
    checkInstanceElims = mapM_ checkInstanceElim

    checkInstanceElim :: Elim -> C ()
    checkInstanceElim (Apply v) =
      when (isInstance v && usableQuantity v) $
        checkInstance $ unArg v
    checkInstanceElim IApply{} = illegalInstance
    checkInstanceElim (Proj _ f) =
      unlessM (isInstance . defArgInfo <$> getConstInfo f) illegalInstance