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
35import GHC hiding (parseModule, typecheckModule)
36import qualified Parser
43import GhcPlugins as GHC hiding (fst3, (<>))
44import qualified HeaderInfo as Hdr
47import StringBuffer as SB
53import Control.Exception as E
55import qualified Control.Monad.Trans.Except as Ex
59import Data.Tuple.Extra
60import qualified Data.Map.Strict as Map
62import Development.IDE.Types.SpanInfo
63import GHC.Generics (Generic)
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 FilePath)]
99getSrcSpanInfos opt mod packageState imports tc =
100 runGhcSession opt (Just mod) packageState
101 . getSpanInfo imports
105-- | Given a string buffer, return a pre-processed @ParsedModule@.
110 -> (UTCTime, SB.StringBuffer)
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 -> PackageDynFlags -> 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.
141 -> [LoadPackageResult]
143 -> IO ([FileDiagnostic], Maybe TcModuleResult)
144typecheckModule opt mod packageState uniqSupply deps pkgs pm =
145 fmap (either (, Nothing) (second Just)) $ Ex.runExceptT $
146 runGhcSessionExcept opt (Just mod) packageState $
148 setupEnv uniqSupply deps pkgs
149 (warnings, tcm) <- withWarnings "Typechecker" $ \tweak ->
150 GHC.typecheckModule pm{pm_mod_summary = tweak $ pm_mod_summary pm}
151 tcm2 <- mkTcModuleResult (WriteInterface $ optWriteIface opt) tcm
152 return (warnings, tcm2)
154-- | Load a pkg and populate the name cache and external package state.
159 -> [LoadPackageResult]
161 -> IO (Either [FileDiagnostic] LoadPackageResult)
162loadPackage opt packageState us lps p =
164 runGhcSessionExcept opt Nothing packageState $
167 dflags <- hsc_dflags <$> getSession
168 exposedMods <- liftIO $ exposedModules <$> getPackage dflags p
170 [ Module (DefiniteUnitId (DefUnitId p)) mod
171 | (mod, _mbParent) <- exposedMods
173 forM_ mods $ \mod -> GHC.getModuleInfo mod
174 -- this populates the namecache and external package state
175 session <- getSession
176 modEnv <- nsNames <$> liftIO (readIORef $ hsc_NC session)
177 eps <- liftIO (readIORef $ hsc_EPS session)
178 pure $ LoadPackageResult p modEnv eps
180-- | Compile a single type-checked module to a 'CoreModule' value, or
188 -> [LoadPackageResult]
190 -> IO ([FileDiagnostic], Maybe GhcModule)
191compileModule opt mod packageState uniqSupply deps pkgs tmr =
192 fmap (either (, Nothing) (second Just)) $ Ex.runExceptT $
193 runGhcSessionExcept opt (Just mod) packageState $
195 setupEnv uniqSupply (deps ++ [tmr]) pkgs
197 let tm = tmrModule tmr
198 session <- getSession
199 (warnings,desugar) <- withWarnings "Desugarer" $ \tweak -> do
200 let pm = tm_parsed_module tm
201 let pm' = pm{pm_mod_summary = tweak $ pm_mod_summary pm}
202 let tm' = tm{tm_parsed_module = pm'}
203 GHC.dm_core_module <$> GHC.desugarModule tm'
205 -- give variables unique OccNames
206 (tidy, details) <- liftIO $ tidyProgram session desugar
208 let path = ml_hs_file $ ms_location $ pm_mod_summary $ tm_parsed_module tm
209 let core = CoreModule
213 (mg_safe_haskell desugar)
215 return (warnings, GhcModule path core)
217-- | Evaluate a GHC session using a new environment constructed with
218-- the supplied options.
221 -> Maybe ParsedModule
223 -> Ex.ExceptT e Ghc a
225runGhcSessionExcept opts mbMod pkg m =
226 Ex.ExceptT $ runGhcSession opts mbMod pkg $ Ex.runExceptT m
229getGhcDynFlags :: IdeOptions -> ParsedModule -> PackageDynFlags -> IO DynFlags
230getGhcDynFlags opts mod pkg = runGhcSession opts (Just mod) pkg getSessionDynFlags
232-- | Evaluate a GHC session using a new environment constructed with
233-- the supplied options.
236 -> Maybe ParsedModule
240runGhcSession IdeOptions{..} = optRunGhcSession
242-- When we make a fresh GHC environment, the OrigNameCache comes already partially
243-- populated. So to be safe, we simply extend this one.
244mkNameCache :: GhcMonad m => UniqSupply -> [TcModuleResult] -> [LoadPackageResult] -> m NameCache
245mkNameCache uniqSupply tms pkgs = do
246 session <- getSession
247 onc <- nsNames <$> liftIO (readIORef $ hsc_NC session)
249 { nsUniqs = uniqSupply
250 , nsNames = extendOrigNameCache' onc tms pkgs
253-- | Extend the name cache with the names from the typechecked home modules and the loaded packages.
254-- If we have two environments containing the same module we take the later one. We do this because
255-- the name cache comes prepopulated with modules from daml-prim and we overwrite those with our own
257extendOrigNameCache' :: OrigNameCache -> [TcModuleResult] -> [LoadPackageResult] -> OrigNameCache
258extendOrigNameCache' onc tms pkgs = foldl (plusModuleEnv_C (\_x y -> y)) onc modEnvs
262 [(ms_mod $ tcModSummary $ tmrModule tm, tmrOccEnvName tm) | tm <- tms] :
263 [lprModuleEnv lm | lm <- pkgs]
265newtype WriteInterface = WriteInterface Bool
272mkTcModuleResult (WriteInterface writeIface) tcm = do
273 session <- getSession
274 nc <- liftIO $ readIORef (hsc_NC session)
275 (iface,_) <- liftIO $ mkIfaceTc session Nothing Sf_None details tcGblEnv
276 liftIO $ when writeIface $ do
277 writeIfaceFile (hsc_dflags session) (replaceExtension (file tcm) ".hi") iface
278 -- For now, we write .hie files whenever we write .hi files which roughly corresponds to
279 -- when we are building a package. It should be easily decoupable if that turns out to be
281 hieFile <- runHsc session $ mkHieFile (tcModSummary tcm) tcGblEnv (fromJust $ renamedSource tcm)
282 writeHieFile (replaceExtension (file tcm) ".hie") hieFile
283 let mod_info = HomeModInfo iface details Nothing
285 case lookupModuleEnv origNc (tcmModule tcm) of
287 Just occ -> return $ TcModuleResult tcm mod_info occ
289 file = ms_hspp_file . tcModSummary
290 tcmModule = ms_mod . tcModSummary
291 (tcGblEnv, details) = tm_internals_ tcm
292 err = "Internal error : module not found in NameCache :" <>
293 moduleNameString (moduleName $ tcmModule tcm)
295tcModSummary :: TypecheckedModule -> ModSummary
296tcModSummary = pm_mod_summary . tm_parsed_module
298-- | Setup the environment that GHC needs according to our
299-- best understanding (!)
300setupEnv :: GhcMonad m => UniqSupply -> [TcModuleResult] -> [LoadPackageResult] -> m ()
301setupEnv uniqSupply tms lps = do
302 session <- getSession
304 let mss = map (pm_mod_summary . tm_parsed_module . tmrModule) tms
306 -- set the target and module graph in the session
307 let graph = mkModuleGraph mss
308 setSession session { hsc_mod_graph = graph }
310 -- Make modules available for others that import them,
311 -- by putting them in the finder cache.
312 let ims = map (InstalledModule (thisInstalledUnitId $ hsc_dflags session) . moduleName . ms_mod) mss
313 ifrs = zipWith (\ms -> InstalledFound (ms_location ms)) mss ims
314 liftIO $ modifyIORef (hsc_FC session) $ \fc ->
315 foldl' (\fc (im, ifr) -> GHC.extendInstalledModuleEnv fc im ifr) fc
318 -- construct a new NameCache
319 nc' <- mkNameCache uniqSupply tms lps
320 -- update the name cache
321 liftIO $ modifyIORef (hsc_NC session) $ const nc'
322 -- update the external package state
323 liftIO $ modifyIORef (hsc_EPS session) (updateEps lps)
324 -- load dependent modules, which must be in topological order.
325 mapM_ loadModuleHome tms
327-- | Update the external package state given the loaded package results.
328updateEps :: [LoadPackageResult] -> ExternalPackageState -> ExternalPackageState
331 { eps_inst_env = newInstEnv
334 , eps_rule_base = newRuleBase
335 , eps_complete_matches = newCompleteMatches
336 , eps_fam_inst_env = newFamInst
337 , eps_ann_env = newAnnEnv
338 , eps_mod_fam_inst_env = newModFamInstEnv
341 (newInstEnv, (newPIT, (newPTE, (newRuleBase, (newCompleteMatches, (newFamInst, (newAnnEnv, newModFamInstEnv))))))) =
343 (\(instEnv, (pit, (pte, (ruleBase, (completeMatches, (famInst, (annEnv, modFamInstEnv))))))) ->
344 (instEnv `extendInstEnvList0`) ***
345 (pit `plusModuleEnv`) ***
346 (pte `plusTypeEnv`) ***
347 (ruleBase `unionRuleBase`) ***
348 (completeMatches `extendCompleteMatchMap`) ***
349 (famInst `extendFamInstEnvList`) ***
350 (annEnv `plusAnnEnv`) *** (modFamInstEnv `plusModuleEnv`))
352 , ( emptyPackageIfaceTable
355 , (emptyUFM, (emptyFamInstEnv, (emptyAnnEnv, emptyModuleEnv)))))))
356 [ ( instEnvElts $ eps_inst_env e
360 , ( concat $ eltsUFM $ eps_complete_matches e
361 , ( famInstEnvElts $ eps_fam_inst_env e
362 , (eps_ann_env e, eps_mod_fam_inst_env e)))))))
367 -- TODO (drsk): This is necessary because the EPS that we store include the results of
368 -- previously loaded packages and we end up adding instances several times to the environment.
369 -- It would be better to have pure delta stored in the LoadPackageResult, such that it
370 -- contains only identities/instances/names coming from that specific loaded package, but I
371 -- failed so far in computing the correct delta.
372 extendInstEnvList0 instEnv0 clsInsts =
373 extendInstEnvList emptyInstEnv $
374 nubOrdOn is_dfun_name $ instEnvElts instEnv0 ++ clsInsts
376-- | Load a module, quickly. Input doesn't need to be desugared.
377-- A module must be loaded before dependent modules can be typechecked.
378-- This variant of loadModuleHome will *never* cause recompilation, it just
379-- modifies the session.
384loadModuleHome tmr = modifySession $ \e ->
385 e { hsc_HPT = addToHpt (hsc_HPT e) mod mod_info }
387 ms = pm_mod_summary . tm_parsed_module . tmrModule $ tmr
388 mod_info = tmrModInfo tmr
391-- | Produce a module summary from a StringBuffer.
392getModSummaryFromBuffer
395 -> (SB.StringBuffer, UTCTime)
398 -> Ex.ExceptT [FileDiagnostic] m ModSummary
399getModSummaryFromBuffer fp (contents, fileDate) dflags parsed = do
400 (modName, imports) <- FindImports.getImportsParsed dflags parsed
402 let modLoc = ModLocation
403 { ml_hs_file = Just fp
404 , ml_hi_file = replaceExtension fp "hi"
405 , ml_obj_file = replaceExtension fp "o"
407 , ml_hie_file = replaceExtension fp "hie"
409 -- This does not consider the dflags configuration
410 -- (-osuf and -hisuf, object and hi dir.s).
411 -- However, we anyway don't want to generate them.
413 InstalledUnitId unitId = thisInstalledUnitId dflags
415 { ms_mod = mkModule (fsToUnitId unitId) modName
416 , ms_location = modLoc
417 , ms_hs_date = fileDate
418 , ms_textual_imps = imports
420 , ms_hspp_opts = dflags
421 , ms_hspp_buf = Just contents
424 , ms_hsc_src = HsSrcFile
425 , ms_obj_date = Nothing
426 , ms_iface_date = Nothing
428 , ms_hie_date = Nothing
430 , ms_srcimps = [] -- source imports are not allowed
431 , ms_parsed_mod = Nothing
434-- | Given a buffer, flags, file path and module summary, produce a
435-- parsed module (or errors) and any parse warnings.
438 => (GHC.ParsedSource -> ([(GHC.SrcSpan, String)], GHC.ParsedSource))
439 -> FilePath -- ^ the filename (for source locations)
440 -> (UTCTime, SB.StringBuffer) -- ^ Haskell module source text (full Unicode is supported)
441 -> Ex.ExceptT [FileDiagnostic] m ([FileDiagnostic], ParsedModule)
442parseFileContents preprocessor filename (time, contents) = do
443 let loc = mkRealSrcLoc (mkFastString filename) 1 1
444 dflags <- parsePragmasIntoDynFlags filename contents
445 case unP Parser.parseModule (mkPState dflags contents loc) of
447 PFailed _ logMsg msgErr ->
448 Ex.throwE $ mkErrorDoc dflags locErr msgErr
451 Ex.throwE $ toDiagnostics dflags $ snd $ getMessages s dflags
453 POk pst rdr_module ->
454 let hpm_annotations =
455 (Map.fromListWith (++) $ annotations pst,
456 Map.fromList ((noSrcSpan,comment_q pst)
457 :annotations_comments pst))
458 (warns,_) = getMessages pst dflags
461 let (errs, parsed) = preprocessor rdr_module
462 unless (null errs) $ Ex.throwE $ mkErrors dflags errs
463 ms <- getModSummaryFromBuffer filename (contents, time) dflags parsed
467 , pm_parsed_source = parsed
468 , pm_extra_src_files=[] -- src imports not allowed
469 , pm_annotations = hpm_annotations
471 warnings = mapMaybe (mkDiag dflags "Parser") $ bagToList warns
475-- | This reads the pragma information directly from the provided buffer.
476parsePragmasIntoDynFlags
480 -> Ex.ExceptT [FileDiagnostic] m DynFlags
481parsePragmasIntoDynFlags fp contents = catchSrcErrors $ do
482 dflags0 <- getSessionDynFlags
483 let opts = Hdr.getOptions dflags0 contents fp
484 (dflags, _, _) <- parseDynamicFilePragma dflags0 opts
487generatePackageState :: [FilePath] -> Bool -> [(String, ModRenaming)] -> IO PackageDynFlags
488generatePackageState paths hideAllPkgs pkgImports = do
489 let dflags = setPackageImports hideAllPkgs pkgImports $ setPackageDbs paths fakeDynFlags
490 (newDynFlags, _) <- initPackages dflags
491 pure $ getPackageDynFlags newDynFlags
493-- | Run something in a Ghc monad and catch the errors (SourceErrors and
494-- compiler-internal exceptions like Panic or InstallationError).
495catchSrcErrors :: GhcMonad m => m a -> Ex.ExceptT [FileDiagnostic] m a
496catchSrcErrors ghcM = do
497 dflags <- getDynFlags
499 handleGhcException (ghcExceptionToDiagnostics dflags) $
500 handleSourceError (sourceErrorToDiagnostics dflags) $
503 ghcExceptionToDiagnostics dflags = return . Left . mkErrorsGhcException dflags
504 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