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
20import Development.IDE.GHC.Warnings
21import Development.IDE.GHC.CPP
22import Development.IDE.Types.Diagnostics
23import qualified Development.IDE.Import.FindImports as FindImports
24import Development.IDE.GHC.Error
25import Development.IDE.Spans.Calculate
26import Development.IDE.GHC.Orphans()
27import Development.IDE.GHC.Util
28import Development.IDE.GHC.Compat
29import Development.IDE.Types.Options
30import Development.IDE.Types.Location
32import GHC hiding (parseModule, typecheckModule)
33import qualified Parser
40import GhcPlugins as GHC hiding (fst3, (<>))
41import qualified HeaderInfo as Hdr
43import StringBuffer as SB
45import qualified GHC.LanguageExtensions as LangExt
49import Control.Monad.Trans.Except
50import qualified Data.Text as T
54import Data.Tuple.Extra
55import qualified Data.Map.Strict as Map
56import Development.IDE.Spans.Type
58import System.Directory
62-- | Contains the typechecked module and the OrigNameCache entry for
64data TcModuleResult = TcModuleResult
65 { tmrModule :: TypecheckedModule
66 , tmrModInfo :: HomeModInfo
68instance Show TcModuleResult where
69 show = show . pm_mod_summary . tm_parsed_module . tmrModule
71instance NFData TcModuleResult where
75-- | Get source span info, used for e.g. AtPoint and Goto Definition.
79 -> [(Located ModuleName, Maybe NormalizedFilePath)]
82getSrcSpanInfos mod env imports tc =
83 runGhcSession (Just mod) env
88-- | Given a string buffer, return a pre-processed @ParsedModule@.
94 -> IO ([FileDiagnostic], Maybe ParsedModule)
95parseModule IdeOptions{..} env file =
96 fmap (either (, Nothing) (second Just)) .
97 -- We need packages since imports fail to resolve otherwise.
98 runGhcSession Nothing env . runExceptT . parseFileContents optPreprocessor file
101-- | Given a package identifier, what packages does it depend on
105 -> IO (Either [FileDiagnostic] [InstalledUnitId])
106computePackageDeps env pkg = do
107 let dflags = hsc_dflags env
108 case lookupInstalledPackage dflags pkg of
109 Nothing -> return $ Left [ideErrorText (toNormalizedFilePath noFilePath) $
110 T.pack $ "unknown package: " ++ show pkg]
111 Just pkgInfo -> return $ Right $ depends pkgInfo
114-- | Typecheck a single module using the supplied dependencies and packages.
120 -> IO ([FileDiagnostic], Maybe TcModuleResult)
121typecheckModule opt packageState deps pm =
122 fmap (either (, Nothing) (second Just)) $
123 runGhcSession (Just pm) packageState $
126 (warnings, tcm) <- withWarnings $ \tweak ->
127 GHC.typecheckModule pm{pm_mod_summary = tweak $ pm_mod_summary pm}
128 tcm2 <- mkTcModuleResult (WriteInterface $ optWriteIface opt) tcm
129 return (warnings, tcm2)
131-- | Compile a single type-checked module to a 'CoreModule' value, or
138 -> IO ([FileDiagnostic], Maybe CoreModule)
139compileModule mod packageState deps tmr =
140 fmap (either (, Nothing) (second Just)) $
141 runGhcSession (Just mod) packageState $
143 setupEnv (deps ++ [tmr])
145 let tm = tmrModule tmr
146 session <- getSession
147 (warnings,desugar) <- withWarnings $ \tweak -> do
148 let pm = tm_parsed_module tm
149 let pm' = pm{pm_mod_summary = tweak $ pm_mod_summary pm}
150 let tm' = tm{tm_parsed_module = pm'}
151 GHC.dm_core_module <$> GHC.desugarModule tm'
153 -- give variables unique OccNames
154 (tidy, details) <- liftIO $ tidyProgram session desugar
156 let core = CoreModule
160 (mg_safe_haskell desugar)
162 return (warnings, core)
165getGhcDynFlags :: ParsedModule -> HscEnv -> IO DynFlags
166getGhcDynFlags mod pkg = runGhcSession (Just mod) pkg getSessionDynFlags
168-- | Evaluate a GHC session using a new environment constructed with
169-- the supplied options.
171 :: Maybe ParsedModule
175runGhcSession modu env act = runGhcEnv env $ do
176 modifyDynFlags $ \x -> x
177 {importPaths = nubOrd $ maybeToList (moduleImportPaths =<< modu) ++ importPaths x}
181moduleImportPaths :: GHC.ParsedModule -> Maybe FilePath
183 | rootModDir == "." = Just rootPathDir
185 -- TODO (MK) stripSuffix (normalise rootModDir) (normalise rootPathDir)
186 -- would be a better choice but at the moment we do not consistently
187 -- normalize file paths in the Shake graph so we can end up with the
188 -- same module being represented twice in the Shake graph.
189 Just $ dropTrailingPathSeparator $ dropEnd (length rootModDir) rootPathDir
191 ms = GHC.pm_mod_summary pm
192 file = GHC.ms_hspp_file ms
194 rootPathDir = takeDirectory file
195 rootModDir = takeDirectory . moduleNameSlashes . GHC.moduleName $ mod'
198newtype WriteInterface = WriteInterface Bool
205mkTcModuleResult (WriteInterface writeIface) tcm = do
206 session <- getSession
207 (iface,_) <- liftIO $ mkIfaceTc session Nothing Sf_None details tcGblEnv
208 liftIO $ when writeIface $ do
209 let path = ".interfaces" </> file tcm
210 createDirectoryIfMissing True (takeDirectory path)
211 writeIfaceFile (hsc_dflags session) (replaceExtension path ".hi") iface
212 -- For now, we write .hie files whenever we write .hi files which roughly corresponds to
213 -- when we are building a package. It should be easily decoupable if that turns out to be
215 hieFile <- runHsc session $ mkHieFile (tcModSummary tcm) tcGblEnv (fromJust $ renamedSource tcm)
216 writeHieFile (replaceExtension path ".hie") hieFile
217 let mod_info = HomeModInfo iface details Nothing
218 return $ TcModuleResult tcm mod_info
220 file = ms_hspp_file . tcModSummary
221 (tcGblEnv, details) = tm_internals_ tcm
223tcModSummary :: TypecheckedModule -> ModSummary
224tcModSummary = pm_mod_summary . tm_parsed_module
226-- | Setup the environment that GHC needs according to our
227-- best understanding (!)
228setupEnv :: GhcMonad m => [TcModuleResult] -> m ()
230 session <- getSession
232 let mss = map (pm_mod_summary . tm_parsed_module . tmrModule) tms
234 -- set the target and module graph in the session
235 let graph = mkModuleGraph mss
236 setSession session { hsc_mod_graph = graph }
238 -- Make modules available for others that import them,
239 -- by putting them in the finder cache.
240 let ims = map (InstalledModule (thisInstalledUnitId $ hsc_dflags session) . moduleName . ms_mod) mss
241 ifrs = zipWith (\ms -> InstalledFound (ms_location ms)) mss ims
242 liftIO $ modifyIORef (hsc_FC session) $ \fc ->
243 foldl' (\fc (im, ifr) -> GHC.extendInstalledModuleEnv fc im ifr) fc
246 -- load dependent modules, which must be in topological order.
247 mapM_ loadModuleHome tms
250-- | Load a module, quickly. Input doesn't need to be desugared.
251-- A module must be loaded before dependent modules can be typechecked.
252-- This variant of loadModuleHome will *never* cause recompilation, it just
253-- modifies the session.
258loadModuleHome tmr = modifySession $ \e ->
259 e { hsc_HPT = addToHpt (hsc_HPT e) mod mod_info }
261 ms = pm_mod_summary . tm_parsed_module . tmrModule $ tmr
262 mod_info = tmrModInfo tmr
265-- | Produce a module summary from a StringBuffer.
266getModSummaryFromBuffer
272 -> ExceptT [FileDiagnostic] m ModSummary
273getModSummaryFromBuffer fp contents dflags parsed = do
274 (modName, imports) <- FindImports.getImportsParsed dflags parsed
276 let modLoc = ModLocation
277 { ml_hs_file = Just fp
278 , ml_hi_file = replaceExtension fp "hi"
279 , ml_obj_file = replaceExtension fp "o"
281 , ml_hie_file = replaceExtension fp "hie"
283 -- This does not consider the dflags configuration
284 -- (-osuf and -hisuf, object and hi dir.s).
285 -- However, we anyway don't want to generate them.
287 InstalledUnitId unitId = thisInstalledUnitId dflags
289 { ms_mod = mkModule (fsToUnitId unitId) modName
290 , ms_location = modLoc
291 , ms_hs_date = error "Rules should not depend on ms_hs_date"
292 -- ^ When we are working with a virtual file we do not have a file date.
293 -- To avoid silent issues where something is not processed because the date
294 -- has not changed, we make sure that things blow up if they depend on the
296 , ms_textual_imps = imports
298 , ms_hspp_opts = dflags
299 , ms_hspp_buf = Just contents
302 , ms_hsc_src = HsSrcFile
303 , ms_obj_date = Nothing
304 , ms_iface_date = Nothing
306 , ms_hie_date = Nothing
308 , ms_srcimps = [] -- source imports are not allowed
309 , ms_parsed_mod = Nothing
312-- | Given a buffer, flags, file path and module summary, produce a
313-- parsed module (or errors) and any parse warnings.
316 => (GHC.ParsedSource -> ([(GHC.SrcSpan, String)], GHC.ParsedSource))
317 -> FilePath -- ^ the filename (for source locations)
318 -> SB.StringBuffer -- ^ Haskell module source text (full Unicode is supported)
319 -> ExceptT [FileDiagnostic] m ([FileDiagnostic], ParsedModule)
320parseFileContents preprocessor filename contents = do
321 let loc = mkRealSrcLoc (mkFastString filename) 1 1
322 dflags <- ExceptT $ parsePragmasIntoDynFlags filename contents
324 (contents, dflags) <-
325 if not $ xopt LangExt.Cpp dflags then
326 return (contents, dflags)
328 contents <- liftIO $ withTempDir $ \dir -> do
329 let inp = dir </> takeFileName filename
330 let out = dir </> takeFileName filename <.> "out"
331 let f x = if SB.atEnd x then Nothing else Just $ SB.nextChar x
332 liftIO $ writeFileUTF8 inp (unfoldr f contents)
333 doCpp dflags True inp out
334 liftIO $ SB.hGetStringBuffer out
335 dflags <- ExceptT $ parsePragmasIntoDynFlags filename contents
336 return (contents, dflags)
338 case unP Parser.parseModule (mkPState dflags contents loc) of
339 PFailed _ locErr msgErr ->
340 throwE $ diagFromErrMsg dflags $ mkPlainErrMsg dflags locErr msgErr
341 POk pst rdr_module ->
342 let hpm_annotations =
343 (Map.fromListWith (++) $ annotations pst,
344 Map.fromList ((noSrcSpan,comment_q pst)
345 :annotations_comments pst))
346 (warns, errs) = getMessages pst dflags
349 -- Just because we got a `POk`, it doesn't mean there
350 -- weren't errors! To clarify, the GHC parser
351 -- distinguishes between fatal and non-fatal
352 -- errors. Non-fatal errors are the sort that don't
353 -- prevent parsing from continuing (that is, a parse
354 -- tree can still be produced despite the error so that
355 -- further errors/warnings can be collected). Fatal
356 -- errors are those from which a parse tree just can't
359 throwE $ diagFromErrMsgs dflags $ snd $ getMessages pst dflags
361 -- Ok, we got here. It's safe to continue.
362 let (errs, parsed) = preprocessor rdr_module
363 unless (null errs) $ throwE $ diagFromStrings errs
364 ms <- getModSummaryFromBuffer filename contents dflags parsed
368 , pm_parsed_source = parsed
369 , pm_extra_src_files=[] -- src imports not allowed
370 , pm_annotations = hpm_annotations
372 warnings = diagFromErrMsgs dflags warns
376-- | This reads the pragma information directly from the provided buffer.
377parsePragmasIntoDynFlags
381 -> m (Either [FileDiagnostic] DynFlags)
382parsePragmasIntoDynFlags fp contents = catchSrcErrors $ do
383 dflags0 <- getSessionDynFlags
384 let opts = Hdr.getOptions dflags0 contents fp
385 (dflags, _, _) <- parseDynamicFilePragma dflags0 opts
388-- | Run something in a Ghc monad and catch the errors (SourceErrors and
389-- compiler-internal exceptions like Panic or InstallationError).
390catchSrcErrors :: GhcMonad m => m a -> m (Either [FileDiagnostic] a)
391catchSrcErrors ghcM = do
392 dflags <- getDynFlags
393 handleGhcException (ghcExceptionToDiagnostics dflags) $
394 handleSourceError (sourceErrorToDiagnostics dflags) $
397 ghcExceptionToDiagnostics dflags = return . Left . diagFromGhcException dflags
398 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