agda2hs-1.2: src/Agda2Hs/Compile/Record.hs
{-# LANGUAGE NamedFieldPuns #-}
module Agda2Hs.Compile.Record where
import Control.Monad ( unless )
import Control.Monad.Reader ( MonadReader(local) )
import Data.List ( (\\), nub )
import Data.List.NonEmpty ( NonEmpty(..) )
import Data.Map ( Map )
import qualified Data.Map as Map
import qualified Language.Haskell.Exts as Hs
import Agda.Compiler.Backend
import Agda.Syntax.Common ( Arg(unArg), defaultArg )
import Agda.Syntax.Internal
import Agda.Syntax.Common.Pretty ( prettyShow )
import Agda.TypeChecking.Pretty ( ($$), (<+>), text, vcat )
import Agda.TypeChecking.Substitute ( TelV(TelV), Apply(apply) )
import Agda.TypeChecking.Telescope
import Agda.Utils.Impossible ( __IMPOSSIBLE__ )
import Agda2Hs.AgdaUtils
import Agda2Hs.Compile.ClassInstance
import Agda2Hs.Compile.Function ( compileFun )
import Agda2Hs.Compile.Type ( compileDom, compileTeleBinds )
import Agda2Hs.Compile.Types
import Agda2Hs.Compile.Utils
import Agda2Hs.HsUtils
-- | Primitive fields and default implementations
type MinRecord = ([Hs.Name ()], Map (Hs.Name ()) (Hs.Decl ()))
withMinRecord :: QName -> C a -> C a
withMinRecord m = local $ \ e -> e { minRecordName = Just (qnameToMName m) }
compileMinRecord :: [Hs.Name ()] -> QName -> C MinRecord
compileMinRecord fieldNames m = do
rdef <- getConstInfo m
definedFields <- classMemberNames rdef
let Record{recPars = npars, recTel = tel} = theDef rdef
pars = map Apply $ take npars $ teleArgs tel
rtype = El __DUMMY_SORT__ $ Def m pars
defaults <- lookupDefaultImplementations m (fieldNames \\ definedFields)
-- We can't simply compileFun here for two reasons:
-- * it has an explicit dictionary argument
-- * it's using the fields and definitions from the minimal record and not the parent record
compiled <- withMinRecord m $ addContext (defaultDom rtype) $
fmap concat $ traverse (compileFun False) defaults
let declMap = Map.fromList [ (definedName c, def) | def@(Hs.FunBind _ (c : _)) <- compiled ]
return (definedFields, declMap)
compileMinRecords :: Definition -> [String] -> C [Hs.Decl ()]
compileMinRecords def sls = do
members <- classMemberNames def
qnames <- traverse resolveStringName sls
(prims, defaults) <- unzip <$> traverse (compileMinRecord members) qnames
-- 0. [OPTIONAL] check all record signatures match (or simply leave to GHC)
-- 1. build minimal pragma
let
-- make the formula for a list of names of methods for for a single minimal instance
helpAnd :: [Hs.Name ()] -> Hs.BooleanFormula ()
helpAnd xs = Hs.AndFormula () $ Hs.VarFormula () <$> xs
-- combine formulae for all minimal instances
helpOr :: [Hs.BooleanFormula ()] -> Hs.Decl ()
helpOr bs = Hs.MinimalPragma () (Just $ Hs.OrFormula () bs)
minPragma = helpOr (map helpAnd prims)
-- 2. assert that all default implementations are the same (for a certain field)
let getUnique f (x :| xs)
| all (x ==) xs = return x
| otherwise = genericDocError =<< do
text ("Conflicting default implementations for " ++ pp f ++ ":") $$
vcat [ text "-" <+> multilineText (pp d) | d <- nub (x : xs) ]
decls <- Map.traverseWithKey getUnique
$ Map.unionsWith (<>) $ (map . fmap) (:| []) defaults
-- TODO: order default implementations differently?
return ([minPragma | not (null prims)] ++ Map.elems decls)
compileRecord :: RecordTarget -> Definition -> C (Hs.Decl ())
compileRecord target def = do
TelV tel _ <- telViewUpTo recPars (defType def)
addContext tel $ checkingVars $ do
checkValidTypeName rName
binds <- compileTeleBinds tel
let hd = foldl (Hs.DHApp ()) (Hs.DHead () rName) binds
let fieldTel = snd $ splitTelescopeAt recPars recTel
case target of
ToClass ms -> do
(classConstraints, classDecls) <- compileRecFields classDecl recFields fieldTel
let context = case classConstraints of
[] -> Nothing
[asst] -> Just (Hs.CxSingle () asst)
assts -> Just (Hs.CxTuple () assts)
defaultDecls <- compileMinRecords def ms
return $ Hs.ClassDecl () context hd [] (Just (classDecls ++ map (Hs.ClsDecl ()) defaultDecls))
ToRecord ds -> do
checkValidConName cName
(constraints, fieldDecls) <- compileRecFields fieldDecl recFields fieldTel
compileDataRecord constraints fieldDecls (Hs.DataType ()) hd ds
ToRecordNewType ds -> do
checkValidConName cName
(constraints, fieldDecls) <- compileRecFields fieldDecl recFields fieldTel
checkSingleElement cName fieldDecls "Newtype must have exactly one field in constructor"
compileDataRecord constraints fieldDecls (Hs.NewType ()) hd ds
where
rName = hsName $ prettyShow $ qnameName $ defName def
cName | recNamedCon = hsName $ prettyShow $ qnameName $ conName recConHead
| otherwise = rName -- Reuse record name for constructor if no given name
-- In Haskell, projections live in the same scope as the record type, so check here that the
-- record module has been opened.
checkFieldInScope f = isInScopeUnqualified f >>= \case
True -> return ()
False -> setCurrentRangeQ f $ genericError $
"Record projections (`" ++ prettyShow (qnameName f) ++
"` in this case) must be brought into scope when compiling to Haskell record types. " ++
"Add `open " ++ Hs.prettyPrint rName ++ " public` after the record declaration to fix this."
Record{..} = theDef def
classDecl :: Hs.Name () -> Hs.Type () -> Hs.ClassDecl ()
classDecl n = Hs.ClsDecl () . Hs.TypeSig () [n]
fieldDecl :: Hs.Name () -> Hs.Type () -> Hs.FieldDecl ()
fieldDecl n = Hs.FieldDecl () [n]
compileRecFields :: (Hs.Name () -> Hs.Type () -> b)
-> [Dom QName] -> Telescope -> C ([Hs.Asst ()], [b])
compileRecFields decl ns tel = case (ns, tel) of
(_ , EmptyTel ) -> return ([], [])
(n:ns, ExtendTel dom tel') -> do
hsDom <- compileDom (absName tel') dom
(hsAssts, hsFields) <- underAbstraction dom tel' $ compileRecFields decl ns
case hsDom of
DomType s hsA -> do
let fieldName = hsName $ prettyShow $ qnameName $ unDom n
fieldType <- addTyBang s hsA
checkValidFunName fieldName
return (hsAssts, decl fieldName fieldType : hsFields)
DomConstraint hsA -> case target of
ToClass{} -> return (hsA : hsAssts , hsFields)
ToRecord{} -> genericError $
"Not supported: record/class with constraint fields"
DomDropped -> return (hsAssts , hsFields)
(_, _) -> __IMPOSSIBLE__
compileDataRecord :: [Hs.Asst ()] -> [Hs.FieldDecl ()] -- compiled rec fields
-> Hs.DataOrNew () -- whether to compile to data or newtype
-> Hs.DeclHead () -- the head of the type declaration
-> [Hs.Deriving ()] -- data extracted from the pragma
-> C (Hs.Decl ())
compileDataRecord constraints fieldDecls don hd ds = do
unless (null constraints) __IMPOSSIBLE__ -- no constraints for records
mapM_ checkFieldInScope (map unDom recFields)
let conDecl = Hs.QualConDecl () Nothing Nothing $ Hs.RecDecl () cName fieldDecls
return $ Hs.DataDecl () don Nothing hd [conDecl] ds
checkUnboxPragma :: Defn -> C ()
checkUnboxPragma def
| Record{recFields} <- def
, length (filter keepArg recFields) == 1
, not (recRecursive def)
= return ()
| otherwise
= genericError "An unboxed type must be a non-recursive record type with exactly one non-erased field."