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.Functions.Compile
13 , LoadPackageResult(..)
21 , generatePackageState
24import Development.IDE.Functions.Warnings
25import Development.IDE.Types.Diagnostics
26import qualified Development.IDE.Functions.FindImports as FindImports
27import Development.IDE.Functions.GHCError
28import Development.IDE.Functions.SpanInfo
29import Development.IDE.UtilGHC
30import Development.IDE.Types.Options
32import GHC hiding (parseModule, typecheckModule)
33import qualified Parser
40import GhcPlugins as GHC hiding (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-- | 'CoreModule' together with some additional information required for the
64-- conversion to DAML-LF.
65data GhcModule = GhcModule
66 { gmPath :: Maybe FilePath
67 , gmCore :: CoreModule
69 deriving (Generic, Show)
71instance NFData GhcModule
73-- | Contains the typechecked module and the OrigNameCache entry for
75data TcModuleResult = TcModuleResult
76 { tmrModule :: TypecheckedModule
77 , tmrModInfo :: HomeModInfo
78 , tmrOccEnvName :: OccEnv Name
81-- | Contains the result of loading an interface. In particular the delta to the name cache.
82data LoadPackageResult = LoadPackageResult
83 { lprInstalledUnitId :: InstalledUnitId
84 , lprModuleEnv :: ModuleEnv (OccEnv Name)
85 , lprEps :: ExternalPackageState
88-- | Get source span info, used for e.g. AtPoint and Goto Definition.
93 -> [(Located ModuleName, Maybe FilePath)]
96getSrcSpanInfos opt mod packageState imports tc =
97 runGhcSession opt (Just mod) packageState
102-- | Given a string buffer, return a pre-processed @ParsedModule@.
107 -> (UTCTime, SB.StringBuffer)
108 -> IO ([FileDiagnostic], Maybe ParsedModule)
109parseModule opt@IdeOptions{..} packageState file =
110 fmap (either (, Nothing) (second Just)) . Ex.runExceptT .
111 -- We need packages since imports fail to resolve otherwise.
112 runGhcSessionExcept opt Nothing packageState . parseFileContents optPreprocessor file
115 IdeOptions -> PackageDynFlags -> InstalledUnitId -> IO (Either [FileDiagnostic] [InstalledUnitId])
116computePackageDeps opts packageState iuid =
118 runGhcSessionExcept opts Nothing packageState $
120 dflags <- hsc_dflags <$> getSession
121 liftIO $ depends <$> getPackage dflags iuid
123getPackage :: DynFlags -> InstalledUnitId -> IO PackageConfig
125 case lookupInstalledPackage dflags p of
126 Nothing -> E.throwIO $ CmdLineError (missingPackageMsg p)
127 Just pkg -> return pkg
129 missingPackageMsg p = showSDoc dflags $ text "unknown package:" <+> ppr p
131-- | Typecheck a single module using the supplied dependencies and packages.
138 -> [LoadPackageResult]
140 -> IO ([FileDiagnostic], Maybe TcModuleResult)
141typecheckModule opt mod packageState uniqSupply deps pkgs pm =
142 fmap (either (, Nothing) (second Just)) $ Ex.runExceptT $
143 runGhcSessionExcept opt (Just mod) packageState $
145 setupEnv uniqSupply deps pkgs
146 (warnings, tcm) <- withWarnings "Typechecker" $ \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 -> [LoadPackageResult]
158 -> IO (Either [FileDiagnostic] LoadPackageResult)
159loadPackage opt packageState us lps p =
161 runGhcSessionExcept opt Nothing packageState $
164 dflags <- hsc_dflags <$> getSession
165 exposedMods <- liftIO $ exposedModules <$> getPackage dflags p
167 [ Module (DefiniteUnitId (DefUnitId p)) mod
168 | (mod, _mbParent) <- exposedMods
170 forM_ mods $ \mod -> GHC.getModuleInfo mod
171 -- this populates the namecache and external package state
172 session <- getSession
173 modEnv <- nsNames <$> liftIO (readIORef $ hsc_NC session)
174 eps <- liftIO (readIORef $ hsc_EPS session)
175 pure $ LoadPackageResult p modEnv eps
177-- | Compile a single type-checked module to a 'CoreModule' value, or
185 -> [LoadPackageResult]
187 -> IO ([FileDiagnostic], Maybe GhcModule)
188compileModule opt mod packageState uniqSupply deps pkgs tmr =
189 fmap (either (, Nothing) (second Just)) $ Ex.runExceptT $
190 runGhcSessionExcept opt (Just mod) packageState $
192 setupEnv uniqSupply (deps ++ [tmr]) pkgs
194 let tm = tmrModule tmr
195 session <- getSession
196 (warnings,desugar) <- withWarnings "Desugarer" $ \tweak -> do
197 let pm = tm_parsed_module tm
198 let pm' = pm{pm_mod_summary = tweak $ pm_mod_summary pm}
199 let tm' = tm{tm_parsed_module = pm'}
200 removeTypeableInfo . GHC.dm_core_module <$> GHC.desugarModule tm'
202 -- give variables unique OccNames
203 (tidy, details) <- liftIO $ tidyProgram session desugar
205 let path = ml_hs_file $ ms_location $ pm_mod_summary $ tm_parsed_module tm
206 let core = CoreModule
210 (mg_safe_haskell desugar)
212 return (warnings, GhcModule path core)
214-- | Evaluate a GHC session using a new environment constructed with
215-- the supplied options.
218 -> Maybe ParsedModule
220 -> Ex.ExceptT e Ghc a
222runGhcSessionExcept opts mbMod pkg m =
223 Ex.ExceptT $ runGhcSession opts mbMod pkg $ Ex.runExceptT m
226getGhcDynFlags :: IdeOptions -> ParsedModule -> PackageDynFlags -> IO DynFlags
227getGhcDynFlags opts mod pkg = runGhcSession opts (Just mod) pkg getSessionDynFlags
229-- | Evaluate a GHC session using a new environment constructed with
230-- the supplied options.
233 -> Maybe ParsedModule
237runGhcSession IdeOptions{..} = optRunGhcSession
239-- When we make a fresh GHC environment, the OrigNameCache comes already partially
240-- populated. So to be safe, we simply extend this one.
241mkNameCache :: GhcMonad m => UniqSupply -> [TcModuleResult] -> [LoadPackageResult] -> m NameCache
242mkNameCache uniqSupply tms pkgs = do
243 session <- getSession
244 onc <- nsNames <$> liftIO (readIORef $ hsc_NC session)
246 { nsUniqs = uniqSupply
247 , nsNames = extendOrigNameCache' onc tms pkgs
250-- | Extend the name cache with the names from the typechecked home modules and the loaded packages.
251-- If we have two environments containing the same module we take the later one. We do this because
252-- the name cache comes prepopulated with modules from daml-prim and we overwrite those with our own
254extendOrigNameCache' :: OrigNameCache -> [TcModuleResult] -> [LoadPackageResult] -> OrigNameCache
255extendOrigNameCache' onc tms pkgs = foldl (plusModuleEnv_C (\_x y -> y)) onc modEnvs
259 [(ms_mod $ tcModSummary $ tmrModule tm, tmrOccEnvName tm) | tm <- tms] :
260 [lprModuleEnv lm | lm <- pkgs]
262newtype WriteInterface = WriteInterface Bool
269mkTcModuleResult (WriteInterface writeIface) tcm = do
270 session <- getSession
271 nc <- liftIO $ readIORef (hsc_NC session)
272 (iface,_) <- liftIO $ mkIfaceTc session Nothing Sf_None details tcGblEnv
275 writeIfaceFile (hsc_dflags session) (replaceExtension (file tcm) ".hi") iface
276 let mod_info = HomeModInfo iface details Nothing
278 case lookupModuleEnv origNc (tcmModule tcm) of
280 Just occ -> return $ TcModuleResult tcm mod_info occ
282 file = ms_hspp_file . tcModSummary
283 tcmModule = ms_mod . tcModSummary
284 (tcGblEnv, details) = tm_internals_ tcm
285 err = "Internal error : module not found in NameCache :" <>
286 moduleNameString (moduleName $ tcmModule tcm)
288tcModSummary :: TypecheckedModule -> ModSummary
289tcModSummary = pm_mod_summary . tm_parsed_module
291-- | Setup the environment that GHC needs according to our
292-- best understanding (!)
293setupEnv :: GhcMonad m => UniqSupply -> [TcModuleResult] -> [LoadPackageResult] -> m ()
294setupEnv uniqSupply tms lps = do
295 session <- getSession
297 let mss = map (pm_mod_summary . tm_parsed_module . tmrModule) tms
299 -- set the target and module graph in the session
300 let graph = mkModuleGraph mss
301 setSession session { hsc_mod_graph = graph }
303 -- Make modules available for others that import them,
304 -- by putting them in the finder cache.
305 let ims = map (InstalledModule (thisInstalledUnitId $ hsc_dflags session) . moduleName . ms_mod) mss
306 ifrs = zipWith (\ms -> InstalledFound (ms_location ms)) mss ims
307 liftIO $ modifyIORef (hsc_FC session) $ \fc ->
308 foldl' (\fc (im, ifr) -> GHC.extendInstalledModuleEnv fc im ifr) fc
311 -- construct a new NameCache
312 nc' <- mkNameCache uniqSupply tms lps
313 -- update the name cache
314 liftIO $ modifyIORef (hsc_NC session) $ const nc'
315 -- update the external package state
316 liftIO $ modifyIORef (hsc_EPS session) (updateEps lps)
317 -- load dependent modules, which must be in topological order.
318 mapM_ loadModuleHome tms
320-- | Update the external package state given the loaded package results.
321updateEps :: [LoadPackageResult] -> ExternalPackageState -> ExternalPackageState
324 { eps_inst_env = newInstEnv
327 , eps_rule_base = newRuleBase
328 , eps_complete_matches = newCompleteMatches
329 , eps_fam_inst_env = newFamInst
330 , eps_ann_env = newAnnEnv
331 , eps_mod_fam_inst_env = newModFamInstEnv
334 (newInstEnv, (newPIT, (newPTE, (newRuleBase, (newCompleteMatches, (newFamInst, (newAnnEnv, newModFamInstEnv))))))) =
336 (\(instEnv, (pit, (pte, (ruleBase, (completeMatches, (famInst, (annEnv, modFamInstEnv))))))) ->
337 (instEnv `extendInstEnvList0`) ***
338 (pit `plusModuleEnv`) ***
339 (pte `plusTypeEnv`) ***
340 (ruleBase `unionRuleBase`) ***
341 (completeMatches `extendCompleteMatchMap`) ***
342 (famInst `extendFamInstEnvList`) ***
343 (annEnv `plusAnnEnv`) *** (modFamInstEnv `plusModuleEnv`))
345 , ( emptyPackageIfaceTable
348 , (emptyUFM, (emptyFamInstEnv, (emptyAnnEnv, emptyModuleEnv)))))))
349 [ ( instEnvElts $ eps_inst_env e
353 , ( concat $ eltsUFM $ eps_complete_matches e
354 , ( famInstEnvElts $ eps_fam_inst_env e
355 , (eps_ann_env e, eps_mod_fam_inst_env e)))))))
360 -- TODO (drsk): This is necessary because the EPS that we store include the results of
361 -- previously loaded packages and we end up adding instances several times to the environment.
362 -- It would be better to have pure delta stored in the LoadPackageResult, such that it
363 -- contains only identities/instances/names coming from that specific loaded package, but I
364 -- failed so far in computing the correct delta.
365 extendInstEnvList0 instEnv0 clsInsts =
366 extendInstEnvList emptyInstEnv $
367 nubOrdOn is_dfun_name $ instEnvElts instEnv0 ++ clsInsts
369-- | Load a module, quickly. Input doesn't need to be desugared.
370-- A module must be loaded before dependent modules can be typechecked.
371-- This variant of loadModuleHome will *never* cause recompilation, it just
372-- modifies the session.
377loadModuleHome tmr = modifySession $ \e ->
378 e { hsc_HPT = addToHpt (hsc_HPT e) mod mod_info }
380 ms = pm_mod_summary . tm_parsed_module . tmrModule $ tmr
381 mod_info = tmrModInfo tmr
384-- | Produce a module summary from a StringBuffer.
385getModSummaryFromBuffer
388 -> (SB.StringBuffer, UTCTime)
391 -> Ex.ExceptT [FileDiagnostic] m ModSummary
392getModSummaryFromBuffer fp (contents, fileDate) dflags parsed = do
393 (modName, imports) <- FindImports.getImportsParsed dflags parsed
395 let modLoc = ModLocation
396 { ml_hs_file = Just fp
397 , ml_hi_file = replaceExtension fp "hi"
398 , ml_obj_file = replaceExtension fp "o"
400 , ml_hie_file = replaceExtension fp "hie"
402 -- This does not consider the dflags configuration
403 -- (-osuf and -hisuf, object and hi dir.s).
404 -- However, we anyway don't want to generate them.
406 InstalledUnitId unitId = thisInstalledUnitId dflags
408 { ms_mod = mkModule (fsToUnitId unitId) modName
409 , ms_location = modLoc
410 , ms_hs_date = fileDate
411 , ms_textual_imps = imports
413 , ms_hspp_opts = dflags
414 , ms_hspp_buf = Just contents
417 , ms_hsc_src = HsSrcFile
418 , ms_obj_date = Nothing
419 , ms_iface_date = Nothing
421 , ms_hie_date = Nothing
423 , ms_srcimps = [] -- source imports are not allowed
424 , ms_parsed_mod = Nothing
427-- | Given a buffer, flags, file path and module summary, produce a
428-- parsed module (or errors) and any parse warnings.
431 => (GHC.ParsedSource -> ([(GHC.SrcSpan, String)], GHC.ParsedSource))
432 -> FilePath -- ^ the filename (for source locations)
433 -> (UTCTime, SB.StringBuffer) -- ^ Haskell module source text (full Unicode is supported)
434 -> Ex.ExceptT [FileDiagnostic] m ([FileDiagnostic], ParsedModule)
435parseFileContents preprocessor filename (time, contents) = do
436 let loc = mkRealSrcLoc (mkFastString filename) 1 1
437 dflags <- parsePragmasIntoDynFlags filename contents
438 case unP Parser.parseModule (mkPState dflags contents loc) of
440 PFailed getMessages _ _ ->
441 Ex.throwE $ toDiagnostics dflags $ snd $ getMessages dflags
444 Ex.throwE $ toDiagnostics dflags $ snd $ getMessages s dflags
446 POk pst rdr_module ->
447 let hpm_annotations =
448 (Map.fromListWith (++) $ annotations pst,
449 Map.fromList ((noSrcSpan,comment_q pst)
450 :annotations_comments pst))
451 (warns,_) = getMessages pst dflags
454 let (errs, parsed) = preprocessor rdr_module
455 unless (null errs) $ Ex.throwE $ mkErrors dflags errs
456 ms <- getModSummaryFromBuffer filename (contents, time) dflags parsed
460 , pm_parsed_source = parsed
461 , pm_extra_src_files=[] -- src imports not allowed
462 , pm_annotations = hpm_annotations
464 warnings = mapMaybe (mkDiag dflags "Parser") $ bagToList warns
468-- | This reads the pragma information directly from the provided buffer.
469parsePragmasIntoDynFlags
473 -> Ex.ExceptT [FileDiagnostic] m DynFlags
474parsePragmasIntoDynFlags fp contents = catchSrcErrors $ do
475 dflags0 <- getSessionDynFlags
476 let opts = Hdr.getOptions dflags0 contents fp
477 (dflags, _, _) <- parseDynamicFilePragma dflags0 opts
480generatePackageState :: [FilePath] -> Bool -> [(String, ModRenaming)] -> IO PackageDynFlags
481generatePackageState paths hideAllPkgs pkgImports = do
482 let dflags = setPackageImports hideAllPkgs pkgImports $ setPackageDbs paths (defaultDynFlags fakeSettings fakeLlvmConfig)
483 (newDynFlags, _) <- initPackages dflags
484 pure $ getPackageDynFlags newDynFlags
486-- | Run something in a Ghc monad and catch the errors (SourceErrors and
487-- compiler-internal exceptions like Panic or InstallationError).
488catchSrcErrors :: GhcMonad m => m a -> Ex.ExceptT [FileDiagnostic] m a
489catchSrcErrors ghcM = do
490 dflags <- getDynFlags
492 handleGhcException (ghcExceptionToDiagnostics dflags) $
493 handleSourceError (sourceErrorToDiagnostics dflags) $
496 ghcExceptionToDiagnostics dflags = return . Left . mkErrorsGhcException dflags
497 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