1-- Copyright (c) 2019 The DAML Authors. All rights reserved.
2-- SPDX-License-Identifier: Apache-2.0
4{-# 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.Core.Compile
18import Development.IDE.Core.RuleTypes
19import Development.IDE.Core.Preprocessor
20import Development.IDE.GHC.Error
21import Development.IDE.GHC.Warnings
22import Development.IDE.Types.Diagnostics
23import Development.IDE.GHC.Orphans()
24import Development.IDE.GHC.Util
25import qualified GHC.LanguageExtensions.Type as GHC
26import Development.IDE.Types.Options
27import Development.IDE.Types.Location
29import GHC hiding (parseModule, typecheckModule)
30import qualified Parser
36import GhcPlugins as GHC hiding (fst3, (<>))
37import qualified HeaderInfo as Hdr
39import StringBuffer as SB
42import Control.Monad.Extra
43import Control.Monad.Except
44import Control.Monad.Trans.Except
47import qualified Data.Text as T
51import Data.Tuple.Extra
52import qualified Data.Map.Strict as Map
56-- | Given a string buffer, return a pre-processed @ParsedModule@.
61 -> Maybe SB.StringBuffer
62 -> IO ([FileDiagnostic], Maybe ParsedModule)
63parseModule IdeOptions{..} env file =
64 fmap (either (, Nothing) (second Just)) .
65 -- We need packages since imports fail to resolve otherwise.
66 runGhcEnv env . runExceptT . parseFileContents optPreprocessor file
69-- | Given a package identifier, what packages does it depend on
73 -> IO (Either [FileDiagnostic] [InstalledUnitId])
74computePackageDeps env pkg = do
75 let dflags = hsc_dflags env
76 case lookupInstalledPackage dflags pkg of
77 Nothing -> return $ Left [ideErrorText (toNormalizedFilePath noFilePath) $
78 T.pack $ "unknown package: " ++ show pkg]
79 Just pkgInfo -> return $ Right $ depends pkgInfo
82-- | Typecheck a single module using the supplied dependencies and packages.
87 -> IO ([FileDiagnostic], Maybe TcModuleResult)
88typecheckModule packageState deps pm =
89 fmap (either (, Nothing) (second Just)) $
90 runGhcEnv packageState $
91 catchSrcErrors "typecheck" $ do
93 (warnings, tcm) <- withWarnings "typecheck" $ \tweak ->
94 GHC.typecheckModule pm{pm_mod_summary = tweak $ pm_mod_summary pm}
95 tcm2 <- mkTcModuleResult tcm
96 return (warnings, tcm2)
98-- | Compile a single type-checked module to a 'CoreModule' value, or
104 -> IO ([FileDiagnostic], Maybe CoreModule)
105compileModule packageState deps tmr =
106 fmap (either (, Nothing) (second Just)) $
107 runGhcEnv packageState $
108 catchSrcErrors "compile" $ do
109 setupEnv (deps ++ [tmr])
111 let tm = tmrModule tmr
112 session <- getSession
113 (warnings,desugar) <- withWarnings "compile" $ \tweak -> do
114 let pm = tm_parsed_module tm
115 let pm' = pm{pm_mod_summary = tweak $ pm_mod_summary pm}
116 let tm' = tm{tm_parsed_module = pm'}
117 GHC.dm_core_module <$> GHC.desugarModule tm'
119 -- give variables unique OccNames
120 (tidy, details) <- liftIO $ tidyProgram session desugar
122 let core = CoreModule
126 (mg_safe_haskell desugar)
128 return (warnings, core)
131addRelativeImport :: ParsedModule -> DynFlags -> DynFlags
132addRelativeImport modu dflags = dflags
133 {importPaths = nubOrd $ maybeToList (moduleImportPaths modu) ++ importPaths dflags}
139mkTcModuleResult tcm = do
140 session <- getSession
141 (iface, _) <- liftIO $ mkIfaceTc session Nothing Sf_None details tcGblEnv
142 let mod_info = HomeModInfo iface details Nothing
143 return $ TcModuleResult tcm mod_info
145 (tcGblEnv, details) = tm_internals_ tcm
147-- | Setup the environment that GHC needs according to our
148-- best understanding (!)
149setupEnv :: GhcMonad m => [TcModuleResult] -> m ()
151 -- if both a .hs-boot file and a .hs file appear here, we want to make sure that the .hs file
152 -- takes precedence, so put the .hs-boot file earlier in the list
153 let isSourceFile = (==HsBootFile) . ms_hsc_src . pm_mod_summary . tm_parsed_module . tmrModule
154 tms = sortBy (compare `on` Down . isSourceFile) tmsIn
156 session <- getSession
158 let mss = map (pm_mod_summary . tm_parsed_module . tmrModule) tms
160 -- set the target and module graph in the session
161 let graph = mkModuleGraph mss
162 setSession session { hsc_mod_graph = graph }
164 -- Make modules available for others that import them,
165 -- by putting them in the finder cache.
166 let ims = map (InstalledModule (thisInstalledUnitId $ hsc_dflags session) . moduleName . ms_mod) mss
167 ifrs = zipWith (\ms -> InstalledFound (ms_location ms)) mss ims
168 liftIO $ modifyIORef (hsc_FC session) $ \fc ->
169 foldl' (\fc (im, ifr) -> GHC.extendInstalledModuleEnv fc im ifr) fc
172 -- load dependent modules, which must be in topological order.
173 mapM_ loadModuleHome tms
176-- | Load a module, quickly. Input doesn't need to be desugared.
177-- A module must be loaded before dependent modules can be typechecked.
178-- This variant of loadModuleHome will *never* cause recompilation, it just
179-- modifies the session.
184loadModuleHome tmr = modifySession $ \e ->
185 e { hsc_HPT = addToHpt (hsc_HPT e) mod mod_info }
187 ms = pm_mod_summary . tm_parsed_module . tmrModule $ tmr
188 mod_info = tmrModInfo tmr
193-- | GhcMonad function to chase imports of a module given as a StringBuffer. Returns given module's
194-- name and its imports.
195getImportsParsed :: DynFlags ->
197 Either [FileDiagnostic] (GHC.ModuleName, [(Bool, (Maybe FastString, Located GHC.ModuleName))])
198getImportsParsed dflags (L loc parsed) = do
199 let modName = maybe (GHC.mkModuleName "Main") GHC.unLoc $ GHC.hsmodName parsed
201 -- most of these corner cases are also present in https://hackage.haskell.org/package/ghc-8.6.1/docs/src/HeaderInfo.html#getImports
202 -- but we want to avoid parsing the module twice
203 let implicit_prelude = xopt GHC.ImplicitPrelude dflags
204 implicit_imports = Hdr.mkPrelImports modName loc implicit_prelude $ GHC.hsmodImports parsed
206 -- filter out imports that come from packages
207 return (modName, [(ideclSource i, (fmap sl_fs $ ideclPkgQual i, ideclName i))
208 | i <- map GHC.unLoc $ implicit_imports ++ GHC.hsmodImports parsed
209 , GHC.moduleNameString (GHC.unLoc $ ideclName i) /= "GHC.Prim"
213-- | Produce a module summary from a StringBuffer.
214getModSummaryFromBuffer
220 -> ExceptT [FileDiagnostic] m ModSummary
221getModSummaryFromBuffer fp contents dflags parsed = do
222 (modName, imports) <- liftEither $ getImportsParsed dflags parsed
224 let modLoc = ModLocation
225 { ml_hs_file = Just fp
226 , ml_hi_file = derivedFile "hi"
227 , ml_obj_file = derivedFile "o"
229 , ml_hie_file = derivedFile "hie"
231 -- This does not consider the dflags configuration
232 -- (-osuf and -hisuf, object and hi dir.s).
233 -- However, we anyway don't want to generate them.
235 InstalledUnitId unitId = thisInstalledUnitId dflags
237 { ms_mod = mkModule (fsToUnitId unitId) modName
238 , ms_location = modLoc
239 , ms_hs_date = error "Rules should not depend on ms_hs_date"
240 -- When we are working with a virtual file we do not have a file date.
241 -- To avoid silent issues where something is not processed because the date
242 -- has not changed, we make sure that things blow up if they depend on the
244 , ms_textual_imps = [imp | (False, imp) <- imports]
246 , ms_hspp_opts = dflags
247 , ms_hspp_buf = Just contents
250 , ms_hsc_src = sourceType
251 , ms_obj_date = Nothing
252 , ms_iface_date = Nothing
254 , ms_hie_date = Nothing
256 , ms_srcimps = [imp | (True, imp) <- imports]
257 , ms_parsed_mod = Nothing
260 (sourceType, derivedFile) =
261 let (stem, ext) = splitExtension fp in
262 if "-boot" `isSuffixOf` ext
263 then (HsBootFile, \newExt -> stem <.> newExt ++ "-boot")
264 else (HsSrcFile , \newExt -> stem <.> newExt)
267-- | Given a buffer, flags, file path and module summary, produce a
268-- parsed module (or errors) and any parse warnings.
271 => (GHC.ParsedSource -> ([(GHC.SrcSpan, String)], GHC.ParsedSource))
272 -> FilePath -- ^ the filename (for source locations)
273 -> Maybe SB.StringBuffer -- ^ Haskell module source text (full Unicode is supported)
274 -> ExceptT [FileDiagnostic] m ([FileDiagnostic], ParsedModule)
275parseFileContents customPreprocessor filename mbContents = do
276 (contents, dflags) <- preprocessor filename mbContents
277 let loc = mkRealSrcLoc (mkFastString filename) 1 1
278 case unP Parser.parseModule (mkPState dflags contents loc) of
279 PFailed _ locErr msgErr ->
280 throwE $ diagFromErrMsg "parser" dflags $ mkPlainErrMsg dflags locErr msgErr
281 POk pst rdr_module ->
282 let hpm_annotations =
283 (Map.fromListWith (++) $ annotations pst,
284 Map.fromList ((noSrcSpan,comment_q pst)
285 :annotations_comments pst))
286 (warns, errs) = getMessages pst dflags
289 -- Just because we got a `POk`, it doesn't mean there
290 -- weren't errors! To clarify, the GHC parser
291 -- distinguishes between fatal and non-fatal
292 -- errors. Non-fatal errors are the sort that don't
293 -- prevent parsing from continuing (that is, a parse
294 -- tree can still be produced despite the error so that
295 -- further errors/warnings can be collected). Fatal
296 -- errors are those from which a parse tree just can't
299 throwE $ diagFromErrMsgs "parser" dflags $ snd $ getMessages pst dflags
301 -- Ok, we got here. It's safe to continue.
302 let (errs, parsed) = customPreprocessor rdr_module
303 unless (null errs) $ throwE $ diagFromStrings "parser" errs
304 ms <- getModSummaryFromBuffer filename contents dflags parsed
308 , pm_parsed_source = parsed
309 , pm_extra_src_files=[] -- src imports not allowed
310 , pm_annotations = hpm_annotations
312 warnings = diagFromErrMsgs "parser" dflags warns
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