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
19import Development.IDE.Core.RuleTypes
20import Development.IDE.GHC.CPP
21import Development.IDE.GHC.Error
22import Development.IDE.GHC.Warnings
23import Development.IDE.Types.Diagnostics
24import Development.IDE.GHC.Orphans()
25import Development.IDE.GHC.Util
26import Development.IDE.GHC.Compat
27import qualified GHC.LanguageExtensions.Type as GHC
28import Development.IDE.Types.Options
29import Development.IDE.Types.Location
31import GHC hiding (parseModule, typecheckModule)
32import qualified Parser
39import GhcPlugins as GHC hiding (fst3, (<>))
40import qualified HeaderInfo as Hdr
42import StringBuffer as SB
44import qualified GHC.LanguageExtensions as LangExt
46import Control.Monad.Extra
47import Control.Monad.Except
48import Control.Monad.Trans.Except
49import qualified Data.Text as T
53import Data.Tuple.Extra
54import qualified Data.Map.Strict as Map
56import System.Directory
60-- | Given a string buffer, return a pre-processed @ParsedModule@.
65 -> Maybe SB.StringBuffer
66 -> IO ([FileDiagnostic], Maybe ParsedModule)
67parseModule IdeOptions{..} env file =
68 fmap (either (, Nothing) (second Just)) .
69 -- We need packages since imports fail to resolve otherwise.
70 runGhcEnv env . runExceptT . parseFileContents optPreprocessor file
73-- | Given a package identifier, what packages does it depend on
77 -> IO (Either [FileDiagnostic] [InstalledUnitId])
78computePackageDeps env pkg = do
79 let dflags = hsc_dflags env
80 case lookupInstalledPackage dflags pkg of
81 Nothing -> return $ Left [ideErrorText (toNormalizedFilePath noFilePath) $
82 T.pack $ "unknown package: " ++ show pkg]
83 Just pkgInfo -> return $ Right $ depends pkgInfo
86-- | Typecheck a single module using the supplied dependencies and packages.
92 -> IO ([FileDiagnostic], Maybe TcModuleResult)
93typecheckModule opt packageState deps pm =
94 fmap (either (, Nothing) (second Just)) $
95 runGhcEnv packageState $
98 (warnings, tcm) <- withWarnings $ \tweak ->
99 GHC.typecheckModule pm{pm_mod_summary = tweak $ pm_mod_summary pm}
100 tcm2 <- mkTcModuleResult (optIfaceDir opt) tcm
101 return (warnings, tcm2)
103-- | Compile a single type-checked module to a 'CoreModule' value, or
109 -> IO ([FileDiagnostic], Maybe CoreModule)
110compileModule packageState deps tmr =
111 fmap (either (, Nothing) (second Just)) $
112 runGhcEnv packageState $
114 setupEnv (deps ++ [tmr])
116 let tm = tmrModule tmr
117 session <- getSession
118 (warnings,desugar) <- withWarnings $ \tweak -> do
119 let pm = tm_parsed_module tm
120 let pm' = pm{pm_mod_summary = tweak $ pm_mod_summary pm}
121 let tm' = tm{tm_parsed_module = pm'}
122 GHC.dm_core_module <$> GHC.desugarModule tm'
124 -- give variables unique OccNames
125 (tidy, details) <- liftIO $ tidyProgram session desugar
127 let core = CoreModule
131 (mg_safe_haskell desugar)
133 return (warnings, core)
136addRelativeImport :: ParsedModule -> DynFlags -> DynFlags
137addRelativeImport modu dflags = dflags
138 {importPaths = nubOrd $ maybeToList (moduleImportPaths modu) ++ importPaths dflags}
142 => InterfaceDirectory
145mkTcModuleResult (InterfaceDirectory mbIfaceDir) tcm = do
146 session <- getSession
147 (iface,_) <- liftIO $ mkIfaceTc session Nothing Sf_None details tcGblEnv
148 liftIO $ whenJust mbIfaceDir $ \ifaceDir -> do
149 let path = ifaceDir </> file tcm
150 createDirectoryIfMissing True (takeDirectory path)
151 writeIfaceFile (hsc_dflags session) (replaceExtension path ".hi") iface
152 -- For now, we write .hie files whenever we write .hi files which roughly corresponds to
153 -- when we are building a package. It should be easily decoupable if that turns out to be
155 hieFile <- runHsc session $ mkHieFile (tcModSummary tcm) tcGblEnv (fromJust $ renamedSource tcm)
156 writeHieFile (replaceExtension path ".hie") hieFile
157 let mod_info = HomeModInfo iface details Nothing
158 return $ TcModuleResult tcm mod_info
160 file = ms_hspp_file . tcModSummary
161 (tcGblEnv, details) = tm_internals_ tcm
163tcModSummary :: TypecheckedModule -> ModSummary
164tcModSummary = pm_mod_summary . tm_parsed_module
166-- | Setup the environment that GHC needs according to our
167-- best understanding (!)
168setupEnv :: GhcMonad m => [TcModuleResult] -> m ()
170 session <- getSession
172 let mss = map (pm_mod_summary . tm_parsed_module . tmrModule) tms
174 -- set the target and module graph in the session
175 let graph = mkModuleGraph mss
176 setSession session { hsc_mod_graph = graph }
178 -- Make modules available for others that import them,
179 -- by putting them in the finder cache.
180 let ims = map (InstalledModule (thisInstalledUnitId $ hsc_dflags session) . moduleName . ms_mod) mss
181 ifrs = zipWith (\ms -> InstalledFound (ms_location ms)) mss ims
182 liftIO $ modifyIORef (hsc_FC session) $ \fc ->
183 foldl' (\fc (im, ifr) -> GHC.extendInstalledModuleEnv fc im ifr) fc
186 -- load dependent modules, which must be in topological order.
187 mapM_ loadModuleHome tms
190-- | Load a module, quickly. Input doesn't need to be desugared.
191-- A module must be loaded before dependent modules can be typechecked.
192-- This variant of loadModuleHome will *never* cause recompilation, it just
193-- modifies the session.
198loadModuleHome tmr = modifySession $ \e ->
199 e { hsc_HPT = addToHpt (hsc_HPT e) mod mod_info }
201 ms = pm_mod_summary . tm_parsed_module . tmrModule $ tmr
202 mod_info = tmrModInfo tmr
207-- | GhcMonad function to chase imports of a module given as a StringBuffer. Returns given module's
208-- name and its imports.
209getImportsParsed :: DynFlags ->
211 Either [FileDiagnostic] (GHC.ModuleName, [(Maybe FastString, Located GHC.ModuleName)])
212getImportsParsed dflags (L loc parsed) = do
213 let modName = maybe (GHC.mkModuleName "Main") GHC.unLoc $ GHC.hsmodName parsed
215 -- refuse source imports
216 let srcImports = filter (ideclSource . GHC.unLoc) $ GHC.hsmodImports parsed
217 when (not $ null srcImports) $ Left $
219 [ diagFromString mloc ("Illegal source import of " <> GHC.moduleNameString (GHC.unLoc $ GHC.ideclName i))
220 | L mloc i <- srcImports ]
222 -- most of these corner cases are also present in https://hackage.haskell.org/package/ghc-8.6.1/docs/src/HeaderInfo.html#getImports
223 -- but we want to avoid parsing the module twice
224 let implicit_prelude = xopt GHC.ImplicitPrelude dflags
225 implicit_imports = Hdr.mkPrelImports modName loc implicit_prelude $ GHC.hsmodImports parsed
227 -- filter out imports that come from packages
228 return (modName, [(fmap sl_fs $ ideclPkgQual i, ideclName i)
229 | i <- map GHC.unLoc $ implicit_imports ++ GHC.hsmodImports parsed
230 , GHC.moduleNameString (GHC.unLoc $ ideclName i) /= "GHC.Prim"
234-- | Produce a module summary from a StringBuffer.
235getModSummaryFromBuffer
241 -> ExceptT [FileDiagnostic] m ModSummary
242getModSummaryFromBuffer fp contents dflags parsed = do
243 (modName, imports) <- liftEither $ getImportsParsed dflags parsed
245 let modLoc = ModLocation
246 { ml_hs_file = Just fp
247 , ml_hi_file = replaceExtension fp "hi"
248 , ml_obj_file = replaceExtension fp "o"
250 , ml_hie_file = replaceExtension fp "hie"
252 -- This does not consider the dflags configuration
253 -- (-osuf and -hisuf, object and hi dir.s).
254 -- However, we anyway don't want to generate them.
256 InstalledUnitId unitId = thisInstalledUnitId dflags
258 { ms_mod = mkModule (fsToUnitId unitId) modName
259 , ms_location = modLoc
260 , ms_hs_date = error "Rules should not depend on ms_hs_date"
261 -- When we are working with a virtual file we do not have a file date.
262 -- To avoid silent issues where something is not processed because the date
263 -- has not changed, we make sure that things blow up if they depend on the
265 , ms_textual_imps = imports
267 , ms_hspp_opts = dflags
268 , ms_hspp_buf = Just contents
271 , ms_hsc_src = HsSrcFile
272 , ms_obj_date = Nothing
273 , ms_iface_date = Nothing
275 , ms_hie_date = Nothing
277 , ms_srcimps = [] -- source imports are not allowed
278 , ms_parsed_mod = Nothing
281-- | Run CPP on a file
282runCpp :: DynFlags -> FilePath -> Maybe SB.StringBuffer -> IO SB.StringBuffer
283runCpp dflags filename contents = withTempDir $ \dir -> do
284 let out = dir </> takeFileName filename <.> "out"
287 -- Happy case, file is not modified, so run CPP on it in-place
288 -- which also makes things like relative #include files work
289 -- and means location information is correct
290 doCpp dflags True filename out
291 liftIO $ SB.hGetStringBuffer out
294 -- Sad path, we have to create a version of the path in a temp dir
295 -- __FILE__ macro is wrong, ignoring that for now (likely not a real issue)
297 -- Relative includes aren't going to work, so we fix that by adding to the include path.
298 let addSelf (IncludeSpecs quote global) = IncludeSpecs (takeDirectory filename : quote) global
299 dflags <- return dflags{includePaths = addSelf $ includePaths dflags}
301 -- Location information is wrong, so we fix that by patching it afterwards.
302 let inp = dir </> "___HIE_CORE_MAGIC___"
303 withBinaryFile inp WriteMode $ \h ->
304 hPutStringBuffer h contents
305 doCpp dflags True inp out
307 -- Fix up the filename in lines like:
308 -- # 1 "C:/Temp/extra-dir-914611385186/___HIE_CORE_MAGIC___"
310 | Just x <- stripPrefix "# " x
311 , "___HIE_CORE_MAGIC___" `isInfixOf` x
312 , let num = takeWhile (not . isSpace) x
313 -- important to use /, and never \ for paths, even on Windows, since then C escapes them
314 -- and GHC gets all confused
315 = "# " <> num <> " \"" <> map (\x -> if isPathSeparator x then '/' else x) filename <> "\""
317 stringToStringBuffer . unlines . map tweak . lines <$> readFileUTF8' out
320-- | Given a buffer, flags, file path and module summary, produce a
321-- parsed module (or errors) and any parse warnings.
324 => (GHC.ParsedSource -> ([(GHC.SrcSpan, String)], GHC.ParsedSource))
325 -> FilePath -- ^ the filename (for source locations)
326 -> Maybe SB.StringBuffer -- ^ Haskell module source text (full Unicode is supported)
327 -> ExceptT [FileDiagnostic] m ([FileDiagnostic], ParsedModule)
328parseFileContents preprocessor filename mbContents = do
329 contents <- liftIO $ maybe (hGetStringBuffer filename) return mbContents
330 let loc = mkRealSrcLoc (mkFastString filename) 1 1
331 dflags <- ExceptT $ parsePragmasIntoDynFlags filename contents
333 (contents, dflags) <-
334 if not $ xopt LangExt.Cpp dflags then
335 return (contents, dflags)
337 contents <- liftIO $ runCpp dflags filename mbContents
338 dflags <- ExceptT $ parsePragmasIntoDynFlags filename contents
339 return (contents, dflags)
341 case unP Parser.parseModule (mkPState dflags contents loc) of
342 PFailed _ locErr msgErr ->
343 throwE $ diagFromErrMsg dflags $ mkPlainErrMsg dflags locErr msgErr
344 POk pst rdr_module ->
345 let hpm_annotations =
346 (Map.fromListWith (++) $ annotations pst,
347 Map.fromList ((noSrcSpan,comment_q pst)
348 :annotations_comments pst))
349 (warns, errs) = getMessages pst dflags
352 -- Just because we got a `POk`, it doesn't mean there
353 -- weren't errors! To clarify, the GHC parser
354 -- distinguishes between fatal and non-fatal
355 -- errors. Non-fatal errors are the sort that don't
356 -- prevent parsing from continuing (that is, a parse
357 -- tree can still be produced despite the error so that
358 -- further errors/warnings can be collected). Fatal
359 -- errors are those from which a parse tree just can't
362 throwE $ diagFromErrMsgs dflags $ snd $ getMessages pst dflags
364 -- Ok, we got here. It's safe to continue.
365 let (errs, parsed) = preprocessor rdr_module
366 unless (null errs) $ throwE $ diagFromStrings errs
367 ms <- getModSummaryFromBuffer filename contents dflags parsed
371 , pm_parsed_source = parsed
372 , pm_extra_src_files=[] -- src imports not allowed
373 , pm_annotations = hpm_annotations
375 warnings = diagFromErrMsgs dflags warns
379-- | This reads the pragma information directly from the provided buffer.
380parsePragmasIntoDynFlags
384 -> m (Either [FileDiagnostic] DynFlags)
385parsePragmasIntoDynFlags fp contents = catchSrcErrors $ do
386 dflags0 <- getSessionDynFlags
387 let opts = Hdr.getOptions dflags0 contents fp
388 (dflags, _, _) <- parseDynamicFilePragma dflags0 opts
391-- | Run something in a Ghc monad and catch the errors (SourceErrors and
392-- compiler-internal exceptions like Panic or InstallationError).
393catchSrcErrors :: GhcMonad m => m a -> m (Either [FileDiagnostic] a)
394catchSrcErrors ghcM = do
395 dflags <- getDynFlags
396 handleGhcException (ghcExceptionToDiagnostics dflags) $
397 handleSourceError (sourceErrorToDiagnostics dflags) $
400 ghcExceptionToDiagnostics dflags = return . Left . diagFromGhcException dflags
401 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