packages feed

ide-backend-server-0.10.0: Run.hs

{-# LANGUAGE CPP, TemplateHaskell, RecordWildCards, ScopedTypeVariables #-}
-- Copyright   : (c) JP Moresmau 2011,
--                   Well-Typed 2012
-- (JP Moresmau's buildwrapper package used as template for GHC API use)
--
-- | Implementation details of the calls to GHC that compute information
-- based on source files, provides progress information while doing so
-- and optionally compiles, links and executes code.
--
-- Only @IdeSession.GHC.Run@ and @IdeSession.GHC.HsWalk@ should import
-- any modules from the ghc package and the modules should not be reexported
-- anywhere else, with the exception of @IdeSession.GHC.Server@.
module Run
  ( -- * Re-exported GHC API
    Ghc
  , runFromGhc
  , liftIO
  , GhcException
  , ghandle
  , ghandleJust
  , getModuleGraph
  , moduleNameString
  , ms_mod_name, ms_hs_date
    -- * Processing source files (including compilation)
  , compileInGhc
  , collectSrcError
  , DynamicOpts
  , submitStaticOpts
  , optsToDynFlags
  , DynFlags(hooks)
  , defaultDynFlags
  , getSessionDynFlags
  , setSessionDynFlags
  , parseDynamicFlags
  , hsExtensions
  , importList
  , autocompletion
    -- * Executing snippets
  , runInGhc
  , RunCmd(..)
  , RunResult(..)
  , RunBufferMode(..)
  , breakFromSpan
  , printVars
  ) where

#define DEBUG 0

import Prelude hiding (id, mod, span)
import qualified Control.Exception as Ex
import Control.Monad (filterM, liftM, void, when)
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Maybe (MaybeT(..))
import Control.Applicative ((<$>))
import Data.List ((\\))
import Data.Maybe (catMaybes, listToMaybe)
import Data.Text (Text)
import Data.Array (Array)
import qualified Data.Array as Array
import qualified Data.Text as Text
import System.FilePath.Find (find, always, extension)
import System.Process
import System.IO.Unsafe (unsafePerformIO)

#if defined(GHC_761)
-- http://hackage.haskell.org/trac/ghc/ticket/7548
-- is not fixed in this version. Compilation should fail,
-- because the problem is not minor and not easy to spot otherwise.
#error "GHC 7.6.1 is broken (#7548) and not supported"
#endif

import Bag (bagToList)
import DynFlags (defaultDynFlags)
import Exception (ghandle)
import ErrUtils (ErrMsg)
import FastString (unpackFS, mkFastString)
import GHC hiding (
    ModuleName
  , RunResult(..)
  , BreakInfo(..)
  , getBreak
  )
import GhcMonad (liftIO)
import OccName (occEnvElts)
import Outputable (PprStyle, mkUserStyle)
import RdrName (GlobalRdrEnv, GlobalRdrElt, gre_name)
import RnNames (rnImports)
import SrcLoc (mkRealSrcSpan, mkRealSrcLoc)
import TcRnMonad (initTc)
import TcRnTypes (RnM)
import RtClosureInspect (Term)
import qualified HscTypes
import qualified GHC           as GHC
import qualified Config        as GHC
import qualified Outputable    as GHC
import qualified ByteCodeInstr as GHC
import qualified Id            as GHC

#if __GLASGOW_HASKELL__ >= 706
import ErrUtils   ( MsgDoc )
#else
import ErrUtils   ( Message )
#endif

import IdeSession.GHC.API
import IdeSession.Types.Public (RunBufferMode(..), Targets(..))
import IdeSession.Types.Private
import qualified IdeSession.Types.Public as Public
import IdeSession.Util
import IdeSession.Strict.Container
import IdeSession.Strict.IORef
import qualified IdeSession.Strict.List as StrictList
import qualified IdeSession.Strict.Map  as StrictMap

import HsWalk (idInfoForName)
import Haddock
import Debug
import Conv
import FilePathCaching
import Break
import GhcShim

type DynamicOpts = [Located String]

-- | Set static flags at server startup and return dynamic flags.
submitStaticOpts :: [String] -> IO DynamicOpts
submitStaticOpts opts = do
  (dynFlags, _) <- parseStaticFlags (map noLoc opts)
  return dynFlags

-- | Create dynamic flags from their command-line names.
optsToDynFlags :: [String] -> DynamicOpts
optsToDynFlags = map noLoc

getGhcLibdir :: IO FilePath
getGhcLibdir = do
  let ghcbinary = "ghc-" ++ GHC.cProjectVersion
  out <- readProcess ghcbinary ["--print-libdir"] ""
  case lines out of
    [libdir] -> return libdir
    _        -> fail "cannot parse output of ghc --print-libdir"

runFromGhc :: Ghc a -> IO a
runFromGhc a = do
  libdir <- getGhcLibdir
  runGhc (Just libdir) a

-- | Version of handleJust for use in the GHC monad
ghandleJust :: Ex.Exception e => (e -> Maybe b) -> (b -> Ghc a) -> Ghc a -> Ghc a
ghandleJust p handler a = ghandle handler' a
  where
    handler' e = case p e of
                   Nothing -> liftIO $ Ex.throwIO e
                   Just b  -> handler b

-- | Compile a set of targets
--
-- Returns the errors, loaded modules, and mapping from filenames to modules
compileInGhc :: FilePath            -- ^ target directory, only used for 'TargetsExclude'
             -> Bool                -- ^ should we generate code
             -> Targets             -- ^ targets
             -> StrictIORef (Strict [] SourceError) -- ^ the IORef where GHC stores errors
             -> Ghc ( Strict [] SourceError
                    , [ModuleName]
                    , Strict (Map FilePath) ModuleId
                    )
compileInGhc configSourcesDir generateCode mTargets errsRef = do
    -- Reset errors storage.
    liftIO $ writeIORef errsRef StrictList.nil
    -- Compute new GHC flags.
    flags1 <- getSessionDynFlags
    -- Let GHC API print "compiling M ... done." for each module.
    let verbosityNew :: Int
        verbosityNew = 1
        (hscTargetNew, ghcLinkNew)
          | generateCode = (HscInterpreted, LinkInMemory)
          | otherwise    = (HscNothing,     NoLink)
        flagsNeedChange = hscTargetNew /= hscTarget flags1
                          || ghcLinkNew /= ghcLink flags1
                          || verbosityNew /= verbosity flags1
        flags = flags1 {
                         hscTarget = hscTargetNew,
                         ghcLink = ghcLinkNew,
                         verbosity = verbosityNew
                       }
    handleErrors flags $ do
      defaultCleanupHandler flags $ do
        -- Set up the GHC flags.
        when flagsNeedChange $ void $ setSessionDynFlags flags
        setTargets =<< computeTargets
        void $ load LoadAllTargets

    -- Collect info
    errs    <- liftIO $ readIORef errsRef
    loaded  <- getLoaded
    fileMap <- getFileMap
    return ( StrictList.reverse errs
           , loaded
           , StrictMap.fromList $ fileMap
           )
  where
    getLoaded :: Ghc [ModuleName]
    getLoaded = do
      graph <- getModuleGraph
      names <- filterM isLoaded $ map ms_mod_name graph
      return $ map (Text.pack . moduleNameString) names

    getFileMap :: Ghc [(FilePath, ModuleId)]
    getFileMap = do
        dflags <- getSessionDynFlags
        let aux :: ModSummary -> Maybe (FilePath, ModuleId)
            aux summary = do
              hs <- ml_hs_file $ ms_location summary
              return (hs, importModuleId dflags (ms_mod summary))
        graph <- getModuleGraph
        return $ catMaybes $ map aux graph

    computeTargets :: Ghc [Target]
    computeTargets = do
      targetIds <- case mTargets of
        TargetsInclude include -> return (map targetIdFromFile include)
        TargetsExclude exclude -> liftIO $ do
          allPaths <- find always ((`elem` hsExtensions) `liftM` extension)
                                  configSourcesDir
          let paths = allPaths \\ exclude
          return (map targetIdFromFile paths)
      return (map targetWithId targetIds)

    targetWithId :: TargetId -> Target
    targetWithId targetId = Target {
        targetId           = targetId
      , targetAllowObjCode = True
      , targetContents     = Nothing
      }

    targetIdFromFile :: FilePath -> TargetId
    targetIdFromFile path = TargetFile path Nothing

    sourceErrorHandler :: DynFlags -> HscTypes.SourceError -> Ghc ()
    sourceErrorHandler _flags e = liftIO $ do
      debug dVerbosity $ "handleSourceError: " ++ show e
      errs <- readIORef errsRef
      e'   <- fromHscSourceError e
      writeIORef errsRef (force e' StrictList.++ errs)

    -- A workaround for http://hackage.haskell.org/trac/ghc/ticket/7430.
    -- Some errors are reported as exceptions instead.
    ghcExceptionHandler :: DynFlags -> GhcException -> Ghc ()
    ghcExceptionHandler _flags e = liftIO $ do
      let eText   = Text.pack $ show e  -- no SrcSpan as a field in GhcException
          fromEx  = Text.pack $ "<from GhcException>"
          exError = SourceError KindError (TextSpan fromEx) eText
            -- though it may be embedded in string
      debug dVerbosity $ "handleOtherErrors: " ++ Text.unpack eText
      errs <- readIORef errsRef
      writeIORef errsRef (exError `StrictList.cons` errs)

    handleErrors :: DynFlags -> Ghc () -> Ghc ()
    handleErrors flags = ghandle (ghcExceptionHandler flags)
                       . handleSourceError (sourceErrorHandler flags)

importList :: ModSummary -> Ghc (Strict [] Import)
importList summary = do
  dflags  <- getSessionDynFlags

  -- TODO: This is lossy. We might want a more accurate data type.
  let unLIE :: LIE RdrName -> Text
      unLIE (L _ name) = Text.pack $ pretty dflags GHC.defaultUserStyle name

#if __GLASGOW_HASKELL__ >= 710
  let unLocNames :: Located [LIE name] -> [LIE name]
      unLocNames (L _ names) = names
#else
  let unLocNames :: [LIE name] -> [LIE name]
      unLocNames names = names
#endif

#if __GLASGOW_HASKELL__ >= 710
  let mkImportEntities :: Maybe (Bool, Located [LIE RdrName]) -> ImportEntities
#else
  let mkImportEntities :: Maybe (Bool, [LIE RdrName]) -> ImportEntities
#endif
      mkImportEntities Nothing               = ImportAll
      mkImportEntities (Just (True, names))  = ImportHiding (force $ map unLIE (unLocNames names))
      mkImportEntities (Just (False, names)) = ImportOnly   (force $ map unLIE (unLocNames names))

  let goImp :: Located (ImportDecl RdrName) -> Import
      goImp (L _ decl) = Import {
          importModule    = importModuleId' dflags (ideclPkgQual decl) (unLoc (ideclName decl))
        , importPackage   = force $ ((Text.pack . unpackFS) <$> ideclPkgQual decl)
        , importQualified = ideclQualified decl
        , importImplicit  = ideclImplicit decl
        , importAs        = force $ ((Text.pack . moduleNameString) <$> ideclAs decl)
        , importEntities  = mkImportEntities (ideclHiding decl)
        }

  return . force $ map goImp (ms_srcimps summary)
                ++ map goImp (ms_textual_imps summary)

autocompletion :: ModSummary -> Ghc (Strict [] IdInfo)
autocompletion summary = do
  dflags  <- getSessionDynFlags
  session <- getSession

  let pkgDeps = pkgDepsFromModSummary dflags summary
  linkEnv <- liftIO $ linkEnvForDeps dflags pkgDeps

  let eltsToAutocompleteMap :: GlobalRdrElt -> Ghc IdInfo
      eltsToAutocompleteMap elt = do
        let name          = gre_name elt
            currentModule = Nothing -- Must be imported (TH stage restriction)
        (idProp, Just idScope) <- idInfoForName dflags
                                                name
                                                UseSite
                                                (Just elt)
                                                currentModule
                                                (homeModuleFor dflags linkEnv)
        return IdInfo{..}

      autoEnvs :: ModSummary -> IO [GlobalRdrElt]
      autoEnvs ms@ModSummary{ ms_mod
                                 , ms_hsc_src
                                 , ms_srcimps
                                 , ms_textual_imps
                                 } = do
        let go :: RnM (a, GlobalRdrEnv, b, c) -> IO [GlobalRdrElt]
            go op = do
#if GHC_AFTER_710_1
              -- Not sure if this is the same thing that GHC would
              -- pass in, but this is just to initialize the state,
              -- and will be overridden before any uses.
              let srcSpan = mkRealSrcSpan loc loc
                  loc = mkRealSrcLoc (mkFastString (HscTypes.msHsFilePath ms)) 1 1
              ((_warns, _errs), res) <- initTc session ms_hsc_src False ms_mod srcSpan op
#else
              ((_warns, _errs), res) <- initTc session ms_hsc_src False ms_mod op
#endif
              case res of
                Nothing -> do
                  -- TODO: deal with import errors
#if DEBUG
                  appendFile "/tmp/ghc.importerrors" $ show
                                                     . map GHC.showSDoc
                                                     $ ErrUtils.pprErrMsgBag _errs
#endif
                  return []
                Just (_, elts, _, _) ->
                  return . concat $ occEnvElts elts
        env1 <- go $ rnImports ms_srcimps
        env2 <- go $ rnImports ms_textual_imps
        return $ env1 ++ env2

  envs    <- liftIO $ autoEnvs summary
  idIs    <- mapM eltsToAutocompleteMap envs
  return $ force idIs

-- | Run a snippet.
runInGhc :: RunCmd -> Ghc RunResult
runInGhc cmd = do
  flags <- getSessionDynFlags
  defaultCleanupHandler flags . handleErrors $ do
      runRes <- runCmd cmd
      case runRes of
        GHC.RunOk _ ->
          return RunOk
        GHC.RunException ex ->
          return $ RunProgException (showExWithClass ex)
        GHC.RunBreak _tid names mBreakInfo -> do
          Just info <- importBreakInfo mBreakInfo names
          return $ RunBreak info
  where
    handleError :: Show a => a -> Ghc RunResult
    handleError = return . RunGhcException . show

    -- "such-and-such not in scope" is reported as a source error
    -- not sure when GhcExceptions are thrown (if at all)
    handleErrors :: Ghc RunResult -> Ghc RunResult
    handleErrors = handleSourceError handleError
                 . ghandle (handleError :: GhcException -> Ghc RunResult)

-- | Auxiliary to 'runInGhc'
runCmd :: RunCmd -> Ghc GHC.RunResult
runCmd (RunStmt {..}) = do
    setContext $ [ IIDecl $ simpleImportDecl $ mkModuleName runCmdModule
                 , IIDecl $ simpleImportDecl $ mkModuleName "IdeBackendRTS"
                 ]
    runStmt expr RunToCompletion
  where
    expr :: String
    expr = fqn "run "
        ++ "(" ++ fqBMode runCmdStdout ++ ")"
        ++ "(" ++ fqBMode runCmdStderr ++ ")"
        ++ "(" ++ runCmdModule ++ "." ++ runCmdFunction ++ ")"

    fqn :: String -> String
    fqn = (++) "IdeBackendRTS."

    fqBMode :: RunBufferMode -> String
    fqBMode RunNoBuffering =
      fqn "RunNoBuffering"
    fqBMode (RunLineBuffering t) =
      fqn "RunLineBuffering (" ++ fqMInt t ++ ")"
    fqBMode (RunBlockBuffering sz t) =
      fqn "RunBlockBuffering (" ++ fqMInt sz ++ ") (" ++ fqMInt t ++ ")"

    fqMInt :: Maybe Int -> String
    fqMInt Nothing  = fqn "Nothing"
    fqMInt (Just n) = fqn "Just " ++ show n
runCmd Resume =
    resume (const True) RunToCompletion

{------------------------------------------------------------------------------
  Dealing with breakpoints
------------------------------------------------------------------------------}

-- | We store a print context whenever we hit a breakpoint so that we have
-- sufficient context for subsequent requests for pretty-printing types
printContext :: StrictIORef PprStyle
{-# NOINLINE printContext #-}
printContext = unsafePerformIO $
  newIORef (mkUserStyle alwaysQualify GHC.AllTheWay)

getPrintContext :: Ghc PprStyle
getPrintContext = liftIO $ readIORef printContext

setPrintContext :: PprStyle -> Ghc ()
setPrintContext ctxt = liftIO $ writeIORef printContext ctxt

breakFromSpan :: ModuleName        -- ^ Module containing the breakpoint
              -> Public.SourceSpan -- ^ Location of the breakpoint
              -> Bool              -- ^ New value for the breakpoint
              -> Ghc (Maybe Bool)    -- ^ Old valeu of the breakpoint (if valid)
breakFromSpan modName span newValue = runMaybeT $ do
    modInfo <- MaybeT $ getModuleInfo mod
    let ModBreaks{..} = modInfoModBreaks modInfo

    breakIndex <- MaybeT $ return $ findBreakIndex modBreaks_locs
    oldValue   <- MaybeT $ getBreak modBreaks_flags breakIndex

    lift $ setBreak modBreaks_flags breakIndex newValue
    return oldValue
  where
    findBreakIndex :: Array BreakIndex SrcSpan -> Maybe BreakIndex
    findBreakIndex breaks = listToMaybe
                          . catMaybes
                          . map matchesSpan
                          . Array.assocs
                          $ breaks

    matchesSpan :: (BreakIndex, SrcSpan) -> Maybe BreakIndex
    matchesSpan (_, UnhelpfulSpan _) = Nothing
    matchesSpan (i, RealSrcSpan span') =
      if  srcSpanStartLine span' == Public.spanFromLine   span
       && srcSpanStartCol  span' == Public.spanFromColumn span
       && srcSpanEndLine   span' == Public.spanToLine     span
       && srcSpanEndCol    span' == Public.spanToColumn   span
      then Just i
      else Nothing

    mod :: Module
    mod = mkModule mainPackageKey (mkModuleName . Text.unpack $ modName)

importBreakInfo :: Maybe GHC.BreakInfo
                -> [Name]
                -> Ghc (Maybe BreakInfo)
importBreakInfo (Just GHC.BreakInfo{..}) names = runMaybeT $ do
    modInfo          <- MaybeT $ getModuleInfo breakInfo_module
    printUnqualified <- MaybeT $ mkPrintUnqualifiedForModule modInfo

    let ModBreaks{..} = modInfoModBreaks modInfo
        srcSpan       = modBreaks_locs Array.! breakInfo_number
        pprStyle      = mkUserStyle printUnqualified GHC.AllTheWay

    lift $ do
      setPrintContext pprStyle

      localVars           <- mkTerms >>= mapM (exportVar pprStyle)
      ProperSpan srcSpan' <- liftIO $ extractEitherSpan srcSpan
      prettyResTy         <- prettyM pprStyle breakInfo_resty

      return BreakInfo {
          breakInfoModule      = mod
        , breakInfoSpan        = srcSpan'
        , breakInfoResultType  = Text.pack prettyResTy
        , breakInfoVariableEnv = localVars
        }
  where
    mkTerms :: Ghc [(Id, Term)]
    mkTerms = resolveNames names >>= evaluateIds False False

    -- we ignore the package (because it's always the home package)
    mod = Text.pack . moduleNameString . GHC.moduleName $ breakInfo_module
importBreakInfo Nothing _names = error "importBreakInfo: missing case"

printVars :: String -> Bool -> Bool -> Ghc Public.VariableEnv
printVars vars bind forceEval =
  getPrintContext            >>= \unqual ->
  parseNames vars            >>=
  resolveNames               >>=
  evaluateIds bind forceEval >>=
  mapM (exportVar unqual)

exportVar :: PprStyle -> (Id, Term) -> Ghc (Public.Name, Public.Type, Public.Value)
exportVar pprStyle (var, term) = do
    nameStr <- prettyM     pprStyle             (GHC.idName var)
    typeStr <- prettyTypeM pprStyle showForalls (GHC.idType var)
    termStr <- prettyM     pprStyle             term
    return (Text.pack nameStr, Text.pack typeStr, Text.pack termStr)
  where
    showForalls  = False

-----------------------
-- Source error conversion and collection
--

collectSrcError :: StrictIORef (Strict [] SourceError)
                -> (String -> IO ())
                -> (String -> IO ())
                -> DynFlags
                -> Severity -> SrcSpan -> PprStyle -> MsgDoc -> IO ()
collectSrcError errsRef handlerOutput handlerRemaining flags
                severity srcspan style msg = do
  -- Prepare debug prints.
  debug dVerbosity
   $  "Severity: "   ++ show severity
   ++ "  SrcSpan: "  ++ show srcspan
-- ++ "  PprStyle: " ++ show style
   ++ "  MsgDoc: "
   ++ showSDocForUser flags (queryQual style) msg
   ++ "\n"
  -- Actually collect errors.
  collectSrcError'
    errsRef handlerOutput handlerRemaining flags severity srcspan style msg

collectSrcError' :: StrictIORef (Strict [] SourceError)
                 -> (String -> IO ())
                 -> (String -> IO ())
                 -> DynFlags
                 -> Severity -> SrcSpan -> PprStyle -> MsgDoc -> IO ()
collectSrcError' errsRef _ _ flags severity srcspan style msg
  | Just errKind <- case severity of
                      SevWarning -> Just KindWarning
                      SevError   -> Just KindError
                      SevFatal   -> Just KindError
                      _          -> Nothing
  = do let msgstr = showSDocForUser flags (queryQual style) msg
       sp <- extractEitherSpan srcspan
       modifyIORef errsRef (StrictList.cons $ SourceError errKind sp (Text.pack msgstr))

collectSrcError' _errsRef handlerOutput _ flags SevOutput _srcspan style msg
  = let msgstr = showSDocForUser flags (queryQual style) msg
     in handlerOutput msgstr

collectSrcError' _errsRef _ handlerRemaining flags _severity _srcspan style msg
  = let msgstr = showSDocForUser flags (queryQual style) msg
     in handlerRemaining msgstr

-- TODO: perhaps make a honest SrcError from the first span from the first
-- error message and put the rest into the message string? That probably
-- entains string-search-and-replace inside the message string not to
-- duplicate the first span, because the types involved are abstract.
-- And even then, all the file paths but the first would be out of
-- our control (and so, e.g., not relative to the project root).
-- But at least the IDE could point somewhere in the code.
-- | Convert GHC's SourceError type into ours.
fromHscSourceError :: forall m. MonadFilePathCaching m => HscTypes.SourceError -> m [SourceError]
fromHscSourceError = mapM aux . bagToList . HscTypes.srcErrorMessages
  where
    aux :: ErrMsg -> m SourceError
    aux e = do
      let err   = Text.pack (show e)
          noloc = Text.pack "<no location info>"

      case sourceErrorSpan e of
        Just real -> do
          xSpan <- extractEitherSpan real
          return $ SourceError KindError xSpan err
        Nothing ->
          return $ SourceError KindError (TextSpan noloc) err

-----------------------
-- GHC version compat
--

#if __GLASGOW_HASKELL__ < 706
type MsgDoc = Message
#endif

showSDocForUser :: DynFlags -> PrintUnqualified -> MsgDoc -> String
#if __GLASGOW_HASKELL__ >= 706
showSDocForUser  flags uqual msg = GHC.showSDocForUser flags uqual msg
#else
showSDocForUser _flags uqual msg = GHC.showSDocForUser       uqual msg
#endif

showSDocDebug :: DynFlags -> MsgDoc -> String
#if __GLASGOW_HASKELL__ >= 706
showSDocDebug  flags msg = GHC.showSDocDebug flags msg
#else
showSDocDebug _flags msg = GHC.showSDocDebug        msg
#endif

queryQual :: PprStyle -> PrintUnqualified
#if __GLASGOW_HASKELL__ >= 710
queryQual = GHC.queryQual
#else
queryQual style = (GHC.qualName style, GHC.qualModule style)
#endif


-----------------------
-- Debug
--

_debugPpContext :: DynFlags -> String -> Ghc ()
_debugPpContext flags msg = do
  context <- getContext
  liftIO $ debug dVerbosity
    $ msg ++ ": " ++ showSDocDebug flags (GHC.ppr context)