agda2hs-1.4: src/Agda2Hs/Compile/Function.hs
module Agda2Hs.Compile.Function where
import Control.Arrow ( (***) )
import Control.Monad ( (>=>), filterM, forM_ )
import Control.Monad.Reader ( asks, local )
import Data.Generics
import Data.List
import Data.Maybe ( fromMaybe, isJust )
import qualified Data.Text as Text
import Agda.Compiler.Backend
import Agda.Compiler.Common
import Agda.Syntax.Common
import Agda.Syntax.Internal
import Agda.Syntax.Internal.Pattern ( patternToTerm )
import Agda.Syntax.Literal
import Agda.Syntax.Common.Pretty ( prettyShow )
import Agda.Syntax.Scope.Monad ( isDatatypeModule )
import Agda.TypeChecking.Pretty
import Agda.TypeChecking.Substitute
import Agda.TypeChecking.Telescope ( telView, mustBePi, piApplyM )
import Agda.TypeChecking.Sort ( ifIsSort )
import Agda.TypeChecking.Datatypes ( getConType, isDataOrRecord )
import Agda.TypeChecking.Records ( getDefType )
import Agda.TypeChecking.Reduce ( reduce )
import Agda.Utils.Functor ( (<&>), dget)
import Agda.Utils.Impossible ( __IMPOSSIBLE__ )
import Agda.Utils.Lens ((^.))
import Agda.Utils.List
import Agda.Utils.Maybe
import Agda.Utils.Monad
import Agda.Utils.Size ( Sized(size) )
import Agda2Hs.AgdaUtils
import Agda2Hs.Compile.Name ( compileQName )
import Agda2Hs.Compile.Term ( compileTerm, usableDom )
import Agda2Hs.Compile.Type ( compileType, compileDom, DomOutput(..), compileDomType )
import Agda2Hs.Compile.TypeDefinition ( compileTypeDef )
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
( Strictness, hsName, pApp, patToExp, constrainType, qualifyType )
-- | Compilation rules for specific constructors in patterns.
isSpecialCon :: QName -> Maybe (Type -> NAPs -> C (Hs.Pat ()))
isSpecialCon qn = case prettyShow qn of
s | s `elem` badConstructors -> Just itsBad
"Agda.Builtin.Int.Int.pos" -> Just posIntPat
"Agda.Builtin.Int.Int.negsuc" -> Just negSucIntPat
_ -> Nothing
where
badConstructors =
[ "Agda.Builtin.Nat.Nat.zero"
, "Agda.Builtin.Nat.Nat.suc"
, "Haskell.Extra.Delay.Delay.now"
, "Haskell.Extra.Delay.Delay.later"
]
itsBad :: Type -> NAPs -> C (Hs.Pat ())
itsBad _ _ = agda2hsErrorM $ "constructor `" <> prettyTCM qn <> "` not supported in patterns"
-- | Translate Int.pos pattern.
posIntPat :: Type -> NAPs -> C (Hs.Pat ())
posIntPat ty [p] = do
n <- compileLitNatPat (namedArg p)
return $ Hs.PLit () (Hs.Signless ()) (Hs.Int () n (show n))
posIntPat _ _ = __IMPOSSIBLE__
-- | Translate Int.negsuc pattern.
negSucIntPat :: Type -> NAPs -> C (Hs.Pat ())
negSucIntPat ty [p] = do
n <- (1+) <$> compileLitNatPat (namedArg p)
return $ Hs.PLit () (Hs.Negative ()) (Hs.Int () n (show (negate n)))
negSucIntPat _ _ = __IMPOSSIBLE__
-- Agda2Hs does not support natural number patterns directly (since
-- they don't exist in Haskell), however they occur as part of
-- patterns of type Integer, so we need to compile literal natural
-- number patterns.
compileLitNatPat :: DeBruijnPattern -> C Integer
compileLitNatPat = \case
ConP ch _ ps
| prettyShow (conName ch) == "Agda.Builtin.Nat.Nat.zero" -> return 0
| prettyShow (conName ch) == "Agda.Builtin.Nat.Nat.suc"
, [p] <- ps -> (1+) <$> compileLitNatPat (namedArg p)
p -> agda2hsErrorM $ "not a literal natural number pattern:" <?> prettyTCM p
compileFun, compileFun'
:: Bool -- ^ Whether the type signature shuuld also be generated
-> Definition -> C [Hs.Decl ()]
compileFun withSig def@Defn{..} =
setCurrentRangeQ defName
$ maybePrependFixity defName (defName ^. lensFixity)
-- initialize locals when first stepping into a function
$ withFunctionLocals defName
$ compileFun' withSig def
-- inherit existing (instantiated) locals
compileFun' withSig def@Defn{..} = inTopContext $ withCurrentModule m $ do
reportSDoc "agda2hs.compile" 6 $ "Compiling function: " <+> prettyTCM defName
whenM ((withSig &&) <$> inRecordMod defName) $
agda2hsError "not supported: functions inside a record module"
ifM (endsInSort defType)
-- if the function type ends in Sort, it's a type alias!
(ensureNoLocals err >> compileTypeDef x def)
-- otherwise, we have to compile clauses.
$ do
tel <- lookupSection m
-- If this is a top-level function, we compile the module parameters so
-- we can add them to the type signature and patterns.
(paramTy , paramPats) <- ifM (asks compilingLocal) (return (id, [])) $ compileModuleParams tel
addContext tel $ do
-- Jesper: we need to set the checkpoint for the current module so that
-- the canonicity check for typeclass instances picks up the
-- module parameters (see https://github.com/agda/agda2hs/issues/305).
liftTCM $ setModuleCheckpoint m
-- We apply the function clause to the module parameters from the context.
-- In case of a projection-like function, the clause is already
-- (partially or fully) applied so we should not apply again
-- (see https://github.com/agda/agda2hs/issues/359)
let droppedPars = case funProjection of
Left{} -> 0
Right proj -> size $ getProjLams $ projLams proj
pars <- drop droppedPars <$> getContextArgs
reportSDoc "agda2hs.compile.type" 8 $ "Function module parameters: " <+> prettyTCM pars
reportSDoc "agda2hs.compile.type" 8 $ "Function type (before instantiation): " <+> inTopContext (prettyTCM defType)
typ <- piApplyM defType pars
reportSDoc "agda2hs.compile.type" 8 $ "Function type (after instantiation): " <+> prettyTCM typ
sig <- if not withSig then return [] else do
checkValidFunName x
ty <- paramTy <$> compileType (unEl typ)
reportSDoc "agda2hs.compile.type" 8 $ "Compiled function type: " <+> text (Hs.prettyPrint ty)
return [Hs.TypeSig () [x] ty]
let clauses = funClauses `apply` pars
cs <- map (addPats paramPats) <$>
mapMaybeM (compileClause m (Just defName) x typ) clauses
when (null cs) $ agda2hsErrorM $
text "Functions defined with absurd patterns exclusively are not supported."
<+> text "Use function `error` from the Haskell.Prelude instead."
return $ sig ++ [Hs.FunBind () cs]
where
Function{..} = theDef
m = qnameModule defName
n = qnameName defName
x = hsName $ prettyShow n
err = "Not supported: type definition with `where` clauses"
addPats :: [Hs.Pat ()] -> Hs.Match () -> Hs.Match ()
addPats [] cl = cl
addPats ps (Hs.Match l f qs rhs bs) = Hs.Match l f (ps++qs) rhs bs
addPats (p:ps) (Hs.InfixMatch l q f qs rhs bs) = Hs.InfixMatch l p f (ps++q:qs) rhs bs
compileModuleParams :: Telescope -> C (Hs.Type () -> Hs.Type () , [Hs.Pat ()])
compileModuleParams EmptyTel = return (id, [])
compileModuleParams (ExtendTel a tel) = do
(f , p) <- compileDomType (absName tel) a >>= \case
DomDropped -> return (id, [])
DomConstraint c -> return (constrainType c, [])
DomForall a -> return (qualifyType a, [])
DomType s a -> do
let n = hsName $ absName tel
checkValidVarName n
return (Hs.TyFun () a, [Hs.PVar () n])
((f .) *** (p++)) <$> underAbstraction a tel compileModuleParams
compileClause :: ModuleName -> Maybe QName -> Hs.Name () -> Type -> Clause -> C (Maybe (Hs.Match ()))
compileClause curModule mproj x t c =
withClauseLocals curModule c $ do
compileClause' curModule mproj x t c
compileClause' :: ModuleName -> Maybe QName -> Hs.Name () -> Type -> Clause -> C (Maybe (Hs.Match ()))
compileClause' curModule projName x ty c@Clause{..} = do
reportSDoc "agda2hs.compile" 7 $ "compiling clause: " <+> prettyTCM c
ifNotM (keepClause c) (pure Nothing) $ addContext (KeepNames clauseTel) $ do
reportSDoc "agda2hs.compile" 17 $ "Old context: " <+> (inTopContext . prettyTCM =<< getContext)
reportSDoc "agda2hs.compile" 17 $ "Clause telescope: " <+> inTopContext (prettyTCM clauseTel)
reportSDoc "agda2hs.compile" 17 $ "Clause type: " <+> prettyTCM clauseType
reportSDoc "agda2hs.compile" 17 $ "Function type: " <+> prettyTCM ty
reportSDoc "agda2hs.compile" 17 $ "Clause patterns: " <+> text (prettyShow namedClausePats)
reportSDoc "agda2hs.compile" 18 $ "Clause module:" <+> prettyTCM curModule
ls <- asks locals
reportSDoc "agda2hs.compile" 18 $ "Clause locals:" <+> prettyTCM ls
toDrop <- case projName of
Nothing -> pure 0
Just q -> maybe 0 (pred . projIndex) <$> isProjection q
reportSDoc "agda2hs.compile" 17 $ "Args to drop (proj-like): " <+> prettyTCM toDrop
-- NOTE(flupe: for projection-like definitions, we drop the first parameters)
let ntel = size clauseTel
ty <- ty `piApplyM` [Var (ntel - k - 1) [] | k <- [0.. (toDrop - 1)]]
reportSDoc "agda2hs.compile" 17 $ "Corrected type: " <+> prettyTCM ty
ps <- compilePats ty namedClausePats
let isWhereDecl = not . isExtendedLambdaName
/\ (curModule `isFatherModuleOf`) . qnameModule
children <- filter isWhereDecl <$> asks locals
-- TODO: remove this when Agda exposes where-provenance in 'Internal' syntax
let withWhereModule = case children of
[] -> id
(c:_) -> addWhereModule $ qnameModule c
whereDecls <- withWhereModule $ compileLocal $ mapM (getConstInfo >=> compileFun' True) children
let Just body = clauseBody
Just (unArg -> typ) = clauseType
hsBody <- withWhereModule $ compileTerm typ body
let rhs = Hs.UnGuardedRhs () hsBody
whereBinds | null whereDecls = Nothing
| otherwise = Just $ Hs.BDecls () (concat whereDecls)
match = case (x, ps) of
(Hs.Symbol{}, p : q : ps) -> Hs.InfixMatch () p x (q : ps) rhs whereBinds
_ -> Hs.Match () x ps rhs whereBinds
return $ Just match
keepClause :: Clause -> C Bool
keepClause c@Clause{..} = case (clauseBody, clauseType) of
(Nothing, _) -> pure False
(_, Nothing) -> pure False
(Just body, Just cty) -> compileDom (domFromArg cty) <&> \case
DODropped -> False
DOInstance -> True
DOType -> __IMPOSSIBLE__
DOTerm -> True
-- TODO(flupe): projection-like definitions are missing the first (variable) patterns
-- (that are however present in the type)
-- so we should drop the first parameters in the input type (using funProjection.projLams)
compilePats :: Type -> NAPs -> C [Hs.Pat ()]
compilePats _ [] = pure []
compilePats ty ((namedArg -> ProjP po pn):ps) = do
reportSDoc "agda2hs.compile" 10 $ "compiling copattern: " <+> text (prettyShow pn)
unlessM (asks copatternsEnabled `or2M` (isJust <$> isUnboxProjection pn)) $
agda2hsError "not supported in Haskell: copatterns"
ty <- fromMaybe __IMPOSSIBLE__ <$> getDefType pn ty
(a, b) <- mustBePi ty
compilePats (absBody b) ps
compilePats ty ((namedArg -> pat):ps) = do
(a, b) <- mustBePi ty
reportSDoc "agda2hs.compile.pattern" 10 $ text "Compiling pattern:" <+> prettyTCM pat
let rest = compilePats (absApp b (patternToTerm pat)) ps
when (usableDom a) checkForced
compileDom a >>= \case
DOInstance -> rest
DODropped -> rest
DOType -> rest
DOTerm -> do
checkNoAsPatterns pat
(:) <$> compilePat (unDom a) pat <*> rest
where checkForced = when (isForcedPat pat) $ agda2hsError "not supported: forced (dot) patterns in non-erased positions"
compilePat :: Type -> DeBruijnPattern -> C (Hs.Pat ())
-- variable pattern
compilePat ty p@(VarP o x)
| PatOWild <- patOrigin o = return $ Hs.PWildCard ()
| otherwise = do
n <- hsName <$> compileDBVar (dbPatVarIndex x)
checkValidVarName n
return $ Hs.PVar () n
-- special constructor pattern
compilePat ty (ConP ch i ps) = do
Just ((_, _, _), ty) <- getConType ch =<< reduce ty
let c = conName ch
ifJust (isSpecialCon c) (\semantics -> setCurrentRange ch $ semantics ty ps) $ do
ifJustM (isUnboxConstructor c) (\s -> compileErasedConP ty s ps) $ do
ifJustM (isTupleConstructor c) (\b -> compileTupleConP ty b ps) $ do
ps <- compilePats ty ps
c <- compileQName (conName ch)
return $ pApp c ps
-- literal patterns
compilePat ty (LitP _ l) = compileLitPat l
-- nothing else is supported
compilePat _ p = agda2hsErrorM $ "bad pattern:" <?> prettyTCM p
compileErasedConP :: Type -> Strictness -> NAPs -> C (Hs.Pat ())
compileErasedConP ty s ps = compilePats ty ps >>= \case
[p] -> addPatBang s p
_ -> __IMPOSSIBLE__
compileLitPat :: Literal -> C (Hs.Pat ())
compileLitPat = \case
LitChar c -> return $ Hs.charP c
l -> agda2hsErrorM $ "bad literal pattern:" <?> prettyTCM l
compileTupleConP :: Type -> Hs.Boxed -> NAPs -> C (Hs.Pat ())
compileTupleConP ty b ps = do
ps <- compilePats ty ps
return $ Hs.PTuple () b ps
-- Local (where) declarations ---------------------------------------------
-- TODO: simplify this when Agda exposes where-provenance in 'Internal' syntax
-- | Run a computation with all the local declarations in the state.
withFunctionLocals :: QName -> C a -> C a
withFunctionLocals q k = do
ls <- takeWhile (isAnonymousModuleName . qnameModule)
. dropWhile (<= q)
. map fst
. filter (usableModality . getModality . snd) -- drop if it's an erased definition anyway
. sortDefs <$> liftTCM curDefs
reportSDoc "agda2hs.compile.locals" 17 $ "Function locals: "<+> prettyTCM ls
withLocals ls k
-- | Filter local declarations that belong to the given module.
zoomLocals :: ModuleName -> LocalDecls -> LocalDecls
zoomLocals mname = filter ((mname `isLeParentModuleOf`) . qnameModule)
-- | Before checking a clause, grab all of its local declarations.
-- TODO: simplify this when Agda exposes where-provenance in 'Internal' syntax
withClauseLocals :: ModuleName -> Clause -> C a -> C a
withClauseLocals curModule c@Clause{..} k = do
ls <- asks locals
let
uses = filter
( (curModule `isFatherModuleOf`) . qnameModule
\/ (`extLamUsedIn` c) )
(getLocalUses ls c)
nonExtLamUses = qnameModule <$> filter (not . isExtendedLambdaName) uses
whereModuleName
| null uses = Nothing
| otherwise = Just $ case nonExtLamUses ++ [curModule] of
(x:_) -> x
_ -> __IMPOSSIBLE__
ls' = case whereModuleName of
Nothing -> []
Just m -> zoomLocals m ls
reportSDoc "agda2hs.compile.locals" 18 $ "Clause locals: "<+> prettyTCM ls'
withLocals ls' k
-- | Ensure a definition can be defined as transparent.
checkTransparentPragma :: Definition -> C ()
checkTransparentPragma def = compileFun False def >>= \case
[Hs.FunBind _ cls] ->
mapM_ checkTransparentClause cls
[Hs.TypeDecl _ hd b] ->
checkTransparentTypeDef hd b
_ -> __IMPOSSIBLE__
where
checkTransparentClause :: Hs.Match () -> C ()
checkTransparentClause = \case
Hs.Match _ _ [p] (Hs.UnGuardedRhs _ e) _ | patToExp p == Just e -> return ()
_ -> errNotTransparent
checkTransparentTypeDef :: Hs.DeclHead () -> Hs.Type () -> C ()
checkTransparentTypeDef (Hs.DHApp _ _ (Hs.UnkindedVar _ x)) (Hs.TyVar _ y) | x == y = return ()
checkTransparentTypeDef _ _ = errNotTransparent
errNotTransparent = agda2hsErrorM $
"Cannot make function" <+> prettyTCM (defName def) <+> "transparent." <+>
"A transparent function must have exactly one non-erased argument and return it unchanged."
-- | Ensure a definition can be defined as inline.
checkInlinePragma :: Definition -> C ()
checkInlinePragma def@Defn{defName = f} = do
let Function{funClauses = cs} = theDef def
case filter (isJust . clauseBody) cs of
[c] ->
unlessM (allowedPats (namedClausePats c)) $ agda2hsErrorM $
"Cannot make function" <+> prettyTCM (defName def) <+> "inlinable." <+>
"Inline functions can only use variable patterns or transparent record constructor patterns."
_ ->
agda2hsErrorM $
"Cannot make function" <+> prettyTCM f <+> "inlinable." <+>
"An inline function must have exactly one clause."
where allowedPat :: DeBruijnPattern -> C Bool
allowedPat VarP{} = pure True
-- only allow matching on (unboxed) record constructors
allowedPat (ConP ch ci cargs) =
isUnboxConstructor (conName ch) >>= \case
Just _ -> allowedPats cargs
Nothing -> pure False
allowedPat _ = pure False
allowedPats :: NAPs -> C Bool
allowedPats pats = allM (allowedPat . dget . dget) pats