packages feed

ide-backend-server-0.9.0: Haddock.hs

module Haddock (
    -- Package dependencies
    pkgDepsFromModSummary
    -- Interfacing with Haddock
  , haddockInterfaceFor
    -- Link environment
  , LinkEnv
  , homeModuleFor
    -- Link environment cache
  , linkEnvForDeps
  ) where

-- Platform imports
import Prelude hiding (mod)
import System.IO.Unsafe (unsafePerformIO)
import Control.Exception (SomeException)
import Control.Applicative ((<$>))
import qualified Control.Exception as Ex
import Data.Maybe (catMaybes)
import Data.Either (rights)
import qualified Data.Map as LazyMap

-- GHC imports; unqualified where no confusion can arise
import GHC (DynFlags, unLoc, RdrName, ImportDecl(..), Located)
import MonadUtils (MonadIO(..)) -- ghc's MonadIO
import Outputable (defaultUserStyle)
import qualified GHC
import qualified Name     as GHC
import qualified Packages as GHC

-- Haddock imports
import qualified Documentation.Haddock as Hk

-- ide-backend imports
import IdeSession.Types.Private
import IdeSession.Strict.Container
import IdeSession.Strict.IORef
import qualified IdeSession.Strict.Map   as StrictMap
import qualified IdeSession.Strict.Maybe as StrictMaybe

-- Our imports
import Conv
import GhcShim

{------------------------------------------------------------------------------
  Package dependencies
------------------------------------------------------------------------------}

pkgDepsFromModSummary :: DynFlags
                      -> GHC.ModSummary
                      -> [PackageKey]
pkgDepsFromModSummary dflags s =
    catMaybes (map (uncurry (findExposedModule dflags)) impMods)
  where
    aux :: Located (ImportDecl RdrName)
        -> (PackageQualifier, GHC.ModuleName)
    aux lIdecl = let idecl = unLoc lIdecl
                 in (ideclPkgQual idecl, unLoc (ideclName idecl))

    impMods :: [(PackageQualifier, GHC.ModuleName)]
    impMods = map aux (GHC.ms_srcimps      s)
           ++ map aux (GHC.ms_textual_imps s)

{------------------------------------------------------------------------------
  Interfacing with Haddock
------------------------------------------------------------------------------}

haddockInterfaceFilePath :: DynFlags
                         -> PackageKey
                         -> Either String FilePath
haddockInterfaceFilePath dflags pkg =
  let cfg = lookupPackage dflags pkg in
  case () of
    () | null (GHC.haddockInterfaces cfg) -> do
      Left $ "No haddock interfaces found for package "
          ++ pretty dflags defaultUserStyle pkg
    () | length (GHC.haddockInterfaces cfg) > 1 -> do
      Left $ "Too many haddock interfaces found for package "
          ++ pretty dflags defaultUserStyle pkg
    _otherwise ->
      Right . head . GHC.haddockInterfaces $ cfg

haddockInterfaceFor :: DynFlags
                    -> Hk.NameCacheAccessor IO
                    -> PackageKey
                    -> IO (Either String LinkEnv)
haddockInterfaceFor dflags cache pkg = do
    case haddockInterfaceFilePath dflags pkg of
      Left err   -> return (Left err)
      Right path -> do
        iface <- try' $ Hk.readInterfaceFile cache path
        return (mkLinkEnv . Hk.ifLinkEnv <$> iface)
  where
    try' :: MonadIO m => IO (Either String a) -> m (Either String a)
    try' act = do
      mr <- liftIO $ Ex.try act
      case mr of
        Left e -> do
          return . Left $ show (e :: SomeException)
        Right (Left e) -> do
          return . Left $ e
        Right (Right r) -> do
          return . Right $ r

{------------------------------------------------------------------------------
  Haddock's LinkEnv maps Names to Modules, but unfortunately those Names
  have different Uniques than the Uniques that ghc uses and hence we cannot
  use them for lookup. We create our own environment instead.
------------------------------------------------------------------------------}

type LinkEnv = Strict (Map (GHC.Module, GHC.OccName)) GHC.Module

mkLinkEnv :: Map GHC.Name GHC.Module -> LinkEnv
mkLinkEnv m = foldr (.) (\x -> x) (map aux (LazyMap.toList m)) StrictMap.empty
  where
    aux :: (GHC.Name, GHC.Module) -> LinkEnv -> LinkEnv
    aux (n, home) = case GHC.nameModule_maybe n of
      Just mod -> StrictMap.insert (mod, GHC.nameOccName n) home
      Nothing  -> \x -> x

homeModuleFor :: DynFlags -> LinkEnv -> GHC.Name -> Strict Maybe ModuleId
homeModuleFor dflags linkEnv name =
  case GHC.nameModule_maybe name of
    Nothing -> do
      StrictMaybe.nothing
    Just mod -> do
      case StrictMap.lookup (mod, GHC.nameOccName name) linkEnv of
        Nothing -> do
          StrictMaybe.nothing
        Just m ->
          StrictMaybe.just (importModuleId dflags m)

{------------------------------------------------------------------------------
  Link environment cache

  We keep separate caches for the link env per package, and the link env per
  set of package dependencies. This is important because the order of the
  dependencies matters; for instance, there is an entry for 'True' in both
  ghc-prim and base, but we want the entry in base so that we report Data.Bool
  as the home module rather than GHC.Types.

  TODO: However, we might be able to be a bit smarter about memory usage here?
------------------------------------------------------------------------------}

linkEnvPerPackageCache :: StrictIORef (Strict (Map PackageKey) (Either String LinkEnv))
{-# NOINLINE linkEnvPerPackageCache #-}
linkEnvPerPackageCache = unsafePerformIO $ newIORef StrictMap.empty

linkEnvForPackage :: DynFlags -> PackageKey -> IO (Either String LinkEnv)
linkEnvForPackage dynFlags pkgId = do
  cache <- readIORef linkEnvPerPackageCache
  case StrictMap.lookup pkgId cache of
    Just linkEnv -> return linkEnv
    Nothing -> do
      linkEnv <- haddockInterfaceFor dynFlags Hk.freshNameCache pkgId
      let cache' = StrictMap.insert pkgId linkEnv cache
      writeIORef linkEnvPerPackageCache cache'
      return linkEnv

linkEnvPerDepsCache :: StrictIORef (Strict (Map [PackageKey]) LinkEnv)
{-# NOINLINE linkEnvPerDepsCache #-}
linkEnvPerDepsCache = unsafePerformIO $ newIORef StrictMap.empty

linkEnvForDeps :: DynFlags -> [PackageKey] -> IO LinkEnv
linkEnvForDeps dynFlags deps = do
  cache <- readIORef linkEnvPerDepsCache
  case StrictMap.lookup deps cache of
    Just linkEnv -> return linkEnv
    Nothing -> do
      linkEnvs <- mapM (linkEnvForPackage dynFlags) deps
      -- See comment above about ordering of dependencies for a justification
      -- of 'unions' (which is biased)
      let linkEnv = StrictMap.unions (rights linkEnvs)
          cache'  = StrictMap.insert deps linkEnv cache
      writeIORef linkEnvPerDepsCache cache'
      return linkEnv