Changes between Version 22 and Version 23 of GhcApiStatus

Aug 28, 2008 9:17:58 PM (7 years ago)

Update API changes


  • GhcApiStatus

    v22 v23  
    1010= Current GHC API design =
    12 Most exported API functions that were previously in {{{IO}}} are now
    13 in the {{{Ghc}}} monad.  Those functions now no longer require a {{{Session}}}
    14 parameter.  To start a GHC API session you now use:
     12There were two main issues which were large cross-cutting concerns and, unfortunately, took up most of my time: (a) consistent error handling and (b) explicit passing around of a `Session`.
     14The latter issue is rather straightforward to fix by introducing a monad, however, functions expecting `IO` callbacks complicated things a little bit.  Consistent error handling is trickier.  I decided to use extensible exceptions which required some `#ifdef`s to properly bootstrap and some special `handle*` type functions to portably handle certain exceptions (akin to `handleGhcException`.)  But first, an overview:
     16A `GhcMonad` is a class, and a default datatype which implements it is the `Ghc` monad.  Most interface functions will be of type `GhcMonad m => ... -> m a`.  This should make it easier to use these functions in custom monads which most non-trivial clients will likely need.  Very simple clients can just reuse the `Ghc` monad.  The `GhcMonad` class is defined as follows:
     18class (MonadIO m, WarnLogMonad m, ExceptionMonad m) => GhcMonad m where
     19  getSession :: m HscEnv
     20  setSession :: HscEnv -> m ()
     23The three required classes ensure that: we can use `liftIO` to call `IO` actions inside the GHC monad, accumulate warnings, and handle (extensible) exceptions in that monad.  The three classes are defined as follows:
     25class Monad m => MonadIO m where   -- util/MonadUtils.hs
     26  liftIO :: IO a -> m a
     28type WarningMessages = Bag WarnMsg
     29class Monad m => WarnLogMonad m where -- main/HscTypes.lhs
     30  setWarnings  :: WarningMessages -> m ()
     31  getWarnings :: m WarningMessages
     32  -- An alternative interface with 'addWarnings' and 'clearWarnings'
     33  -- instead of 'setWarnings' may be nicer if we just want to dump
     34  -- warnings somewhere and not accumulate (in which case
     35  -- 'getWarnings' would always return the emptyBag)
     37class Monad m => ExceptionMonad m where -- utils/Exception.hs
     38  gcatch :: Exception e => m a -> (e -> m a) -> m a
     39  gbracket :: m a -> (a -> m b) -> (a -> m c) -> m c
     40  gfinally :: m a -> m b -> m a
     41  -- 'gfinally' and 'gbracket' may be implemented in terms of
     42  -- 'gcatch' if we add 'gblock' and 'gunblock'.
     43  -- The version for GHC < 6.9 also contains 'gcatchDyn'.
     46There are two GHC-API-specific exceptions:
     48data SourceError = SourceError ErrorMessages
     49data GhcApiError = GhcApiError SDoc
     51mkSrcErr :: ErrorMessages -> SourceError
     52srcErrorMessages :: SourceError -> ErrorMessages
     53mkApiErr :: SDoc -> GhcApiError
     56A source error corresponds to a problem with the compiled code and contains all accumulated error messages (but no warnings).  An API error is used to signal failure of an API call and replace many 'Maybe' results.  The choice wasn't always obvious.  In general API errors should be seldom, but catchable, i.e., they should be rare, but not entirely unexpected.  I guess, the choice which functions return which error needs some fine tuning.
     58The 'WarnLogMonad' does what it's name says.  It accumulate warnings, which can be queried with 'getWarnings' and 'clearWarnings'.  Deciding when to clear warnings is a bit delicate.  ATM, I provide a default function that should be invoked in case of a source error (i.e., compilation failure) which prints all errors and warnings and clears the accumulated warnings.
     60printExceptionAndWarnings :: GhcMonad m => SourceError -> m ()
     61printExceptionAndWarnings err = do
     62    let errs = srcErrorMessages err
     63    warns <- getWarnings
     64    dflags <- getSessionDynFlags
     65    liftIO $ printErrorsAndWarnings dflags (warns, errs)
     66    clearWarnings
     69To start a GHC API session you now use:
    1772withGhc :: Maybe FilePath  -- path to GHC library
    18         -> Maybe [String]  -- ^ Optional list of static flags.
    1973        -> Ghc a           -- ^ The action(s) to perform.
    2074        -> IO a
    2377The first parameter can be determined automatically with the ghc-path
    24 package.  The second is a set of "static" command line flags, for
    25 example, profiling options.  Having those part of the run function for
    26 the monad avoids complicated usage rules (e.g. before parseStaticFlags
    27 had to be called before {{{newSession}}}).
     80TODO: We miss a `GhcT` monad transformer and a init function for custom monads `initSession :: GhcMonad m => Maybe FilePath -> m a`.
     82== Callbacks ==
     84Most of GHC's internal IO callbacks have been changed to use the proper class, e.g., `(MonadIO m, ExceptionMonad m) => ... -> m a -> m a`.  However, this cannot be done for external functions with callbacks.  A particularly complicated case are the asynchronous (FFI-initiated) callbacks of the Readline package.  For this case I added two functions to reflect into and reify from the `IO` monad:
     86-- | Reflect a computation in the 'Ghc' monad into the 'IO' monad.
     88-- You can use this to call functions returning an action in the 'Ghc' monad
     89-- inside a 'IO' action.  This is needed for some (too restrictive) callback
     90-- arguments of some library functions:
     92-- > libFunc :: String -> (Int -> IO a) -> IO a
     93-- > ghcFunc :: Int -> Ghc a
     94-- >
     95-- > ghcFuncUsingLibFunc :: String -> Ghc a -> Ghc a
     96-- > ghcFuncUsingLibFunc str =
     97-- >   reifyGhc $ \s ->
     98-- >     libFunc $ \i -> do
     99-- >       reflectGhc (ghcFunc i) s
     101reflectGhc :: Ghc a -> Session -> IO a
     102reflectGhc m = unGhc m
     104-- > Dual to 'reflectGhc'.  See its documentation.
     105reifyGhc :: (Session -> IO a) -> Ghc a
     106reifyGhc act = Ghc $ act
     109== Interface Changes ==
    29111{{{load}}} and {{{setTarget}}} work like before.  {{{checkModule}}}