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 #-}
7-- | Based on https://ghc.haskell.org/trac/ghc/wiki/Commentary/Compiler/API.
8-- Given a list of paths to find libraries, and a file to compile, produce a list of 'CoreModule' values.
9module Development.IDE.Functions.Compile
14 , LoadPackageResult(..)
22 , generatePackageState
25import Development.IDE.Functions.Warnings
26import Development.IDE.Types.Diagnostics
27import qualified Development.IDE.Functions.FindImports as FindImports
28import Development.IDE.Functions.GHCError
29import Development.IDE.Functions.SpanInfo
30import Development.IDE.UtilGHC
32import GHC hiding (parseModule, typecheckModule)
33import qualified Parser
40import GhcPlugins as GHC hiding (PackageState, fst3, (<>))
41import qualified HeaderInfo as Hdr
44import StringBuffer as SB
50import Control.Exception as E
52import qualified Control.Monad.Trans.Except as Ex
56import Data.Tuple.Extra
57import qualified Data.Map.Strict as Map
59import Development.IDE.Types.SpanInfo
60import GHC.Generics (Generic)
63-- TODO (MK) Move to a separate Options module
64data CompileOpts = CompileOpts
65 { optPreprocessor :: GHC.ParsedSource -> ([(GHC.SrcSpan, String)], GHC.ParsedSource)
66 , optRunGhcSession :: forall a. Maybe ParsedModule -> PackageState -> Ghc a -> IO a
67 -- ^ Setup a GHC session using a given package state. If a `ParsedModule` is supplied,
68 -- the import path should be setup for that module.
69 , optWriteIface :: Bool
71 , optMbPackageName :: Maybe String
73 , optPackageDbs :: [FilePath]
74 , optHideAllPkgs :: Bool
75 , optPackageImports :: [(String, ModRenaming)]
78 , optShakeProfiling :: Maybe FilePath
81-- | 'CoreModule' together with some additional information required for the
82-- conversion to DAML-LF.
83data GhcModule = GhcModule
84 { gmPath :: Maybe FilePath
85 , gmCore :: CoreModule
87 deriving (Generic, Show)
89instance NFData GhcModule
91-- | Contains the typechecked module and the OrigNameCache entry for
93data TcModuleResult = TcModuleResult
94 { tmrModule :: TypecheckedModule
95 , tmrModInfo :: HomeModInfo
96 , tmrOccEnvName :: OccEnv Name
99-- | Contains the result of loading an interface. In particular the delta to the name cache.
100data LoadPackageResult = LoadPackageResult
101 { lprInstalledUnitId :: InstalledUnitId
102 , lprModuleEnv :: ModuleEnv (OccEnv Name)
103 , lprEps :: ExternalPackageState
106-- | Get source span info, used for e.g. AtPoint and Goto Definition.
111 -> [(Located ModuleName, Maybe FilePath)]
114getSrcSpanInfos opt mod packageState imports tc =
115 runGhcSession opt (Just mod) packageState
116 . getSpanInfo imports
120-- | Given a string buffer, return a pre-processed @ParsedModule@.
125 -> (UTCTime, SB.StringBuffer)
126 -> IO ([FileDiagnostic], Maybe ParsedModule)
127parseModule opt@CompileOpts{..} packageState file =
128 fmap (either (, Nothing) (second Just)) . Ex.runExceptT .
129 -- We need packages since imports fail to resolve otherwise.
130 runGhcSessionExcept opt Nothing packageState . parseFileContents optPreprocessor file
133 CompileOpts -> PackageState -> InstalledUnitId -> IO (Either [FileDiagnostic] [InstalledUnitId])
134computePackageDeps opts packageState iuid =
136 runGhcSessionExcept opts Nothing packageState $
138 dflags <- hsc_dflags <$> getSession
139 liftIO $ depends <$> getPackage dflags iuid
141getPackage :: DynFlags -> InstalledUnitId -> IO PackageConfig
143 case lookupInstalledPackage dflags p of
144 Nothing -> E.throwIO $ CmdLineError (missingPackageMsg p)
145 Just pkg -> return pkg
147 missingPackageMsg p = showSDoc dflags $ text "unknown package:" <+> ppr p
149-- | Typecheck a single module using the supplied dependencies and packages.
156 -> [LoadPackageResult]
158 -> IO ([FileDiagnostic], Maybe TcModuleResult)
159typecheckModule opt mod packageState uniqSupply deps pkgs pm =
160 fmap (either (, Nothing) (second Just)) $ Ex.runExceptT $
161 runGhcSessionExcept opt (Just mod) packageState $
163 setupEnv uniqSupply deps pkgs
164 (warnings, tcm) <- withWarnings "Typechecker" $ \tweak ->
165 GHC.typecheckModule pm{pm_mod_summary = tweak $ pm_mod_summary pm}
166 tcm2 <- mkTcModuleResult (WriteInterface $ optWriteIface opt) tcm
167 return (warnings, tcm2)
169-- | Load a pkg and populate the name cache and external package state.
174 -> [LoadPackageResult]
176 -> IO (Either [FileDiagnostic] LoadPackageResult)
177loadPackage opt packageState us lps p =
179 runGhcSessionExcept opt Nothing packageState $
182 dflags <- hsc_dflags <$> getSession
183 exposedMods <- liftIO $ exposedModules <$> getPackage dflags p
185 [ Module (DefiniteUnitId (DefUnitId p)) mod
186 | (mod, _mbParent) <- exposedMods
188 forM_ mods $ \mod -> GHC.getModuleInfo mod
189 -- this populates the namecache and external package state
190 session <- getSession
191 modEnv <- nsNames <$> liftIO (readIORef $ hsc_NC session)
192 eps <- liftIO (readIORef $ hsc_EPS session)
193 pure $ LoadPackageResult p modEnv eps
195-- | Compile a single type-checked module to a 'CoreModule' value, or
203 -> [LoadPackageResult]
205 -> IO ([FileDiagnostic], Maybe GhcModule)
206compileModule opt mod packageState uniqSupply deps pkgs tmr =
207 fmap (either (, Nothing) (second Just)) $ Ex.runExceptT $
208 runGhcSessionExcept opt (Just mod) packageState $
210 setupEnv uniqSupply (deps ++ [tmr]) pkgs
212 let tm = tmrModule tmr
213 session <- getSession
214 (warnings,desugar) <- withWarnings "Desugarer" $ \tweak -> do
215 let pm = tm_parsed_module tm
216 let pm' = pm{pm_mod_summary = tweak $ pm_mod_summary pm}
217 let tm' = tm{tm_parsed_module = pm'}
218 removeTypeableInfo . GHC.dm_core_module <$> GHC.desugarModule tm'
220 -- give variables unique OccNames
221 (tidy, details) <- liftIO $ tidyProgram session desugar
223 let path = ml_hs_file $ ms_location $ pm_mod_summary $ tm_parsed_module tm
224 let core = CoreModule
228 (mg_safe_haskell desugar)
230 return (warnings, GhcModule path core)
232-- | Evaluate a GHC session using a new environment constructed with
233-- the supplied options.
236 -> Maybe ParsedModule
238 -> Ex.ExceptT e Ghc a
240runGhcSessionExcept opts mbMod pkg m =
241 Ex.ExceptT $ runGhcSession opts mbMod pkg $ Ex.runExceptT m
244getGhcDynFlags :: CompileOpts -> ParsedModule -> PackageState -> IO DynFlags
245getGhcDynFlags opts mod pkg = runGhcSession opts (Just mod) pkg getSessionDynFlags
247-- | Evaluate a GHC session using a new environment constructed with
248-- the supplied options.
251 -> Maybe ParsedModule
255runGhcSession CompileOpts{..} = optRunGhcSession
257-- When we make a fresh GHC environment, the OrigNameCache comes already partially
258-- populated. So to be safe, we simply extend this one.
259mkNameCache :: GhcMonad m => UniqSupply -> [TcModuleResult] -> [LoadPackageResult] -> m NameCache
260mkNameCache uniqSupply tms pkgs = do
261 session <- getSession
262 onc <- nsNames <$> liftIO (readIORef $ hsc_NC session)
264 { nsUniqs = uniqSupply
265 , nsNames = extendOrigNameCache' onc tms pkgs
268-- | Extend the name cache with the names from the typechecked home modules and the loaded packages.
269-- If we have two environments containing the same module we take the later one. We do this because
270-- the name cache comes prepopulated with modules from daml-prim and we overwrite those with our own
272extendOrigNameCache' :: OrigNameCache -> [TcModuleResult] -> [LoadPackageResult] -> OrigNameCache
273extendOrigNameCache' onc tms pkgs = foldl (plusModuleEnv_C (\_x y -> y)) onc modEnvs
277 [(ms_mod $ tcModSummary $ tmrModule tm, tmrOccEnvName tm) | tm <- tms] :
278 [lprModuleEnv lm | lm <- pkgs]
280newtype WriteInterface = WriteInterface Bool
287mkTcModuleResult (WriteInterface writeIface) tcm = do
288 session <- getSession
289 nc <- liftIO $ readIORef (hsc_NC session)
290 (iface,_) <- liftIO $ mkIfaceTc session Nothing Sf_None details tcGblEnv
293 writeIfaceFile (hsc_dflags session) (replaceExtension (file tcm) ".hi") iface
294 let mod_info = HomeModInfo iface details Nothing
296 case lookupModuleEnv origNc (tcmModule tcm) of
298 Just occ -> return $ TcModuleResult tcm mod_info occ
300 file = ms_hspp_file . tcModSummary
301 tcmModule = ms_mod . tcModSummary
302 (tcGblEnv, details) = tm_internals_ tcm
303 err = "Internal error : module not found in NameCache :" <>
304 moduleNameString (moduleName $ tcmModule tcm)
306tcModSummary :: TypecheckedModule -> ModSummary
307tcModSummary = pm_mod_summary . tm_parsed_module
309-- | Setup the environment that GHC needs according to our
310-- best understanding (!)
311setupEnv :: GhcMonad m => UniqSupply -> [TcModuleResult] -> [LoadPackageResult] -> m ()
312setupEnv uniqSupply tms lps = do
313 session <- getSession
315 let mss = map (pm_mod_summary . tm_parsed_module . tmrModule) tms
317 -- set the target and module graph in the session
318 let graph = mkModuleGraph mss
319 setSession session { hsc_mod_graph = graph }
321 -- Make modules available for others that import them,
322 -- by putting them in the finder cache.
323 let ims = map (InstalledModule (thisInstalledUnitId $ hsc_dflags session) . moduleName . ms_mod) mss
324 ifrs = zipWith (\ms -> InstalledFound (ms_location ms)) mss ims
325 liftIO $ modifyIORef (hsc_FC session) $ \fc ->
326 foldl' (\fc (im, ifr) -> GHC.extendInstalledModuleEnv fc im ifr) fc
329 -- construct a new NameCache
330 nc' <- mkNameCache uniqSupply tms lps
331 -- update the name cache
332 liftIO $ modifyIORef (hsc_NC session) $ const nc'
333 -- update the external package state
334 liftIO $ modifyIORef (hsc_EPS session) (updateEps lps)
335 -- load dependent modules, which must be in topological order.
336 mapM_ loadModuleHome tms
338-- | Update the external package state given the loaded package results.
339updateEps :: [LoadPackageResult] -> ExternalPackageState -> ExternalPackageState
342 { eps_inst_env = newInstEnv
345 , eps_rule_base = newRuleBase
346 , eps_complete_matches = newCompleteMatches
347 , eps_fam_inst_env = newFamInst
348 , eps_ann_env = newAnnEnv
349 , eps_mod_fam_inst_env = newModFamInstEnv
352 (newInstEnv, (newPIT, (newPTE, (newRuleBase, (newCompleteMatches, (newFamInst, (newAnnEnv, newModFamInstEnv))))))) =
354 (\(instEnv, (pit, (pte, (ruleBase, (completeMatches, (famInst, (annEnv, modFamInstEnv))))))) ->
355 (instEnv `extendInstEnvList0`) ***
356 (pit `plusModuleEnv`) ***
357 (pte `plusTypeEnv`) ***
358 (ruleBase `unionRuleBase`) ***
359 (completeMatches `extendCompleteMatchMap`) ***
360 (famInst `extendFamInstEnvList`) ***
361 (annEnv `plusAnnEnv`) *** (modFamInstEnv `plusModuleEnv`))
363 , ( emptyPackageIfaceTable
366 , (emptyUFM, (emptyFamInstEnv, (emptyAnnEnv, emptyModuleEnv)))))))
367 [ ( instEnvElts $ eps_inst_env e
371 , ( concat $ eltsUFM $ eps_complete_matches e
372 , ( famInstEnvElts $ eps_fam_inst_env e
373 , (eps_ann_env e, eps_mod_fam_inst_env e)))))))
378 -- TODO (drsk): This is necessary because the EPS that we store include the results of
379 -- previously loaded packages and we end up adding instances several times to the environment.
380 -- It would be better to have pure delta stored in the LoadPackageResult, such that it
381 -- contains only identities/instances/names coming from that specific loaded package, but I
382 -- failed so far in computing the correct delta.
383 extendInstEnvList0 instEnv0 clsInsts =
384 extendInstEnvList emptyInstEnv $
385 nubOrdOn is_dfun_name $ instEnvElts instEnv0 ++ clsInsts
387-- | Load a module, quickly. Input doesn't need to be desugared.
388-- A module must be loaded before dependent modules can be typechecked.
389-- This variant of loadModuleHome will *never* cause recompilation, it just
390-- modifies the session.
395loadModuleHome tmr = modifySession $ \e ->
396 e { hsc_HPT = addToHpt (hsc_HPT e) mod mod_info }
398 ms = pm_mod_summary . tm_parsed_module . tmrModule $ tmr
399 mod_info = tmrModInfo tmr
402-- | Produce a module summary from a StringBuffer.
403getModSummaryFromBuffer
406 -> (SB.StringBuffer, UTCTime)
409 -> Ex.ExceptT [FileDiagnostic] m ModSummary
410getModSummaryFromBuffer fp (contents, fileDate) dflags parsed = do
411 (modName, imports) <- FindImports.getImportsParsed dflags parsed
413 let modLoc = ModLocation
414 { ml_hs_file = Just fp
415 , ml_hi_file = replaceExtension fp "hi"
416 , ml_obj_file = replaceExtension fp "o"
417 , ml_hie_file = replaceExtension fp "hie"
418 -- This does not consider the dflags configuration
419 -- (-osuf and -hisuf, object and hi dir.s).
420 -- However, we anyway don't want to generate them.
422 InstalledUnitId unitId = thisInstalledUnitId dflags
424 { ms_mod = mkModule (fsToUnitId unitId) modName
425 , ms_location = modLoc
426 , ms_hs_date = fileDate
427 , ms_textual_imps = imports
429 , ms_hspp_opts = dflags
430 , ms_hspp_buf = Just contents
433 , ms_hsc_src = HsSrcFile
434 , ms_obj_date = Nothing
435 , ms_iface_date = Nothing
436 , ms_hie_date = Nothing
437 , ms_srcimps = [] -- source imports are not allowed
438 , ms_parsed_mod = Nothing
441-- | Given a buffer, flags, file path and module summary, produce a
442-- parsed module (or errors) and any parse warnings.
445 => (GHC.ParsedSource -> ([(GHC.SrcSpan, String)], GHC.ParsedSource))
446 -> FilePath -- ^ the filename (for source locations)
447 -> (UTCTime, SB.StringBuffer) -- ^ Haskell module source text (full Unicode is supported)
448 -> Ex.ExceptT [FileDiagnostic] m ([FileDiagnostic], ParsedModule)
449parseFileContents preprocessor filename (time, contents) = do
450 let loc = mkRealSrcLoc (mkFastString filename) 1 1
451 dflags <- parsePragmasIntoDynFlags filename contents
452 case unP Parser.parseModule (mkPState dflags contents loc) of
454 Ex.throwE $ toDiagnostics dflags $ snd $ getMessages s dflags
455 POk pst rdr_module ->
456 let hpm_annotations =
457 (Map.fromListWith (++) $ annotations pst,
458 Map.fromList ((noSrcSpan,comment_q pst)
459 :annotations_comments pst))
460 (warns,_) = getMessages pst dflags
463 let (errs, parsed) = preprocessor rdr_module
464 unless (null errs) $ Ex.throwE $ mkErrors dflags errs
465 ms <- getModSummaryFromBuffer filename (contents, time) dflags parsed
469 , pm_parsed_source = parsed
470 , pm_extra_src_files=[] -- src imports not allowed
471 , pm_annotations = hpm_annotations
473 warnings = mapMaybe (mkDiag dflags "Parser") $ bagToList warns
477-- | This reads the pragma information directly from the provided buffer.
478parsePragmasIntoDynFlags
482 -> Ex.ExceptT [FileDiagnostic] m DynFlags
483parsePragmasIntoDynFlags fp contents = catchSrcErrors $ do
484 dflags0 <- getSessionDynFlags
485 let opts = Hdr.getOptions dflags0 contents fp
486 (dflags, _, _) <- parseDynamicFilePragma dflags0 opts
489generatePackageState :: [FilePath] -> Bool -> [(String, ModRenaming)] -> IO PackageState
490generatePackageState paths hideAllPkgs pkgImports = do
491 let dflags = setPackageImports hideAllPkgs pkgImports $ setPackageDbs paths (defaultDynFlags fakeSettings fakeLlvmConfig)
492 (newDynFlags, _) <- initPackages dflags
493 pure $ PackageState (pkgDatabase newDynFlags) (pkgState newDynFlags) (thisUnitIdInsts_ newDynFlags)
495-- | Run something in a Ghc monad and catch the errors (SourceErrors and
496-- compiler-internal exceptions like Panic or InstallationError).
497catchSrcErrors :: GhcMonad m => m a -> Ex.ExceptT [FileDiagnostic] m a
498catchSrcErrors ghcM = do
499 dflags <- getDynFlags
501 handleGhcException (ghcExceptionToDiagnostics dflags) $
502 handleSourceError (sourceErrorToDiagnostics dflags) $
505 ghcExceptionToDiagnostics dflags = return . Left . mkErrorsGhcException dflags
506 sourceErrorToDiagnostics dflags = return . Left . toDiagnostics 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