Intro

It’s hard to get into writing code that uses GHC API. It’s huge there are so many options around and not a lot of introduction-level tutorials.

In this series of blog posts I’ll elaborate on some of the peculiar, interesting problems I’ve encountered during my experience writing code that uses GHC API and also provide various tips I find useful.

I have built for myself a small layer of helper functions that helped me with using GHC API for the interactive-diagrams project. The source can be found on GitHub and I plan on refactoring the code and releasing it separately.

Error handling

Today I would like to talk about setting your own error handlers for GHC API. By default you can expect GHC to spew all the errors onto your screen, but for my purposes I wanted to log them.

Naturally at first I tried the following:

I am in need of setting up custom exception handlers when using GHC API to compile modules. Right now I have the following piece of code:

-- Main.hs: import GHC import GHC.Paths import MonadUtils import Exception import Panic import Unsafe.Coerce import System.IO.Unsafe -- I thought this code would handle the exception handleException :: ( ExceptionMonad m , MonadIO m ) => m a -> m ( Either String a ) handleException m = ghandle ( \ ( ex :: SomeException ) -> return ( Left ( show ex ) ) ) $ handleGhcException ( \ ge -> return ( Left ( showGhcException ge "" ) ) ) $ flip gfinally ( liftIO restoreHandlers ) $ m >>= return . Right -- Initializations, needed if you want to compile code on the fly initGhc :: Ghc () initGhc = do dfs <- getSessionDynFlags setSessionDynFlags $ dfs { hscTarget = HscInterpreted , ghcLink = LinkInMemory } return () -- main entry point main = test >>= print test :: IO ( Either String Int ) test = handleException $ runGhc ( Just libdir ) $ do initGhc setTargets =<< sequence [ guessTarget "file1.hs" Nothing ] graph <- depanal [] False loaded <- load LoadAllTargets -- when (failed loaded) $ throw LoadingException setContext ( map ( IIModule . moduleName . ms_mod ) graph ) let expr = "run" res <- unsafePerformIO . unsafeCoerce <$> compileExpr expr return res -- file1.hs: module Main where main = return () run :: IO Int run = do n <- x return ( n+ 1 )

The problem is when I run the ‘test’ function above I receive the following output:

h> test test/file1.hs:4:10: Not in scope: `x' Left "Cannot add module Main to context: not a home module" it :: Either String Int

What the ..? My exception handler did catch the error, but:

A strange one The error I intended to catch got

Is there a way to fix this?

Solution

I even asked this problem on the Haskell-Cafe mailing list, but the folks there don’t seem to be very keen on GHC/GHC API (which is understandable) and I haven’t got any answers.

But thanks to my mentor Luite Stegeman we’ve found the solution.

Errors are handled using the LogAction specified in the DynFlags for your GHC session. So to fix this you need to change ‘log_action’ parameter in dynFlags. For example, you can do this:

initGhc = do .. ref <- liftIO $ newIORef "" dfs <- getSessionDynFlags setSessionDynFlags $ dfs { hscTarget = HscInterpreted , ghcLink = LinkInMemory , log_action = logHandler ref -- ^ this } -- LogAction == DynFlags -> Severity -> SrcSpan -> PprStyle -> MsgDoc -> IO () logHandler :: IORef String -> LogAction logHandler ref dflags severity srcSpan style msg = case severity of SevError -> modifyIORef' ref ( ++ printDoc ) SevFatal -> modifyIORef' ref ( ++ printDoc ) _ -> return () -- ignore the rest where cntx = initSDocContext dflags style locMsg = mkLocMessage severity srcSpan msg printDoc = show ( runSDoc locMsg cntx )

Outro

That’s the first tip and the first post in the series, stay tuned for more updates.