1-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2-- SPDX-License-Identifier: Apache-2.0
4{-# LANGUAGE OverloadedStrings #-}
5{-# LANGUAGE RankNTypes #-}
8-- | Based on https://ghc.haskell.org/trac/ghc/wiki/Commentary/Compiler/API.
9-- Given a list of paths to find libraries, and a file to compile, produce a list of 'CoreModule' values.
10module Development.IDE.Core.Compile
13 , LoadPackageResult(..)
23import Development.IDE.GHC.Warnings
24import Development.IDE.GHC.CPP
25import Development.IDE.Types.Diagnostics
26import qualified Development.IDE.Import.FindImports as FindImports
27import Development.IDE.GHC.Error
28import Development.IDE.Spans.Calculate
29import Development.IDE.GHC.Util
30import Development.IDE.GHC.Compat
31import Development.IDE.Types.Options
32import Development.IDE.Types.Location
34import GHC hiding (parseModule, typecheckModule)
35import qualified Parser
42import GhcPlugins as GHC hiding (fst3, (<>))
43import qualified HeaderInfo as Hdr
46import StringBuffer as SB
48import qualified GHC.LanguageExtensions as LangExt
51import Control.Exception as E
53import qualified Control.Monad.Trans.Except as Ex
57import Data.Tuple.Extra
58import qualified Data.Map.Strict as Map
59import Development.IDE.Spans.Type
60import GHC.Generics (Generic)
62import System.Directory
66-- | 'CoreModule' together with some additional information required for the
67-- conversion to DAML-LF.
68data GhcModule = GhcModule
69 { gmPath :: Maybe FilePath
70 , gmCore :: CoreModule
72 deriving (Generic, Show)
74instance NFData GhcModule
76-- | Contains the typechecked module and the OrigNameCache entry for
78data TcModuleResult = TcModuleResult
79 { tmrModule :: TypecheckedModule
80 , tmrModInfo :: HomeModInfo
81 , tmrOccEnvName :: OccEnv Name
84-- | Contains the result of loading an interface. In particular the delta to the name cache.
85data LoadPackageResult = LoadPackageResult
86 { lprInstalledUnitId :: InstalledUnitId
87 , lprModuleEnv :: ModuleEnv (OccEnv Name)
88 , lprEps :: ExternalPackageState
91-- | Get source span info, used for e.g. AtPoint and Goto Definition.
96 -> [(Located ModuleName, Maybe NormalizedFilePath)]
99getSrcSpanInfos opt mod env imports tc =
100 runGhcSession opt (Just mod) env
101 . getSpanInfo imports
105-- | Given a string buffer, return a pre-processed @ParsedModule@.
111 -> IO ([FileDiagnostic], Maybe ParsedModule)
112parseModule opt@IdeOptions{..} packageState file =
113 fmap (either (, Nothing) (second Just)) . Ex.runExceptT .
114 -- We need packages since imports fail to resolve otherwise.
115 runGhcSessionExcept opt Nothing packageState . parseFileContents optPreprocessor file
118 IdeOptions -> HscEnv -> InstalledUnitId -> IO (Either [FileDiagnostic] [InstalledUnitId])
119computePackageDeps opts packageState iuid =
121 runGhcSessionExcept opts Nothing packageState $
123 dflags <- hsc_dflags <$> getSession
124 liftIO $ depends <$> getPackage dflags iuid
126getPackage :: DynFlags -> InstalledUnitId -> IO PackageConfig
128 case lookupInstalledPackage dflags p of
129 Nothing -> E.throwIO $ CmdLineError (missingPackageMsg p)
130 Just pkg -> return pkg
132 missingPackageMsg p = showSDoc dflags $ text "unknown package:" <+> ppr p
134-- | Typecheck a single module using the supplied dependencies and packages.
140 -> IO ([FileDiagnostic], Maybe TcModuleResult)
141typecheckModule opt packageState deps pm =
142 fmap (either (, Nothing) (second Just)) $ Ex.runExceptT $
143 runGhcSessionExcept opt (Just pm) packageState $
146 (warnings, tcm) <- withWarnings $ \tweak ->
147 GHC.typecheckModule pm{pm_mod_summary = tweak $ pm_mod_summary pm}
148 tcm2 <- mkTcModuleResult (WriteInterface $ optWriteIface opt) tcm
149 return (warnings, tcm2)
151-- | Load a pkg and populate the name cache and external package state.
156 -> IO (Either [FileDiagnostic] LoadPackageResult)
157loadPackage opt packageState p =
159 runGhcSessionExcept opt Nothing packageState $
162 -- this populates the namecache and external package state
163 session <- getSession
164 modEnv <- nsNames <$> liftIO (readIORef $ hsc_NC session)
165 eps <- liftIO (readIORef $ hsc_EPS session)
166 pure $ LoadPackageResult p modEnv eps
168-- | Compile a single type-checked module to a 'CoreModule' value, or
176 -> IO ([FileDiagnostic], Maybe GhcModule)
177compileModule opt mod packageState deps tmr =
178 fmap (either (, Nothing) (second Just)) $ Ex.runExceptT $
179 runGhcSessionExcept opt (Just mod) packageState $
181 setupEnv (deps ++ [tmr])
183 let tm = tmrModule tmr
184 session <- getSession
185 (warnings,desugar) <- withWarnings $ \tweak -> do
186 let pm = tm_parsed_module tm
187 let pm' = pm{pm_mod_summary = tweak $ pm_mod_summary pm}
188 let tm' = tm{tm_parsed_module = pm'}
189 GHC.dm_core_module <$> GHC.desugarModule tm'
191 -- give variables unique OccNames
192 (tidy, details) <- liftIO $ tidyProgram session desugar
194 let path = ml_hs_file $ ms_location $ pm_mod_summary $ tm_parsed_module tm
195 let core = CoreModule
199 (mg_safe_haskell desugar)
201 return (warnings, GhcModule path core)
203-- | Evaluate a GHC session using a new environment constructed with
204-- the supplied options.
207 -> Maybe ParsedModule
209 -> Ex.ExceptT e Ghc a
211runGhcSessionExcept opts mbMod pkg m =
212 Ex.ExceptT $ runGhcSession opts mbMod pkg $ Ex.runExceptT m
215getGhcDynFlags :: IdeOptions -> ParsedModule -> HscEnv -> IO DynFlags
216getGhcDynFlags opts mod pkg = runGhcSession opts (Just mod) pkg getSessionDynFlags
218-- | Evaluate a GHC session using a new environment constructed with
219-- the supplied options.
222 -> Maybe ParsedModule
226runGhcSession IdeOptions{..} modu env act = runGhcEnv env $ do
227 modifyDynFlags $ \x -> x
228 {importPaths = nubOrd $ maybeToList (moduleImportPaths =<< modu) ++ importPaths x}
232moduleImportPaths :: GHC.ParsedModule -> Maybe FilePath
234 | rootModDir == "." = Just rootPathDir
236 -- TODO (MK) stripSuffix (normalise rootModDir) (normalise rootPathDir)
237 -- would be a better choice but at the moment we do not consistently
238 -- normalize file paths in the Shake graph so we can end up with the
239 -- same module being represented twice in the Shake graph.
240 Just $ dropTrailingPathSeparator $ dropEnd (length rootModDir) rootPathDir
242 ms = GHC.pm_mod_summary pm
243 file = GHC.ms_hspp_file ms
245 rootPathDir = takeDirectory file
246 rootModDir = takeDirectory . moduleNameSlashes . GHC.moduleName $ mod'
249newtype WriteInterface = WriteInterface Bool
256mkTcModuleResult (WriteInterface writeIface) tcm = do
257 session <- getSession
258 nc <- liftIO $ readIORef (hsc_NC session)
259 (iface,_) <- liftIO $ mkIfaceTc session Nothing Sf_None details tcGblEnv
260 liftIO $ when writeIface $ do
261 let path = ".interfaces" </> file tcm
262 createDirectoryIfMissing True (takeDirectory path)
263 writeIfaceFile (hsc_dflags session) (replaceExtension path ".hi") iface
264 -- For now, we write .hie files whenever we write .hi files which roughly corresponds to
265 -- when we are building a package. It should be easily decoupable if that turns out to be
267 hieFile <- runHsc session $ mkHieFile (tcModSummary tcm) tcGblEnv (fromJust $ renamedSource tcm)
268 writeHieFile (replaceExtension path ".hie") hieFile
269 let mod_info = HomeModInfo iface details Nothing
271 case lookupModuleEnv origNc (tcmModule tcm) of
273 Just occ -> return $ TcModuleResult tcm mod_info occ
275 file = ms_hspp_file . tcModSummary
276 tcmModule = ms_mod . tcModSummary
277 (tcGblEnv, details) = tm_internals_ tcm
278 err = "Internal error : module not found in NameCache :" <>
279 moduleNameString (moduleName $ tcmModule tcm)
281tcModSummary :: TypecheckedModule -> ModSummary
282tcModSummary = pm_mod_summary . tm_parsed_module
284-- | Setup the environment that GHC needs according to our
285-- best understanding (!)
286setupEnv :: GhcMonad m => [TcModuleResult] -> m ()
288 session <- getSession
290 let mss = map (pm_mod_summary . tm_parsed_module . tmrModule) tms
292 -- set the target and module graph in the session
293 let graph = mkModuleGraph mss
294 setSession session { hsc_mod_graph = graph }
296 -- Make modules available for others that import them,
297 -- by putting them in the finder cache.
298 let ims = map (InstalledModule (thisInstalledUnitId $ hsc_dflags session) . moduleName . ms_mod) mss
299 ifrs = zipWith (\ms -> InstalledFound (ms_location ms)) mss ims
300 liftIO $ modifyIORef (hsc_FC session) $ \fc ->
301 foldl' (\fc (im, ifr) -> GHC.extendInstalledModuleEnv fc im ifr) fc
304 -- load dependent modules, which must be in topological order.
305 mapM_ loadModuleHome tms
308-- | Load a module, quickly. Input doesn't need to be desugared.
309-- A module must be loaded before dependent modules can be typechecked.
310-- This variant of loadModuleHome will *never* cause recompilation, it just
311-- modifies the session.
316loadModuleHome tmr = modifySession $ \e ->
317 e { hsc_HPT = addToHpt (hsc_HPT e) mod mod_info }
319 ms = pm_mod_summary . tm_parsed_module . tmrModule $ tmr
320 mod_info = tmrModInfo tmr
323-- | Produce a module summary from a StringBuffer.
324getModSummaryFromBuffer
330 -> Ex.ExceptT [FileDiagnostic] m ModSummary
331getModSummaryFromBuffer fp contents dflags parsed = do
332 (modName, imports) <- FindImports.getImportsParsed dflags parsed
334 let modLoc = ModLocation
335 { ml_hs_file = Just fp
336 , ml_hi_file = replaceExtension fp "hi"
337 , ml_obj_file = replaceExtension fp "o"
339 , ml_hie_file = replaceExtension fp "hie"
341 -- This does not consider the dflags configuration
342 -- (-osuf and -hisuf, object and hi dir.s).
343 -- However, we anyway don't want to generate them.
345 InstalledUnitId unitId = thisInstalledUnitId dflags
347 { ms_mod = mkModule (fsToUnitId unitId) modName
348 , ms_location = modLoc
349 , ms_hs_date = error "Rules should not depend on ms_hs_date"
350 -- ^ When we are working with a virtual file we do not have a file date.
351 -- To avoid silent issues where something is not processed because the date
352 -- has not changed, we make sure that things blow up if they depend on the
354 , ms_textual_imps = imports
356 , ms_hspp_opts = dflags
357 , ms_hspp_buf = Just contents
360 , ms_hsc_src = HsSrcFile
361 , ms_obj_date = Nothing
362 , ms_iface_date = Nothing
364 , ms_hie_date = Nothing
366 , ms_srcimps = [] -- source imports are not allowed
367 , ms_parsed_mod = Nothing
370-- | Given a buffer, flags, file path and module summary, produce a
371-- parsed module (or errors) and any parse warnings.
374 => (GHC.ParsedSource -> ([(GHC.SrcSpan, String)], GHC.ParsedSource))
375 -> FilePath -- ^ the filename (for source locations)
376 -> SB.StringBuffer -- ^ Haskell module source text (full Unicode is supported)
377 -> Ex.ExceptT [FileDiagnostic] m ([FileDiagnostic], ParsedModule)
378parseFileContents preprocessor filename contents = do
379 let loc = mkRealSrcLoc (mkFastString filename) 1 1
380 dflags <- parsePragmasIntoDynFlags filename contents
382 (contents, dflags) <-
383 if not $ xopt LangExt.Cpp dflags then
384 return (contents, dflags)
386 contents <- liftIO $ withTempDir $ \dir -> do
387 let inp = dir </> takeFileName filename
388 let out = dir </> takeFileName filename <.> "out"
389 let f x = if SB.atEnd x then Nothing else Just $ SB.nextChar x
390 liftIO $ writeFileUTF8 inp (unfoldr f contents)
391 doCpp dflags True inp out
392 liftIO $ SB.hGetStringBuffer out
393 dflags <- parsePragmasIntoDynFlags filename contents
394 return (contents, dflags)
396 case unP Parser.parseModule (mkPState dflags contents loc) of
397 PFailed _ locErr msgErr ->
398 Ex.throwE $ diagFromErrMsg dflags $ mkPlainErrMsg dflags locErr msgErr
399 POk pst rdr_module ->
400 let hpm_annotations =
401 (Map.fromListWith (++) $ annotations pst,
402 Map.fromList ((noSrcSpan,comment_q pst)
403 :annotations_comments pst))
404 (warns, errs) = getMessages pst dflags
407 -- Just because we got a `POk`, it doesn't mean there
408 -- weren't errors! To clarify, the GHC parser
409 -- distinguishes between fatal and non-fatal
410 -- errors. Non-fatal errors are the sort that don't
411 -- prevent parsing from continuing (that is, a parse
412 -- tree can still be produced despite the error so that
413 -- further errors/warnings can be collected). Fatal
414 -- errors are those from which a parse tree just can't
417 Ex.throwE $ diagFromErrMsgs dflags $ snd $ getMessages pst dflags
419 -- Ok, we got here. It's safe to continue.
420 let (errs, parsed) = preprocessor rdr_module
421 unless (null errs) $ Ex.throwE $ diagFromStrings errs
422 ms <- getModSummaryFromBuffer filename contents dflags parsed
426 , pm_parsed_source = parsed
427 , pm_extra_src_files=[] -- src imports not allowed
428 , pm_annotations = hpm_annotations
430 warnings = diagFromErrMsgs dflags warns
434-- | This reads the pragma information directly from the provided buffer.
435parsePragmasIntoDynFlags
439 -> Ex.ExceptT [FileDiagnostic] m DynFlags
440parsePragmasIntoDynFlags fp contents = catchSrcErrors $ do
441 dflags0 <- getSessionDynFlags
442 let opts = Hdr.getOptions dflags0 contents fp
443 (dflags, _, _) <- parseDynamicFilePragma dflags0 opts
446-- | Run something in a Ghc monad and catch the errors (SourceErrors and
447-- compiler-internal exceptions like Panic or InstallationError).
448catchSrcErrors :: GhcMonad m => m a -> Ex.ExceptT [FileDiagnostic] m a
449catchSrcErrors ghcM = do
450 dflags <- getDynFlags
452 handleGhcException (ghcExceptionToDiagnostics dflags) $
453 handleSourceError (sourceErrorToDiagnostics dflags) $
456 ghcExceptionToDiagnostics dflags = return . Left . diagFromGhcException dflags
457 sourceErrorToDiagnostics dflags = return . Left . diagFromErrMsgs dflags . srcErrorMessages
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list