on master help sign in
open-sourcing daml
commitb71858a059fd95b1dd70f15760fc85cdd7b602a4 authorDigital Asset GmbH <support@digitalasset.com> dateThu 4 Apr 2019, 09:33 (8 years ago)
20 files changed+3353
BUILD.bazel+41 src/Development/IDE/Functions/AtPoint.hs+99 src/Development/IDE/Functions/Compile.hs+506 src/Development/IDE/Functions/DependencyInformation.hs+209 src/Development/IDE/Functions/Documentation.hs+86 src/Development/IDE/Functions/FindImports.hs+147 src/Development/IDE/Functions/GHCError.hs+160 src/Development/IDE/Functions/SpanInfo.hs+134 src/Development/IDE/Functions/Warnings.hs+43 src/Development/IDE/Logger.hs+22 src/Development/IDE/State/FileStore.hs+149 src/Development/IDE/State/RuleTypes.hs+186 src/Development/IDE/State/Rules.hs+358 src/Development/IDE/State/Service.hs+124 src/Development/IDE/State/Shake.hs+389 src/Development/IDE/Types/Diagnostics.hs+139 src/Development/IDE/Types/LSP.hs+60 src/Development/IDE/Types/Location.hs+142 src/Development/IDE/Types/SpanInfo.hs+63 src/Development/IDE/UtilGHC.hs+296
BUILD.bazelnew file+41
@@ -0,0 +1,41 @@
1+# Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+# SPDX-License-Identifier: Apache-2.0
3+
4+load("//bazel_tools:haskell.bzl", "da_haskell_library")
5+
6+da_haskell_library(
7+ name = "haskell-ide-core"
8+ , srcs = glob(["src/**/*.hs"])
9+ , src_strip_prefix = "src"
10+ , deps = [
11+ "//libs-haskell/prettyprinter-syntax",
12+ ]
13+ , hazel_deps = [
14+ "aeson",
15+ "base",
16+ "binary",
17+ "bytestring",
18+ "containers",
19+ "deepseq",
20+ "directory",
21+ "either",
22+ "extra",
23+ "filepath",
24+ "ghc-lib",
25+ "ghc-lib-parser",
26+ "hashable",
27+ "mtl",
28+ "pretty",
29+ "safe-exceptions",
30+ "shake",
31+ "stm",
32+ "syb",
33+ "text",
34+ "time",
35+ "transformers",
36+ "uniplate",
37+ "unordered-containers",
38+ "uri-encode",
39+ ]
40+ , visibility = ["//visibility:public"]
41+)
src/Development/IDE/Functions/AtPoint.hsnew file+99
@@ -0,0 +1,99 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+{-# LANGUAGE OverloadedStrings #-}
5+-- | Gives information about symbols at a given point in DAML files.
6+-- These are all pure functions that should execute quickly.
7+module Development.IDE.Functions.AtPoint (
8+ atPoint
9+ , gotoDefinition
10+ ) where
11+
12+import Development.IDE.Functions.Documentation
13+import Development.IDE.Functions.GHCError
14+
15+-- DAML compiler and infrastructure
16+import Development.IDE.Types.Diagnostics
17+import Development.IDE.Types.LSP
18+import Development.IDE.Types.SpanInfo as SpanInfo
19+
20+-- GHC API imports
21+import "ghc-lib" GHC
22+import "ghc-lib-parser" DynFlags
23+import "ghc-lib-parser" Outputable hiding ((<>))
24+import "ghc-lib-parser" Name
25+
26+import Data.Maybe
27+import Data.List
28+import qualified Data.Text as T
29+
30+-- | Locate the definition of the name at a given position.
31+gotoDefinition
32+ :: [SpanInfo]
33+ -> Position
34+ -> Maybe Location
35+gotoDefinition srcSpans pos =
36+ listToMaybe $ locationsAtPoint pos srcSpans
37+
38+-- | Synopsis for the name at a given position.
39+atPoint
40+ :: [TypecheckedModule]
41+ -> [SpanInfo]
42+ -> Position
43+ -> Maybe (Maybe Range, [HoverText])
44+atPoint tcs srcSpans pos = do
45+ SpanInfo{..} <- listToMaybe $ orderSpans $ spansAtPoint pos srcSpans
46+ ty <- spaninfoType
47+ let mbName = getNameM spaninfoSource
48+ mbDefinedAt = HoverHeading . ("Defined " <>) . T.pack . showSDocUnsafe . pprNameDefnLoc <$> mbName
49+ mbDocs = fmap (\name -> getDocumentation name tcs) mbName
50+ docInfo = maybe [] (map HoverMarkdown . docHeaders) mbDocs
51+ range = Range
52+ (Position spaninfoStartLine spaninfoStartCol)
53+ (Position spaninfoEndLine spaninfoEndCol)
54+ typeSig = HoverDamlCode $ case mbName of
55+ Nothing -> ": " <> showName ty
56+ Just name ->
57+ let modulePrefix = maybe "" (<> ".") (getModuleNameAsText name)
58+ in modulePrefix <> showName name <> "\n : " <> showName ty
59+ hoverInfo = docInfo <> [typeSig] <> maybeToList mbDefinedAt
60+ return (Just range, hoverInfo)
61+ where
62+ -- NOTE(RJR): This is a bit hacky.
63+ -- We don't want to show the user type signatures generated from Eq and Show
64+ -- instances, as they do not appear in the source program.
65+ -- However the user could have written an `==` or `show` function directly,
66+ -- in which case we still want to show information for that.
67+ -- Hence we just move such information later in the list of spans.
68+ orderSpans :: [SpanInfo] -> [SpanInfo]
69+ orderSpans = uncurry (++) . partition (not . isTypeclassDeclSpan)
70+ isTypeclassDeclSpan :: SpanInfo -> Bool
71+ isTypeclassDeclSpan spanInfo =
72+ case getNameM (spaninfoSource spanInfo) of
73+ Just name -> any (`isInfixOf` show name) ["==", "showsPrec"]
74+ Nothing -> False
75+
76+locationsAtPoint :: Position -> [SpanInfo] -> [Location]
77+locationsAtPoint pos = map srcSpanToLocation
78+ . mapMaybe (SpanInfo.getSrcSpan . spaninfoSource)
79+ . spansAtPoint pos
80+
81+spansAtPoint :: Position -> [SpanInfo] -> [SpanInfo]
82+spansAtPoint pos = filter atp where
83+ line = positionLine pos + 1
84+ cha = positionCharacter pos + 1
85+ atp SpanInfo{..} = spaninfoStartLine <= line
86+ && spaninfoEndLine >= line
87+ && spaninfoStartCol <= cha
88+ && spaninfoEndCol >= cha
89+
90+showName :: Outputable a => a -> T.Text
91+showName = T.pack . prettyprint
92+ where
93+ prettyprint x = renderWithStyle unsafeGlobalDynFlags (ppr x) style
94+ style = mkUserStyle unsafeGlobalDynFlags neverQualify AllTheWay
95+
96+getModuleNameAsText :: Name -> Maybe T.Text
97+getModuleNameAsText n = do
98+ m <- nameModule_maybe n
99+ return . T.pack . moduleNameString $ moduleName m
src/Development/IDE/Functions/Compile.hsnew file+506
@@ -0,0 +1,506 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+{-# LANGUAGE OverloadedStrings #-}
5+{-# LANGUAGE RankNTypes #-}
6+
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.
9+module Development.IDE.Functions.Compile
10+ ( CompileOpts(..)
11+ , PackageState(..)
12+ , GhcModule(..)
13+ , TcModuleResult(..)
14+ , LoadPackageResult(..)
15+ , getGhcDynFlags
16+ , compileModule
17+ , getSrcSpanInfos
18+ , parseModule
19+ , typecheckModule
20+ , loadPackage
21+ , computePackageDeps
22+ , generatePackageState
23+ ) where
24+
25+import Development.IDE.Functions.Warnings
26+import Development.IDE.Types.Diagnostics
27+import qualified Development.IDE.Functions.FindImports as FindImports
28+import Development.IDE.Functions.GHCError
29+import Development.IDE.Functions.SpanInfo
30+import Development.IDE.UtilGHC
31+
32+import "ghc-lib" GHC hiding (parseModule, typecheckModule)
33+import qualified "ghc-lib-parser" Parser
34+import "ghc-lib-parser" Lexer
35+import "ghc-lib-parser" Bag
36+
37+import qualified "ghc-lib" GHC
38+import "ghc-lib-parser" Panic
39+import "ghc-lib-parser" GhcMonad
40+import "ghc-lib" GhcPlugins as GHC hiding (PackageState, fst3, (<>))
41+import qualified "ghc-lib" HeaderInfo as Hdr
42+import "ghc-lib" MkIface
43+import "ghc-lib-parser" NameCache
44+import "ghc-lib-parser" StringBuffer as SB
45+import "ghc-lib" TidyPgm
46+import "ghc-lib-parser" InstEnv
47+import "ghc-lib-parser" FamInstEnv
48+
49+import Control.DeepSeq
50+import Control.Exception as E
51+import Control.Monad
52+import qualified Control.Monad.Trans.Except as Ex
53+import Data.IORef
54+import Data.List.Extra
55+import Data.Maybe
56+import Data.Tuple.Extra
57+import qualified Data.Map.Strict as Map
58+import Data.Time
59+import Development.IDE.Types.SpanInfo
60+import GHC.Generics (Generic)
61+import System.FilePath
62+
63+-- TODO (MK) Move to a separate Options module
64+data CompileOpts = CompileOpts
65+ { optPreprocessor :: GHC.ParsedSource -> ([(GHC.SrcSpan, String)], GHC.ParsedSource)
66+ , optRunGhcSession :: forall a. Maybe ParsedModule -> PackageState -> Ghc a -> IO a
67+ -- ^ Setup a GHC session using a given package state. If a `ParsedModule` is supplied,
68+ -- the import path should be setup for that module.
69+ , optWriteIface :: Bool
70+
71+ , optMbPackageName :: Maybe String
72+
73+ , optPackageDbs :: [FilePath]
74+ , optHideAllPkgs :: Bool
75+ , optPackageImports :: [(String, [(String, String)])]
76+
77+ , optThreads :: Int
78+ , optShakeProfiling :: Maybe FilePath
79+ }
80+
81+-- | 'CoreModule' together with some additional information required for the
82+-- conversion to DAML-LF.
83+data GhcModule = GhcModule
84+ { gmPath :: Maybe FilePath
85+ , gmCore :: CoreModule
86+ }
87+ deriving (Generic, Show)
88+
89+instance NFData GhcModule
90+
91+-- | Contains the typechecked module and the OrigNameCache entry for
92+-- that module.
93+data TcModuleResult = TcModuleResult
94+ { tmrModule :: TypecheckedModule
95+ , tmrModInfo :: HomeModInfo
96+ , tmrOccEnvName :: OccEnv Name
97+ }
98+
99+-- | Contains the result of loading an interface. In particular the delta to the name cache.
100+data LoadPackageResult = LoadPackageResult
101+ { lprInstalledUnitId :: InstalledUnitId
102+ , lprModuleEnv :: ModuleEnv (OccEnv Name)
103+ , lprEps :: ExternalPackageState
104+ }
105+
106+-- | Get source span info, used for e.g. AtPoint and Goto Definition.
107+getSrcSpanInfos
108+ :: CompileOpts
109+ -> ParsedModule
110+ -> PackageState
111+ -> [(Located ModuleName, Maybe FilePath)]
112+ -> TcModuleResult
113+ -> IO [SpanInfo]
114+getSrcSpanInfos opt mod packageState imports tc =
115+ runGhcSession opt (Just mod) packageState
116+ . getSpanInfo imports
117+ $ tmrModule tc
118+
119+
120+-- | Given a string buffer, return a pre-processed @ParsedModule@.
121+parseModule
122+ :: CompileOpts
123+ -> PackageState
124+ -> FilePath
125+ -> (UTCTime, SB.StringBuffer)
126+ -> IO ([Diagnostic], Maybe ParsedModule)
127+parseModule opt@CompileOpts{..} packageState file =
128+ fmap (either (, Nothing) (second Just)) . Ex.runExceptT .
129+ -- We need packages since imports fail to resolve otherwise.
130+ runGhcSessionExcept opt Nothing packageState . parseFileContents optPreprocessor file
131+
132+computePackageDeps ::
133+ CompileOpts -> PackageState -> InstalledUnitId -> IO (Either [Diagnostic] [InstalledUnitId])
134+computePackageDeps opts packageState iuid =
135+ Ex.runExceptT $
136+ runGhcSessionExcept opts Nothing packageState $
137+ catchSrcErrors $ do
138+ dflags <- hsc_dflags <$> getSession
139+ liftIO $ depends <$> getPackage dflags iuid
140+
141+getPackage :: DynFlags -> InstalledUnitId -> IO PackageConfig
142+getPackage dflags p =
143+ case lookupInstalledPackage dflags p of
144+ Nothing -> E.throwIO $ CmdLineError (missingPackageMsg p)
145+ Just pkg -> return pkg
146+ where
147+ missingPackageMsg p = showSDoc dflags $ text "unknown package:" <+> ppr p
148+
149+-- | Typecheck a single module using the supplied dependencies and packages.
150+typecheckModule
151+ :: CompileOpts
152+ -> ParsedModule
153+ -> PackageState
154+ -> UniqSupply
155+ -> [TcModuleResult]
156+ -> [LoadPackageResult]
157+ -> ParsedModule
158+ -> IO ([Diagnostic], Maybe TcModuleResult)
159+typecheckModule opt mod packageState uniqSupply deps pkgs pm =
160+ fmap (either (, Nothing) (second Just)) $ Ex.runExceptT $
161+ runGhcSessionExcept opt (Just mod) packageState $
162+ catchSrcErrors $ do
163+ setupEnv uniqSupply deps pkgs
164+ (warnings, tcm) <- withWarnings "Typechecker" $ \tweak ->
165+ GHC.typecheckModule pm{pm_mod_summary = tweak $ pm_mod_summary pm}
166+ tcm2 <- mkTcModuleResult (WriteInterface $ optWriteIface opt) tcm
167+ return (warnings, tcm2)
168+
169+-- | Load a pkg and populate the name cache and external package state.
170+loadPackage ::
171+ CompileOpts
172+ -> PackageState
173+ -> UniqSupply
174+ -> [LoadPackageResult]
175+ -> InstalledUnitId
176+ -> IO (Either [Diagnostic] LoadPackageResult)
177+loadPackage opt packageState us lps p =
178+ Ex.runExceptT $
179+ runGhcSessionExcept opt Nothing packageState $
180+ catchSrcErrors $ do
181+ setupEnv us [] lps
182+ dflags <- hsc_dflags <$> getSession
183+ exposedMods <- liftIO $ exposedModules <$> getPackage dflags p
184+ let mods =
185+ [ Module (DefiniteUnitId (DefUnitId p)) mod
186+ | (mod, _mbParent) <- exposedMods
187+ ]
188+ forM_ mods $ \mod -> GHC.getModuleInfo mod
189+ -- this populates the namecache and external package state
190+ session <- getSession
191+ modEnv <- nsNames <$> liftIO (readIORef $ hsc_NC session)
192+ eps <- liftIO (readIORef $ hsc_EPS session)
193+ pure $ LoadPackageResult p modEnv eps
194+
195+-- | Compile a single type-checked module to a 'CoreModule' value, or
196+-- provide errors.
197+compileModule
198+ :: CompileOpts
199+ -> ParsedModule
200+ -> PackageState
201+ -> UniqSupply
202+ -> [TcModuleResult]
203+ -> [LoadPackageResult]
204+ -> TcModuleResult
205+ -> IO ([Diagnostic], Maybe GhcModule)
206+compileModule opt mod packageState uniqSupply deps pkgs tmr =
207+ fmap (either (, Nothing) (second Just)) $ Ex.runExceptT $
208+ runGhcSessionExcept opt (Just mod) packageState $
209+ catchSrcErrors $ do
210+ setupEnv uniqSupply (deps ++ [tmr]) pkgs
211+
212+ let tm = tmrModule tmr
213+ session <- getSession
214+ (warnings,desugar) <- withWarnings "Desugarer" $ \tweak -> do
215+ let pm = tm_parsed_module tm
216+ let pm' = pm{pm_mod_summary = tweak $ pm_mod_summary pm}
217+ let tm' = tm{tm_parsed_module = pm'}
218+ removeTypeableInfo . GHC.dm_core_module <$> GHC.desugarModule tm'
219+
220+ -- give variables unique OccNames
221+ (tidy, details) <- liftIO $ tidyProgram session desugar
222+
223+ let path = ml_hs_file $ ms_location $ pm_mod_summary $ tm_parsed_module tm
224+ let core = CoreModule
225+ (cg_module tidy)
226+ (md_types details)
227+ (cg_binds tidy)
228+ (mg_safe_haskell desugar)
229+
230+ return (warnings, GhcModule path core)
231+
232+-- | Evaluate a GHC session using a new environment constructed with
233+-- the supplied options.
234+runGhcSessionExcept
235+ :: CompileOpts
236+ -> Maybe ParsedModule
237+ -> PackageState
238+ -> Ex.ExceptT e Ghc a
239+ -> Ex.ExceptT e IO a
240+runGhcSessionExcept opts mbMod pkg m =
241+ Ex.ExceptT $ runGhcSession opts mbMod pkg $ Ex.runExceptT m
242+
243+
244+getGhcDynFlags :: CompileOpts -> ParsedModule -> PackageState -> IO DynFlags
245+getGhcDynFlags opts mod pkg = runGhcSession opts (Just mod) pkg getSessionDynFlags
246+
247+-- | Evaluate a GHC session using a new environment constructed with
248+-- the supplied options.
249+runGhcSession
250+ :: CompileOpts
251+ -> Maybe ParsedModule
252+ -> PackageState
253+ -> Ghc a
254+ -> IO a
255+runGhcSession CompileOpts{..} = optRunGhcSession
256+
257+-- When we make a fresh GHC environment, the OrigNameCache comes already partially
258+-- populated. So to be safe, we simply extend this one.
259+mkNameCache :: GhcMonad m => UniqSupply -> [TcModuleResult] -> [LoadPackageResult] -> m NameCache
260+mkNameCache uniqSupply tms pkgs = do
261+ session <- getSession
262+ onc <- nsNames <$> liftIO (readIORef $ hsc_NC session)
263+ return NameCache
264+ { nsUniqs = uniqSupply
265+ , nsNames = extendOrigNameCache' onc tms pkgs
266+ }
267+
268+-- | Extend the name cache with the names from the typechecked home modules and the loaded packages.
269+-- If we have two environments containing the same module we take the later one. We do this because
270+-- the name cache comes prepopulated with modules from daml-prim and we overwrite those with our own
271+-- daml-prim package.
272+extendOrigNameCache' :: OrigNameCache -> [TcModuleResult] -> [LoadPackageResult] -> OrigNameCache
273+extendOrigNameCache' onc tms pkgs = foldl (plusModuleEnv_C (\_x y -> y)) onc modEnvs
274+ where
275+ modEnvs =
276+ mkModuleEnv
277+ [(ms_mod $ tcModSummary $ tmrModule tm, tmrOccEnvName tm) | tm <- tms] :
278+ [lprModuleEnv lm | lm <- pkgs]
279+
280+newtype WriteInterface = WriteInterface Bool
281+
282+mkTcModuleResult
283+ :: GhcMonad m
284+ => WriteInterface
285+ -> TypecheckedModule
286+ -> m TcModuleResult
287+mkTcModuleResult (WriteInterface writeIface) tcm = do
288+ session <- getSession
289+ nc <- liftIO $ readIORef (hsc_NC session)
290+ (iface,_) <- liftIO $ mkIfaceTc session Nothing Sf_None details tcGblEnv
291+ when writeIface $
292+ liftIO $ do
293+ writeIfaceFile (hsc_dflags session) (replaceExtension (file tcm) ".hi") iface
294+ let mod_info = HomeModInfo iface details Nothing
295+ origNc = nsNames nc
296+ case lookupModuleEnv origNc (tcmModule tcm) of
297+ Nothing -> panic err
298+ Just occ -> return $ TcModuleResult tcm mod_info occ
299+ where
300+ file = ms_hspp_file . tcModSummary
301+ tcmModule = ms_mod . tcModSummary
302+ (tcGblEnv, details) = tm_internals_ tcm
303+ err = "Internal error : module not found in NameCache :" <>
304+ moduleNameString (moduleName $ tcmModule tcm)
305+
306+tcModSummary :: TypecheckedModule -> ModSummary
307+tcModSummary = pm_mod_summary . tm_parsed_module
308+
309+-- | Setup the environment that GHC needs according to our
310+-- best understanding (!)
311+setupEnv :: GhcMonad m => UniqSupply -> [TcModuleResult] -> [LoadPackageResult] -> m ()
312+setupEnv uniqSupply tms lps = do
313+ session <- getSession
314+
315+ let mss = map (pm_mod_summary . tm_parsed_module . tmrModule) tms
316+
317+ -- set the target and module graph in the session
318+ let graph = mkModuleGraph mss
319+ setSession session { hsc_mod_graph = graph }
320+
321+ -- Make modules available for others that import them,
322+ -- by putting them in the finder cache.
323+ let ims = map (InstalledModule (thisInstalledUnitId $ hsc_dflags session) . moduleName . ms_mod) mss
324+ ifrs = zipWith (\ms -> InstalledFound (ms_location ms)) mss ims
325+ liftIO $ modifyIORef (hsc_FC session) $ \fc ->
326+ foldl' (\fc (im, ifr) -> GHC.extendInstalledModuleEnv fc im ifr) fc
327+ $ zip ims ifrs
328+
329+ -- construct a new NameCache
330+ nc' <- mkNameCache uniqSupply tms lps
331+ -- update the name cache
332+ liftIO $ modifyIORef (hsc_NC session) $ const nc'
333+ -- update the external package state
334+ liftIO $ modifyIORef (hsc_EPS session) (updateEps lps)
335+ -- load dependent modules, which must be in topological order.
336+ mapM_ loadModuleHome tms
337+
338+-- | Update the external package state given the loaded package results.
339+updateEps :: [LoadPackageResult] -> ExternalPackageState -> ExternalPackageState
340+updateEps lps eps =
341+ eps
342+ { eps_inst_env = newInstEnv
343+ , eps_PIT = newPIT
344+ , eps_PTE = newPTE
345+ , eps_rule_base = newRuleBase
346+ , eps_complete_matches = newCompleteMatches
347+ , eps_fam_inst_env = newFamInst
348+ , eps_ann_env = newAnnEnv
349+ , eps_mod_fam_inst_env = newModFamInstEnv
350+ }
351+ where
352+ (newInstEnv, (newPIT, (newPTE, (newRuleBase, (newCompleteMatches, (newFamInst, (newAnnEnv, newModFamInstEnv))))))) =
353+ foldl
354+ (\(instEnv, (pit, (pte, (ruleBase, (completeMatches, (famInst, (annEnv, modFamInstEnv))))))) ->
355+ (instEnv `extendInstEnvList0`) ***
356+ (pit `plusModuleEnv`) ***
357+ (pte `plusTypeEnv`) ***
358+ (ruleBase `unionRuleBase`) ***
359+ (completeMatches `extendCompleteMatchMap`) ***
360+ (famInst `extendFamInstEnvList`) ***
361+ (annEnv `plusAnnEnv`) *** (modFamInstEnv `plusModuleEnv`))
362+ ( emptyInstEnv
363+ , ( emptyPackageIfaceTable
364+ , ( emptyTypeEnv
365+ , ( emptyRuleBase
366+ , (emptyUFM, (emptyFamInstEnv, (emptyAnnEnv, emptyModuleEnv)))))))
367+ [ ( instEnvElts $ eps_inst_env e
368+ , ( eps_PIT e
369+ , ( eps_PTE e
370+ , ( eps_rule_base e
371+ , ( concat $ eltsUFM $ eps_complete_matches e
372+ , ( famInstEnvElts $ eps_fam_inst_env e
373+ , (eps_ann_env e, eps_mod_fam_inst_env e)))))))
374+ | p <- lps
375+ , let e = lprEps p
376+ ]
377+
378+ -- TODO (drsk): This is necessary because the EPS that we store include the results of
379+ -- previously loaded packages and we end up adding instances several times to the environment.
380+ -- It would be better to have pure delta stored in the LoadPackageResult, such that it
381+ -- contains only identities/instances/names coming from that specific loaded package, but I
382+ -- failed so far in computing the correct delta.
383+ extendInstEnvList0 instEnv0 clsInsts =
384+ extendInstEnvList emptyInstEnv $
385+ nubOrdOn is_dfun_name $ instEnvElts instEnv0 ++ clsInsts
386+
387+-- | Load a module, quickly. Input doesn't need to be desugared.
388+-- A module must be loaded before dependent modules can be typechecked.
389+-- This variant of loadModuleHome will *never* cause recompilation, it just
390+-- modifies the session.
391+loadModuleHome
392+ :: (GhcMonad m)
393+ => TcModuleResult
394+ -> m ()
395+loadModuleHome tmr = modifySession $ \e ->
396+ e { hsc_HPT = addToHpt (hsc_HPT e) mod mod_info }
397+ where
398+ ms = pm_mod_summary . tm_parsed_module . tmrModule $ tmr
399+ mod_info = tmrModInfo tmr
400+ mod = ms_mod_name ms
401+
402+-- | Produce a module summary from a StringBuffer.
403+getModSummaryFromBuffer
404+ :: GhcMonad m
405+ => FilePath
406+ -> (SB.StringBuffer, UTCTime)
407+ -> DynFlags
408+ -> GHC.ParsedSource
409+ -> Ex.ExceptT [Diagnostic] m ModSummary
410+getModSummaryFromBuffer fp (contents, fileDate) dflags parsed = do
411+ (modName, imports) <- FindImports.getImportsParsed dflags parsed
412+
413+ let modLoc = ModLocation
414+ { ml_hs_file = Just fp
415+ , ml_hi_file = replaceExtension fp "hi"
416+ , ml_obj_file = replaceExtension fp "o"
417+ , ml_hie_file = replaceExtension fp "hie"
418+ -- This does not consider the dflags configuration
419+ -- (-osuf and -hisuf, object and hi dir.s).
420+ -- However, we anyway don't want to generate them.
421+ }
422+ InstalledUnitId unitId = thisInstalledUnitId dflags
423+ return $ ModSummary
424+ { ms_mod = mkModule (fsToUnitId unitId) modName
425+ , ms_location = modLoc
426+ , ms_hs_date = fileDate
427+ , ms_textual_imps = imports
428+ , ms_hspp_file = fp
429+ , ms_hspp_opts = dflags
430+ , ms_hspp_buf = Just contents
431+
432+ -- defaults:
433+ , ms_hsc_src = HsSrcFile
434+ , ms_obj_date = Nothing
435+ , ms_iface_date = Nothing
436+ , ms_hie_date = Nothing
437+ , ms_srcimps = [] -- source imports are not allowed
438+ , ms_parsed_mod = Nothing
439+ }
440+
441+-- | Given a buffer, flags, file path and module summary, produce a
442+-- parsed module (or errors) and any parse warnings.
443+parseFileContents
444+ :: GhcMonad m
445+ => (GHC.ParsedSource -> ([(GHC.SrcSpan, String)], GHC.ParsedSource))
446+ -> FilePath -- ^ the filename (for source locations)
447+ -> (UTCTime, SB.StringBuffer) -- ^ Haskell module source text (full Unicode is supported)
448+ -> Ex.ExceptT [Diagnostic] m ([Diagnostic], ParsedModule)
449+parseFileContents preprocessor filename (time, contents) = do
450+ let loc = mkRealSrcLoc (mkFastString filename) 1 1
451+ dflags <- parsePragmasIntoDynFlags filename contents
452+ case unP Parser.parseModule (mkPState dflags contents loc) of
453+ PFailed s ->
454+ Ex.throwE $ toDiagnostics dflags $ snd $ getMessages s dflags
455+ POk pst rdr_module ->
456+ let hpm_annotations =
457+ (Map.fromListWith (++) $ annotations pst,
458+ Map.fromList ((noSrcSpan,comment_q pst)
459+ :annotations_comments pst))
460+ (warns,_) = getMessages pst dflags
461+ in
462+ do
463+ let (errs, parsed) = preprocessor rdr_module
464+ unless (null errs) $ Ex.throwE $ mkErrors dflags errs
465+ ms <- getModSummaryFromBuffer filename (contents, time) dflags parsed
466+ let pm =
467+ ParsedModule {
468+ pm_mod_summary = ms
469+ , pm_parsed_source = parsed
470+ , pm_extra_src_files=[] -- src imports not allowed
471+ , pm_annotations = hpm_annotations
472+ }
473+ warnings = mapMaybe (mkDiag dflags "Parser") $ bagToList warns
474+ pure (warnings, pm)
475+
476+
477+-- | This reads the pragma information directly from the provided buffer.
478+parsePragmasIntoDynFlags
479+ :: GhcMonad m
480+ => FilePath
481+ -> SB.StringBuffer
482+ -> Ex.ExceptT [Diagnostic] m DynFlags
483+parsePragmasIntoDynFlags fp contents = catchSrcErrors $ do
484+ dflags0 <- getSessionDynFlags
485+ let opts = Hdr.getOptions dflags0 contents fp
486+ (dflags, _, _) <- parseDynamicFilePragma dflags0 opts
487+ return dflags
488+
489+generatePackageState :: [FilePath] -> Bool -> [(String, [(String, String)])] -> IO PackageState
490+generatePackageState paths hideAllPkgs pkgImports = do
491+ let dflags = setPackageImports hideAllPkgs pkgImports $ setPackageDbs paths (defaultDynFlags fakeSettings fakeLlvmConfig)
492+ (newDynFlags, _) <- initPackages dflags
493+ pure $ PackageState (pkgDatabase newDynFlags) (pkgState newDynFlags) (thisUnitIdInsts_ newDynFlags)
494+
495+-- | Run something in a Ghc monad and catch the errors (SourceErrors and
496+-- compiler-internal exceptions like Panic or InstallationError).
497+catchSrcErrors :: GhcMonad m => m a -> Ex.ExceptT [Diagnostic] m a
498+catchSrcErrors ghcM = do
499+ dflags <- getDynFlags
500+ Ex.ExceptT $
501+ handleGhcException (ghcExceptionToDiagnostics dflags) $
502+ handleSourceError (sourceErrorToDiagnostics dflags) $
503+ Right <$> ghcM
504+ where
505+ ghcExceptionToDiagnostics dflags = return . Left . mkErrorsGhcException dflags
506+ sourceErrorToDiagnostics dflags = return . Left . toDiagnostics dflags . srcErrorMessages
src/Development/IDE/Functions/DependencyInformation.hsnew file+209
@@ -0,0 +1,209 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+module Development.IDE.Functions.DependencyInformation
5+ ( DependencyInformation(..)
6+ , RawDependencyInformation(..)
7+ , NodeError(..)
8+ , ModuleParseError(..)
9+ , TransitiveDependencies(..)
10+ , processDependencyInformation
11+ , transitiveDeps
12+ ) where
13+
14+import Control.DeepSeq
15+import Data.Bifunctor
16+import Data.Either
17+import Data.Foldable
18+import Data.Graph
19+import Data.List.NonEmpty (NonEmpty(..), nonEmpty)
20+import qualified Data.List.NonEmpty as NonEmpty
21+import Data.Map (Map)
22+import qualified Data.Map.Strict as MS
23+import qualified Data.Map.Lazy as ML
24+import Data.Maybe
25+import Data.Set (Set)
26+import qualified Data.Set as Set
27+import Data.Tuple.Extra (fst3)
28+import GHC.Generics (Generic)
29+
30+import Development.IDE.Types.Diagnostics
31+import Development.IDE.UtilGHC ()
32+
33+import "ghc-lib" GHC
34+import "ghc-lib-parser" Module
35+
36+-- | Unprocessed results that we get from following all imports recursively starting from a module.
37+data RawDependencyInformation = RawDependencyInformation
38+ { moduleDependencies :: Map FilePath (Either ModuleParseError [(Located ModuleName, Maybe FilePath)])
39+ , pkgDependencies :: Map FilePath (Set InstalledUnitId)
40+ -- ^ Transitive dependencies on pkgs of this file, i.e. immidiate package dependencies and the
41+ -- transitive package dependencies of those packages.
42+ }
43+
44+data DependencyInformation =
45+ DependencyInformation
46+ { depErrorNodes :: Map FilePath (NonEmpty NodeError)
47+ -- ^ Nodes that cannot be processed correctly.
48+ , depModuleDeps :: Map FilePath (Set FilePath)
49+ -- ^ For a non-error node, this contains the set of module immediate dependencies
50+ -- in the same package.
51+ , depPkgDeps :: Map FilePath (Set InstalledUnitId)
52+ -- ^ For a non-error node, this contains the set of immediate pkg deps.
53+ } deriving (Show, Generic)
54+
55+instance NFData DependencyInformation
56+
57+-- | This does not contain the actual parse error as that is already reported by GetParsedModule.
58+data ModuleParseError = ModuleParseError
59+ deriving (Show, Generic)
60+
61+instance NFData ModuleParseError
62+
63+-- | Error when trying to locate a module.
64+data LocateError = LocateError [Diagnostic]
65+ deriving (Eq, Show, Generic)
66+
67+instance NFData LocateError
68+
69+-- | An error attached to a node in the dependency graph.
70+data NodeError
71+ = PartOfCycle (Located ModuleName) [FilePath]
72+ -- ^ This module is part of an import cycle. The module name corresponds
73+ -- to the import that enters the cycle starting from this module.
74+ -- The list of filepaths represents the elements
75+ -- in the cycle in unspecified order.
76+ | FailedToLocateImport (Located ModuleName)
77+ -- ^ This module has an import that couldn’t be located.
78+ | ParseError ModuleParseError
79+ | ParentOfErrorNode (Located ModuleName)
80+ -- ^ This module is the parent of a module that cannot be
81+ -- processed (either it cannot be parsed, is part of a cycle
82+ -- or the parent of another error node).
83+ deriving (Show, Generic)
84+
85+instance NFData NodeError where
86+ rnf (PartOfCycle m fs) = m `seq` rnf fs
87+ rnf (FailedToLocateImport m) = m `seq` ()
88+ rnf (ParseError e) = rnf e
89+ rnf (ParentOfErrorNode m) = m `seq` ()
90+
91+-- | A processed node in the dependency graph. If there was any error
92+-- during processing the node or any of its dependencies, this is an
93+-- `ErrorNode`. Otherwise it is a `SuccessNode`.
94+data NodeResult
95+ = ErrorNode (NonEmpty NodeError)
96+ | SuccessNode [(Located ModuleName, FilePath)]
97+ deriving Show
98+
99+partitionNodeResults :: [(a, NodeResult)] -> ([(a, NonEmpty NodeError)], [(a, [(Located ModuleName, FilePath)])])
100+partitionNodeResults = partitionEithers . map f
101+ where f (a, ErrorNode errs) = Left (a, errs)
102+ f (a, SuccessNode imps) = Right (a, imps)
103+
104+instance Semigroup NodeResult where
105+ ErrorNode errs <> ErrorNode errs' = ErrorNode (errs <> errs')
106+ ErrorNode errs <> SuccessNode _ = ErrorNode errs
107+ SuccessNode _ <> ErrorNode errs = ErrorNode errs
108+ SuccessNode a <> SuccessNode _ = SuccessNode a
109+
110+processDependencyInformation :: RawDependencyInformation -> DependencyInformation
111+processDependencyInformation rawResults =
112+ DependencyInformation
113+ { depErrorNodes = MS.mapMaybe errorNode resultGraph
114+ , depModuleDeps = moduleDeps
115+ , depPkgDeps = pkgDependencies rawResults
116+ }
117+ where resultGraph = buildResultGraph rawResults
118+ successEdges :: [(FilePath, FilePath, [FilePath])]
119+ successEdges = map (\(k,ks) -> (k,k,ks)) $ MS.toList $
120+ MS.map (map snd) $ MS.mapMaybe successNode resultGraph
121+ moduleDeps =
122+ MS.fromList $ map (\(_, v, vs) -> (v, Set.fromList vs)) successEdges
123+ errorNode (ErrorNode errs) = Just errs
124+ errorNode _ = Nothing
125+ successNode (SuccessNode fs) = Just fs
126+ successNode _ = Nothing
127+
128+-- | Given a dependency graph, buildResultGraph detects and propagates errors in that graph as follows:
129+-- 1. Mark each node that is part of an import cycle as an error node.
130+-- 2. Mark each node that has a parse error as an error node.
131+-- 3. Mark each node whose immediate children could not be located as an error.
132+-- 4. Recursively propagate errors to parents if they are not already error nodes.
133+buildResultGraph :: RawDependencyInformation -> Map FilePath NodeResult
134+buildResultGraph g = propagatedErrors
135+ where
136+ sccs = stronglyConnComp (graphEdges g)
137+ (_, cycles) = partitionSCC sccs
138+ cycleErrors :: Map FilePath NodeResult
139+ cycleErrors = MS.unionsWith (<>) $ map errorsForCycle cycles
140+ errorsForCycle :: [FilePath] -> Map FilePath NodeResult
141+ errorsForCycle files =
142+ MS.fromListWith (<>) (concatMap (cycleErrorsForFile files) files)
143+ cycleErrorsForFile :: [FilePath] -> FilePath -> [(FilePath,NodeResult)]
144+ cycleErrorsForFile cycle f =
145+ let entryPoints = mapMaybe (findImport f) cycle
146+ in map (\imp -> (f, ErrorNode (PartOfCycle imp cycle :| []))) entryPoints
147+ otherErrors = MS.map otherErrorsForFile (moduleDependencies g)
148+ otherErrorsForFile :: Either ModuleParseError [(Located ModuleName, Maybe FilePath)] -> NodeResult
149+ otherErrorsForFile (Left err) = ErrorNode (ParseError err :| [])
150+ otherErrorsForFile (Right imports) =
151+ let toEither (imp, Nothing) = Left imp
152+ toEither (imp, Just path) = Right (imp, path)
153+ (errs, imports') = partitionEithers (map toEither imports)
154+ in case nonEmpty errs of
155+ Nothing -> SuccessNode imports'
156+ Just errs' -> ErrorNode (NonEmpty.map FailedToLocateImport errs')
157+
158+ unpropagatedErrors = MS.unionWith (<>) cycleErrors otherErrors
159+ -- The recursion here is fine since we use a lazy map and
160+ -- we only recurse on SuccessNodes. In particular, we do not recurse
161+ -- on nodes that are part of a cycle as they are already marked as
162+ -- error nodes.
163+ propagatedErrors =
164+ ML.map propagate unpropagatedErrors
165+ propagate :: NodeResult -> NodeResult
166+ propagate n@(ErrorNode _) = n
167+ propagate n@(SuccessNode imps) =
168+ let results = map (\(imp, dep) -> (imp, propagatedErrors MS.! dep)) imps
169+ (errs, _) = partitionNodeResults results
170+ in case nonEmpty errs of
171+ Nothing -> n
172+ Just errs' -> ErrorNode (NonEmpty.map (ParentOfErrorNode . fst) errs')
173+ findImport :: FilePath -> FilePath -> Maybe (Located ModuleName)
174+ findImport file importedFile =
175+ case moduleDependencies g MS.! file of
176+ Left _ -> error "Tried to call findImport on a module with a parse error"
177+ Right imports ->
178+ fmap fst $ find (\(_, resolvedImp) -> resolvedImp == Just importedFile) imports
179+
180+graphEdges :: RawDependencyInformation -> [(FilePath, FilePath, [FilePath])]
181+graphEdges g =
182+ map (\(k, ks) -> (k, k, ks)) $ MS.toList $ MS.map deps $ moduleDependencies g
183+ where deps :: Either e [(i, Maybe FilePath)] -> [FilePath]
184+ deps (Left _) = []
185+ deps (Right imports) = mapMaybe snd imports
186+
187+partitionSCC :: [SCC a] -> ([a], [[a]])
188+partitionSCC (CyclicSCC xs:rest) = second (xs:) $ partitionSCC rest
189+partitionSCC (AcyclicSCC x:rest) = first (x:) $ partitionSCC rest
190+partitionSCC [] = ([], [])
191+
192+transitiveDeps :: DependencyInformation -> FilePath -> Maybe TransitiveDependencies
193+transitiveDeps DependencyInformation{..} f = do
194+ reachableVs <- Set.delete f . Set.fromList . map (fst3 . fromVertex) . reachable g <$> toVertex f
195+ let transitiveModuleDeps = filter (\v -> v `Set.member` reachableVs) $ map (fst3 . fromVertex) vs
196+ let transitivePkgDeps = Set.toList $ foldMap (\f -> MS.findWithDefault Set.empty f depPkgDeps) (f : transitiveModuleDeps)
197+ pure $ TransitiveDependencies {..}
198+ where (g, fromVertex, toVertex) = graphFromEdges (map (\(f, fs) -> (f, f, Set.toList fs)) $ MS.toList depModuleDeps)
199+ vs = topSort g
200+
201+data TransitiveDependencies = TransitiveDependencies
202+ { transitiveModuleDeps :: [FilePath]
203+ -- ^ Transitive module dependencies in topological order.
204+ -- The module itself is not included.
205+ , transitivePkgDeps :: [InstalledUnitId]
206+ -- ^ Transitive pkg dependencies in unspecified order.
207+ } deriving (Eq, Show, Generic)
208+
209+instance NFData TransitiveDependencies
src/Development/IDE/Functions/Documentation.hsnew file+86
@@ -0,0 +1,86 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+module Development.IDE.Functions.Documentation (
5+ getDocumentation
6+ , docHeaders
7+ ) where
8+
9+import Control.Monad
10+import Data.List.Extra
11+import qualified Data.Map as M
12+import Data.Maybe
13+import qualified Data.Text as T
14+import Development.IDE.Functions.GHCError
15+import Development.IDE.Functions.SpanInfo
16+import Development.IDE.UtilGHC
17+import FastString
18+import GHC
19+
20+getDocumentation
21+ :: Name -- ^ The name you want documentation for.
22+ -> [TypecheckedModule] -- ^ All of the possible modules it could be defined in.
23+ -> [RealLocated AnnotationComment]
24+-- This finds any documentation between the name you want
25+-- documentation for and the one before it. This is only an
26+-- approximately correct algorithm and there are easily constructed
27+-- cases where it will be wrong (if so then usually slightly but there
28+-- may be edge cases where it is very wrong).
29+-- TODO : Build a version of GHC exactprint to extract this information
30+-- more accurately.
31+getDocumentation targetName tcs = fromMaybe [] $ do
32+ -- Find the module the target is defined in.
33+ targetNameSpan <- realSpan $ nameSrcSpan targetName
34+ tc <-
35+ listToMaybe
36+ $ filter ((==) (Just $ srcSpanFile targetNameSpan) . annotationFileName)
37+ $ reverse tcs -- TODO : Is reversing the list here really neccessary?
38+ -- Names bound by the module (we want to exclude non-"top-level"
39+ -- bindings but unfortunately we get all here).
40+ let bs = mapMaybe name_of_bind
41+ (listifyAllSpans (tm_typechecked_source tc) :: [LHsBind GhcTc])
42+ -- Sort the names' source spans.
43+ let sortedSpans = sortedNameSpans bs
44+ -- Now go ahead and extract the docs.
45+ let docs = ann tc
46+ nameInd <- elemIndex targetNameSpan sortedSpans
47+ let prevNameSpan =
48+ if nameInd >= 1
49+ then sortedSpans !! (nameInd - 1)
50+ else zeroSpan $ srcSpanFile targetNameSpan
51+ -- Annoyingly "-- |" documentation isn't annotated with a location,
52+ -- so you have to pull it out from the elements.
53+ pure
54+ $ filter (\(L target _) -> isBetween target prevNameSpan targetNameSpan)
55+ $ mapMaybe (\(L l v) -> L <$> realSpan l <*> pure v)
56+ $ join
57+ $ M.elems
58+ docs
59+ where
60+ -- Get the name bound by a binding. We only concern ourselves with
61+ -- @FunBind@ (which covers functions and variables).
62+ name_of_bind :: LHsBind GhcTc -> Maybe Name
63+ name_of_bind (L _ FunBind {fun_id}) = Just (getName (unLoc fun_id))
64+ name_of_bind _ = Nothing
65+ -- Get source spans from names, discard unhelpful spans, remove
66+ -- duplicates and sort.
67+ sortedNameSpans :: [Name] -> [RealSrcSpan]
68+ sortedNameSpans ls = nubSort (mapMaybe (realSpan . nameSrcSpan) ls)
69+ isBetween target before after = before <= target && target <= after
70+ ann = snd . pm_annotations . tm_parsed_module
71+ annotationFileName :: TypecheckedModule -> Maybe FastString
72+ annotationFileName = fmap srcSpanFile . listToMaybe . realSpans . ann
73+ realSpans :: M.Map SrcSpan [Located a] -> [RealSrcSpan]
74+ realSpans =
75+ mapMaybe (realSpan . getLoc)
76+ . join
77+ . M.elems
78+
79+-- | Shows this part of the documentation
80+docHeaders :: [RealLocated AnnotationComment]
81+ -> [T.Text]
82+docHeaders = mapMaybe (wrk . unRealSrcSpan)
83+ where
84+ wrk = \case
85+ AnnDocCommentNext s -> Just $ T.pack s
86+ _ -> Nothing
src/Development/IDE/Functions/FindImports.hsnew file+147
@@ -0,0 +1,147 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+{-# LANGUAGE OverloadedStrings #-}
5+
6+module Development.IDE.Functions.FindImports
7+ ( getImportsParsed
8+ , locateModule
9+ , Import(..)
10+ ) where
11+
12+import Development.IDE.Functions.GHCError as ErrUtils
13+
14+-- GHC imports
15+import "ghc-lib-parser" BasicTypes (StringLiteral(..))
16+import "ghc-lib-parser" DynFlags
17+import "ghc-lib-parser" FastString
18+import "ghc-lib" GHC
19+import qualified "ghc-lib" HeaderInfo as Hdr
20+import qualified "ghc-lib-parser" Module as M
21+import qualified "ghc-lib-parser" GHC.LanguageExtensions.Type as GHC
22+import "ghc-lib-parser" Packages
23+import "ghc-lib-parser" Outputable (showSDoc, ppr, pprPanic)
24+import "ghc-lib" Finder
25+
26+-- standard imports
27+import Control.Monad.Extra
28+import Control.Monad.IO.Class
29+import qualified Control.Monad.Trans.Except as Ex
30+import System.FilePath
31+
32+data Import
33+ = FileImport FilePath
34+ | PackageImport M.InstalledUnitId
35+ deriving (Show)
36+
37+-- | GhcMonad function to chase imports of a module given as a StringBuffer. Returns given module's
38+-- name and its imports.
39+getImportsParsed :: Monad m =>
40+ DynFlags ->
41+ GHC.ParsedSource ->
42+ Ex.ExceptT [Diagnostic] m
43+ (M.ModuleName, [(Maybe FastString, Located M.ModuleName)])
44+getImportsParsed dflags (L loc parsed) = do
45+ let modName = maybe (GHC.mkModuleName "Main") GHC.unLoc $ GHC.hsmodName parsed
46+
47+ -- refuse source imports
48+ let srcImports = filter (ideclSource . GHC.unLoc) $ GHC.hsmodImports parsed
49+ when (not $ null srcImports) $ Ex.throwE $
50+ concat
51+ [ mkErrors dflags [(mloc, "Illegal source import of " <> GHC.moduleNameString (GHC.unLoc $ GHC.ideclName i))]
52+ | L mloc i <- srcImports ]
53+
54+ -- most of these corner cases are also present in https://hackage.haskell.org/package/ghc-8.6.1/docs/src/HeaderInfo.html#getImports
55+ -- but we want to avoid parsing the module twice
56+ let implicit_prelude = xopt GHC.ImplicitPrelude dflags
57+ implicit_imports = Hdr.mkPrelImports modName loc implicit_prelude $ GHC.hsmodImports parsed
58+
59+ -- filter out imports that come from packages
60+ return (modName, [(fmap sl_fs $ ideclPkgQual i, ideclName i)
61+ | i <- map GHC.unLoc $ implicit_imports ++ GHC.hsmodImports parsed
62+ , GHC.moduleNameString (GHC.unLoc $ ideclName i) /= "GHC.Prim"
63+ ])
64+
65+
66+-- | locate a module in the file system. Where we go from *daml to Haskell
67+locateModuleFile :: MonadIO m
68+ => DynFlags
69+ -> (FilePath -> m Bool)
70+ -> ModuleName
71+ -> m (Maybe FilePath)
72+locateModuleFile dflags doesExist modName = do
73+ let libPaths = importPaths dflags
74+ let candidates = [ prefix </> M.moduleNameSlashes modName <.> "daml" | prefix <- libPaths ]
75+ findM doesExist candidates
76+
77+-- | locate a module in either the file system or the package database. Where we go from *daml to
78+-- Haskell
79+locateModule
80+ :: MonadIO m
81+ => DynFlags
82+ -> (FilePath -> m Bool)
83+ -> Located ModuleName
84+ -> Maybe FastString
85+ -> m (Either [Diagnostic] Import)
86+locateModule dflags doesExist modName mbPkgName = do
87+ case mbPkgName of
88+ -- if a package name is given we only go look for a package
89+ Just _pkgName -> lookupInPackageDB dflags
90+ Nothing -> do
91+ -- first try to find the module as a file. If we can't find it try to find it in the package
92+ -- database.
93+ mbFile <- locateModuleFile dflags doesExist $ unLoc modName
94+ case mbFile of
95+ Nothing -> lookupInPackageDB dflags
96+ Just file -> return $ Right $ FileImport file
97+ where
98+ lookupInPackageDB dfs =
99+ case lookupModuleWithSuggestions dfs (unLoc modName) mbPkgName of
100+ LookupFound _m pkgConfig -> return $ Right $ PackageImport $ unitId pkgConfig
101+ reason -> return $ Left $ notFoundErr dfs modName reason
102+
103+-- | Don't call this on a found module.
104+notFoundErr :: DynFlags -> Located M.ModuleName -> LookupResult -> [Diagnostic]
105+notFoundErr dfs modName reason =
106+ mkError' $ ppr' $ cannotFindModule dfs modName0 $ lookupToFindResult reason
107+ where
108+ mkError' = mkError dfs (getLoc modName)
109+ modName0 = unLoc modName
110+ ppr' = showSDoc dfs
111+ -- We convert the lookup result to a find result to reuse GHC's cannotFindMoudle pretty printer.
112+ lookupToFindResult =
113+ \case
114+ LookupFound _m _pkgConfig ->
115+ pprPanic "Impossible: called lookupToFind on found module." (ppr modName0)
116+ LookupMultiple rs -> (FoundMultiple rs)
117+ LookupHidden pkg_hiddens mod_hiddens ->
118+ (NotFound
119+ { fr_paths = []
120+ , fr_pkg = Nothing
121+ , fr_pkgs_hidden = map (moduleUnitId . fst) pkg_hiddens
122+ , fr_mods_hidden = map (moduleUnitId . fst) mod_hiddens
123+ , fr_unusables = []
124+ , fr_suggestions = []
125+ })
126+ LookupUnusable unusable ->
127+ let unusables' = map get_unusable unusable
128+ get_unusable (m, ModUnusable r) = (moduleUnitId m, r)
129+ get_unusable (_, r) =
130+ pprPanic "findLookupResult: unexpected origin" (ppr r)
131+ in (NotFound
132+ { fr_paths = []
133+ , fr_pkg = Nothing
134+ , fr_pkgs_hidden = []
135+ , fr_mods_hidden = []
136+ , fr_unusables = unusables'
137+ , fr_suggestions = []
138+ })
139+ LookupNotFound suggest ->
140+ (NotFound
141+ { fr_paths = []
142+ , fr_pkg = Nothing
143+ , fr_pkgs_hidden = []
144+ , fr_mods_hidden = []
145+ , fr_unusables = []
146+ , fr_suggestions = suggest
147+ })
src/Development/IDE/Functions/GHCError.hsnew file+160
@@ -0,0 +1,160 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+module Development.IDE.Functions.GHCError
5+ ( mkDiag
6+ , toDiagnostics
7+ , srcSpanToLocation
8+
9+ -- * Producing GHC ErrorMessages
10+ , mkErrors
11+ , mkError
12+ , mkErrorDoc
13+ , mkErrorsGhcException
14+
15+ -- * Handling errors in the GHC monad (SourceError, ErrorMessages)
16+ , Diagnostic
17+ , ErrorMessages -- included in module export below
18+ , ErrMsg
19+ , errMsgSpan
20+ , errMsgSeverity
21+ , mkPlainErrMsg
22+
23+ -- * utilities working with 'ErrMsg' and 'ErrorMessages'
24+ , zeroSpan
25+ , realSpan
26+ , noSpan
27+ ) where
28+
29+import Development.IDE.Types.Diagnostics as D
30+import qualified Data.Text as T
31+import Development.IDE.UtilGHC()
32+import qualified "ghc-lib-parser" FastString as FS
33+import "ghc-lib" GHC
34+import "ghc-lib-parser" Bag
35+import Data.Maybe
36+import "ghc-lib-parser" ErrUtils
37+import "ghc-lib-parser" SrcLoc
38+import qualified "ghc-lib-parser" Outputable as Out
39+
40+
41+
42+toDiagnostics :: DynFlags -> ErrorMessages -> [Diagnostic]
43+toDiagnostics dflags = mapMaybe (mkDiag dflags $ T.pack "Compiler") . bagToList
44+
45+
46+mkDiag :: DynFlags -> T.Text -> ErrMsg -> Maybe Diagnostic
47+mkDiag dflags src e =
48+ case toDSeverity $ errMsgSeverity e of
49+ Nothing -> Nothing
50+ Just bSeverity ->
51+ Just
52+ Diagnostic
53+ { dFilePath = srcSpanToFilename $ errMsgSpan e
54+ , dRange = srcSpanToRange $ errMsgSpan e
55+ , dSeverity = bSeverity
56+ , dSource = src
57+ , dMessage = T.pack $ Out.showSDoc dflags (ErrUtils.pprLocErrMsg e)
58+ }
59+
60+-- | Convert a GHC SrcSpan to a DAML compiler Range
61+srcSpanToRange :: SrcSpan -> Range
62+srcSpanToRange (UnhelpfulSpan _) = lRange noLocation
63+srcSpanToRange (RealSrcSpan real) = realSrcSpanToRange real
64+
65+realSrcSpanToRange :: RealSrcSpan -> Range
66+realSrcSpanToRange real =
67+ Range (Position (srcSpanStartLine real - 1) (srcSpanStartCol real - 1))
68+ (Position (srcSpanEndLine real - 1) (srcSpanEndCol real - 1))
69+
70+-- | Extract a file name from a GHC SrcSpan (use message for unhelpful ones)
71+-- FIXME This may not be an _absolute_ file name, needs fixing.
72+srcSpanToFilename :: SrcSpan -> FilePath
73+srcSpanToFilename (UnhelpfulSpan fs) = FS.unpackFS fs
74+srcSpanToFilename (RealSrcSpan real) = FS.unpackFS $ srcSpanFile real
75+
76+srcSpanToLocation :: SrcSpan -> Location
77+srcSpanToLocation src = Location (srcSpanToFilename src) (srcSpanToRange src)
78+
79+-- | Convert a GHC severity to a DAML compiler Severity. Severities below
80+-- "Warning" level are dropped (returning Nothing).
81+toDSeverity :: GHC.Severity -> Maybe D.Severity
82+toDSeverity SevOutput = Nothing
83+toDSeverity SevInteractive = Nothing
84+toDSeverity SevDump = Nothing
85+toDSeverity SevInfo = Nothing
86+toDSeverity SevWarning = Just D.Warning
87+toDSeverity SevError = Just Error
88+toDSeverity SevFatal = Just Error
89+
90+
91+-- | Produce a bag of GHC-style errors (@ErrorMessages@) from the given
92+-- (optional) locations and message strings.
93+mkErrors :: DynFlags -> [(SrcSpan, String)] -> [Diagnostic]
94+mkErrors dflags = concatMap (uncurry $ mkError dflags)
95+
96+-- | Produce a GHC-style error from a source span and a message.
97+mkError :: DynFlags -> SrcSpan -> String -> [Diagnostic]
98+mkError dflags sp = toDiagnostics dflags . Bag.listToBag . pure . mkPlainErrMsg dflags sp . Out.text
99+
100+-- | Produce a GHC-style error from a source span and a message.
101+mkErrorDoc :: DynFlags -> SrcSpan -> Out.SDoc -> [Diagnostic]
102+mkErrorDoc dflags sp = toDiagnostics dflags . Bag.listToBag . pure . mkPlainErrMsg dflags sp
103+
104+
105+-- | Produces an "unhelpful" source span with the given string.
106+noSpan :: String -> SrcSpan
107+noSpan = UnhelpfulSpan . FS.fsLit
108+
109+
110+-- | creates a span with zero length in the filename of the argument passed
111+zeroSpan :: FS.FastString -- ^ file path of span
112+ -> RealSrcSpan
113+zeroSpan file = realSrcLocSpan (mkRealSrcLoc file 1 1)
114+
115+realSpan :: SrcSpan
116+ -> Maybe RealSrcSpan
117+realSpan = \case
118+ RealSrcSpan r -> Just r
119+ UnhelpfulSpan _ -> Nothing
120+
121+
122+mkErrorsGhcException :: DynFlags -> GhcException -> [Diagnostic]
123+mkErrorsGhcException dflags exc = mkErrors dflags [(noSpan "<Internal>", showGHCE dflags exc)]
124+
125+showGHCE :: DynFlags -> GhcException -> String
126+showGHCE dflags exc = case exc of
127+ Signal n
128+ -> "Signal: " <> show n
129+
130+ Panic s
131+ -> unwords ["Compilation Issue:", s, "\n", requestReport]
132+ PprPanic s sdoc
133+ -> unlines ["Compilation Issue", s,""
134+ , Out.showSDoc dflags sdoc
135+ , requestReport ]
136+
137+ Sorry s
138+ -> "Unsupported feature: " <> s
139+ PprSorry s sdoc
140+ -> unlines ["Unsupported feature: ", s,""
141+ , Out.showSDoc dflags sdoc]
142+
143+
144+ ---------- errors below should not happen at all --------
145+ InstallationError str
146+ -> "Installation error: " <> str
147+
148+ UsageError str -- should never happen
149+ -> unlines ["Unexpected usage error", str]
150+
151+ CmdLineError str
152+ -> unlines ["Unexpected usage error", str]
153+
154+ ProgramError str
155+ -> "Program error: " <> str
156+ PprProgramError str sdoc ->
157+ unlines ["Program error:", str,""
158+ , Out.showSDoc dflags sdoc]
159+ where
160+ requestReport = "Please report this bug to the compiler authors."
src/Development/IDE/Functions/SpanInfo.hsnew file+134
@@ -0,0 +1,134 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+-- ORIGINALLY COPIED FROM https://github.com/commercialhaskell/intero
5+
6+{-# LANGUAGE RankNTypes #-}
7+
8+-- | Get information on modules, identifiers, etc.
9+
10+module Development.IDE.Functions.SpanInfo(getSpanInfo,listifyAllSpans) where
11+
12+import ConLike
13+import Control.Monad
14+import qualified CoreUtils
15+import Data.Data
16+import qualified Data.Generics
17+import Data.List
18+import Data.Maybe
19+import DataCon
20+import Desugar
21+import GHC
22+import GhcMonad
23+import FastString (mkFastString)
24+import Development.IDE.Types.SpanInfo
25+import Development.IDE.Functions.GHCError (zeroSpan)
26+import Prelude hiding (mod)
27+import TcHsSyn
28+import Var
29+
30+-- | Get ALL source spans in the module.
31+getSpanInfo :: GhcMonad m
32+ => [(Located ModuleName, Maybe FilePath)] -- ^ imports
33+ -> TypecheckedModule
34+ -> m [SpanInfo]
35+getSpanInfo mods tcm =
36+ do let tcs = tm_typechecked_source tcm
37+ bs = listifyAllSpans tcs :: [LHsBind GhcTc]
38+ es = listifyAllSpans tcs :: [LHsExpr GhcTc]
39+ ps = listifyAllSpans' tcs :: [LPat GhcTc]
40+ bts <- mapM (getTypeLHsBind tcm) bs -- binds
41+ ets <- mapM (getTypeLHsExpr tcm) es -- expressions
42+ pts <- mapM (getTypeLPat tcm) ps -- patterns
43+ let imports = importInfo mods
44+ let exprs = imports ++ concat bts ++ catMaybes (ets ++ pts)
45+ return (mapMaybe toSpanInfo (sortBy cmp exprs))
46+ where cmp (_,a,_) (_,b,_)
47+ | a `isSubspanOf` b = LT
48+ | b `isSubspanOf` a = GT
49+ | otherwise = EQ
50+
51+-- | Get the name and type of a binding.
52+getTypeLHsBind :: (GhcMonad m)
53+ => TypecheckedModule
54+ -> LHsBind GhcTc
55+ -> m [(SpanSource, SrcSpan, Maybe Type)]
56+getTypeLHsBind _ (L _spn FunBind{fun_id = pid,fun_matches = MG _ _ _typ}) =
57+ return [(Named $ getName (unLoc pid), getLoc pid, Just (varType (unLoc pid)))]
58+getTypeLHsBind _ _ = return []
59+
60+-- | Get the name and type of an expression.
61+getTypeLHsExpr :: (GhcMonad m)
62+ => TypecheckedModule
63+ -> LHsExpr GhcTc
64+ -> m (Maybe (SpanSource, SrcSpan, Maybe Type))
65+getTypeLHsExpr _ e = do
66+ hs_env <- getSession
67+ (_, mbe) <- liftIO (deSugarExpr hs_env e)
68+ return $
69+ case mbe of
70+ Just expr ->
71+ Just (getSpanSource (unLoc e), getLoc e, Just (CoreUtils.exprType expr))
72+ Nothing -> Nothing
73+ where
74+ getSpanSource :: HsExpr GhcTc -> SpanSource
75+ getSpanSource (HsVar _ (L _ i)) = Named (getName i)
76+ getSpanSource (HsConLikeOut _ (RealDataCon dc)) = Named (dataConName dc)
77+ getSpanSource RecordCon {rcon_con_name} = Named (getName rcon_con_name)
78+ getSpanSource (HsWrap _ _ xpr) = getSpanSource xpr
79+ getSpanSource (HsPar _ xpr) = getSpanSource (unLoc xpr)
80+ getSpanSource _ = NoSource
81+
82+-- | Get the name and type of a pattern.
83+getTypeLPat :: (GhcMonad m)
84+ => TypecheckedModule
85+ -> Pat GhcTc
86+ -> m (Maybe (SpanSource, SrcSpan, Maybe Type))
87+getTypeLPat _ pat =
88+ let (src, spn) = getSpanSource pat in
89+ return $ Just (src, spn, Just (hsPatType pat))
90+ where
91+ getSpanSource :: Pat GhcTc -> (SpanSource, SrcSpan)
92+ getSpanSource (VarPat _ (L spn vid)) = (Named (getName vid), spn)
93+ getSpanSource (ConPatOut (L spn (RealDataCon dc)) _ _ _ _ _ _) =
94+ (Named (dataConName dc), spn)
95+ getSpanSource _ = (NoSource, noSrcSpan)
96+
97+importInfo :: [(Located ModuleName, Maybe FilePath)]
98+ -> [(SpanSource, SrcSpan, Maybe Type)]
99+importInfo = mapMaybe (uncurry wrk) where
100+ wrk :: Located ModuleName -> Maybe FilePath -> Maybe (SpanSource, SrcSpan, Maybe Type)
101+ wrk modName = \case
102+ Nothing -> Nothing
103+ Just afp -> Just (afpToSpanSource afp, getLoc modName, Nothing)
104+
105+ -- TODO make this point to the module name
106+ afpToSpanSource :: FilePath -> SpanSource
107+ afpToSpanSource afp = Span $ RealSrcSpan $ zeroSpan $ mkFastString afp
108+
109+-- | Get ALL source spans in the source.
110+listifyAllSpans :: Typeable a
111+ => TypecheckedSource -> [Located a]
112+listifyAllSpans tcs =
113+ Data.Generics.listify p tcs
114+ where p (L spn _) = isGoodSrcSpan spn
115+-- This is a version of `listifyAllSpans` specialized on picking out
116+-- patterns. It comes about since GHC now defines `type LPat p = Pat
117+-- p` (no top-level locations).
118+listifyAllSpans' :: Typeable a
119+ => TypecheckedSource -> [Pat a]
120+listifyAllSpans' tcs = Data.Generics.listify (const True) tcs
121+
122+
123+-- | Pretty print the types into a 'SpanInfo'.
124+toSpanInfo :: (SpanSource, SrcSpan, Maybe Type) -> Maybe SpanInfo
125+toSpanInfo (name,mspan,typ) =
126+ case mspan of
127+ RealSrcSpan spn ->
128+ Just (SpanInfo (srcSpanStartLine spn)
129+ (srcSpanStartCol spn - 1)
130+ (srcSpanEndLine spn)
131+ (srcSpanEndCol spn - 1)
132+ typ
133+ name)
134+ _ -> Nothing
src/Development/IDE/Functions/Warnings.hsnew file+43
@@ -0,0 +1,43 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+module Development.IDE.Functions.Warnings(withWarnings) where
5+
6+import "ghc-lib-parser" ErrUtils
7+import "ghc-lib-parser" GhcMonad
8+import "ghc-lib" GhcPlugins as GHC hiding (Var)
9+
10+import qualified Data.Text as T
11+import Data.Maybe
12+import Control.Concurrent.Extra
13+import Control.Monad.Extra
14+
15+import Development.IDE.Types.Diagnostics
16+import Development.IDE.UtilGHC
17+import Development.IDE.Functions.GHCError
18+
19+
20+-- | Take a GHC monadic action (e.g. @typecheckModule pm@ for some
21+-- parsed module 'pm@') and produce a "decorated" action that will
22+-- harvest any warnings encountered executing the action. The 'phase'
23+-- argument classifies the context (e.g. "Parser", "Typechecker").
24+--
25+-- The ModSummary function is required because of
26+-- https://github.com/ghc/ghc/blob/5f1d949ab9e09b8d95319633854b7959df06eb58/compiler/main/GHC.hs#L623-L640
27+-- which basically says that log_action is taken from the ModSummary when GHC feels like it.
28+-- The given argument lets you refresh a ModSummary log_action
29+withWarnings :: GhcMonad m => T.Text -> ((ModSummary -> ModSummary) -> m a) -> m ([Diagnostic], a)
30+withWarnings phase action = do
31+ warnings <- liftIO $ newVar []
32+ oldFlags <- getDynFlags
33+ let newAction dynFlags _ _ loc _ msg = do
34+ let d = mkDiag dynFlags phase $ mkPlainWarnMsg dynFlags loc msg
35+ modifyVar_ warnings $ return . (d:)
36+ setLogAction newAction
37+ res <- action $ \x -> x{ms_hspp_opts = (ms_hspp_opts x){log_action = newAction}}
38+ setLogAction $ log_action oldFlags
39+ warns <- liftIO $ readVar warnings
40+ return (reverse $ catMaybes warns, res)
41+
42+setLogAction :: GhcMonad m => LogAction -> m ()
43+setLogAction act = void $ modifyDynFlags $ \dyn -> dyn{log_action = act}
src/Development/IDE/Logger.hsnew file+22
@@ -0,0 +1,22 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+{-# LANGUAGE RankNTypes #-}
5+-- | This is a compatibility module that abstracts over the
6+-- concrete choice of logging framework so users can plug in whatever
7+-- framework they want to.
8+module Development.IDE.Logger
9+ ( Handle(..)
10+ , makeNopHandle
11+ ) where
12+
13+import qualified Data.Text as T
14+import GHC.Stack
15+
16+data Handle m = Handle
17+ { logInfo :: HasCallStack => T.Text -> m ()
18+ , logDebug :: HasCallStack => T.Text -> m ()
19+ }
20+
21+makeNopHandle :: Monad m => Handle m
22+makeNopHandle = Handle (const $ pure ()) (const $ pure ())
src/Development/IDE/State/FileStore.hsnew file+149
@@ -0,0 +1,149 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+{-# LANGUAGE TypeFamilies #-}
5+
6+module Development.IDE.State.FileStore(
7+ getFileExists, getFileContents,
8+ setBufferModified,
9+ fileStoreRules
10+ ) where
11+
12+
13+
14+import "ghc-lib-parser" StringBuffer
15+
16+import qualified Data.Map.Strict as Map
17+import qualified Data.Text as T
18+import Data.Time.Clock
19+import Control.Monad.Extra
20+import qualified System.Directory as Dir
21+import Development.Shake
22+import Development.Shake.Classes
23+import Development.IDE.State.Shake
24+import Development.IDE.UtilGHC
25+import Control.Concurrent.Extra
26+import Control.Exception
27+import GHC.Generics
28+import qualified Data.ByteString.Char8 as BS
29+import Development.IDE.Types.Diagnostics
30+import Data.Time
31+
32+
33+-- This module stores the changed files in memory, and answers file system questions
34+-- from either the memory changes OR the file system itself
35+
36+type DirtyFiles = Map.Map FilePath (UTCTime, StringBuffer) -- when it was modified, it's current value
37+
38+-- Store the DirtyFiles globally, so we can get at it through setBufferModified
39+newtype GlobalDirtyFiles = GlobalDirtyFiles (Var DirtyFiles)
40+instance IsIdeGlobal GlobalDirtyFiles
41+
42+
43+
44+-- | Get the modification time of a file.
45+type instance RuleResult GetModificationTime = IdeResult UTCTime
46+
47+-- | Get the contents of a file, either dirty (if the buffer is modified) or from disk.
48+type instance RuleResult GetFileContents = IdeResult (UTCTime, StringBuffer)
49+
50+-- | Does the file exist.
51+type instance RuleResult GetFileExists = IdeResult Bool
52+
53+
54+data GetFileExists = GetFileExists
55+ deriving (Eq, Show, Generic)
56+instance Binary GetFileExists
57+instance Hashable GetFileExists
58+instance NFData GetFileExists
59+
60+data GetModificationTime = GetModificationTime
61+ deriving (Eq, Show, Generic)
62+instance Binary GetModificationTime
63+instance Hashable GetModificationTime
64+instance NFData GetModificationTime
65+
66+data GetFileContents = GetFileContents
67+ deriving (Eq, Show, Generic)
68+instance Binary GetFileContents
69+instance Hashable GetFileContents
70+instance NFData GetFileContents
71+
72+
73+getFileExistsRule :: Var DirtyFiles -> Rules ()
74+getFileExistsRule dirty =
75+ defineEarlyCutoff $ \GetFileExists file -> do
76+ alwaysRerun
77+ res <- liftIO $ handle (\(_ :: IOException) -> return False) $
78+ (Map.member file <$> readVar dirty) ||^
79+ Dir.doesFileExist file
80+ return (Just $ if res then BS.singleton '1' else BS.empty, ([], Just res))
81+
82+
83+showTimePrecise :: UTCTime -> String
84+showTimePrecise UTCTime{..} = show (toModifiedJulianDay utctDay, diffTimeToPicoseconds utctDayTime)
85+
86+getModificationTimeRule :: Var DirtyFiles -> Rules ()
87+getModificationTimeRule dirty =
88+ defineEarlyCutoff $ \GetModificationTime file -> do
89+ alwaysRerun
90+ res <- liftIO $ ideTryIOException file $ do
91+ mp <- readVar dirty
92+ case Map.lookup file mp of
93+ Just (time, _) -> return time
94+ Nothing -> Dir.getModificationTime file
95+ case res of
96+ Left err -> return (Nothing, ([err], Nothing))
97+ Right time -> return (Just $ BS.pack $ showTimePrecise time, ([], Just time))
98+
99+
100+getFileContentsRule :: Var DirtyFiles -> Rules ()
101+getFileContentsRule dirty =
102+ define $ \GetFileContents file -> do
103+ -- need to depend on modification time to introduce a dependency with Cutoff
104+ time <- use_ GetModificationTime file
105+ res <- liftIO $ ideTryIOException file $ do
106+ mp <- readVar dirty
107+ case Map.lookup file mp of
108+ Just (_, contents) -> return contents
109+ Nothing -> hGetStringBuffer file
110+ case res of
111+ Left err -> return ([err], Nothing)
112+ Right contents -> return ([], Just (time, contents))
113+
114+
115+getFileContents :: FilePath -> Action (UTCTime, StringBuffer)
116+getFileContents = use_ GetFileContents
117+
118+getFileExists :: FilePath -> Action Bool
119+getFileExists =
120+ -- we deliberately and intentionally wrap the file as an FilePath WITHOUT mkAbsolute
121+ -- so that if the file doesn't exist, is on a shared drive that is unmounted etc we get a properly
122+ -- cached 'No' rather than an exception in the wrong place
123+ use_ GetFileExists
124+
125+
126+fileStoreRules :: Rules ()
127+fileStoreRules = do
128+ dirty <- liftIO $ newVar Map.empty
129+ addIdeGlobal $ GlobalDirtyFiles dirty
130+ getModificationTimeRule dirty
131+ getFileContentsRule dirty
132+ getFileExistsRule dirty
133+
134+
135+strictPair :: a -> b -> (a, b)
136+strictPair !a !b = (a,b)
137+
138+
139+-- | Notify the compiler service of a modified buffer
140+setBufferModified :: IdeState -> FilePath -> (Maybe T.Text, UTCTime) -> IO ()
141+setBufferModified state absFile (mcontents, !time) = do
142+ GlobalDirtyFiles envDirtyFiles <- getIdeGlobalState state
143+ -- update vars synchronously
144+ modifyVar_ envDirtyFiles $ evaluate . case mcontents of
145+ Nothing -> Map.delete absFile
146+ Just contents -> Map.insert absFile $ strictPair time (textToStringBuffer contents)
147+
148+ -- run shake to update results regarding the files of interest
149+ void $ shakeRun state []
src/Development/IDE/State/RuleTypes.hsnew file+186
@@ -0,0 +1,186 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+{-# LANGUAGE FlexibleInstances #-}
5+{-# LANGUAGE TypeFamilies #-}
6+{-# OPTIONS_GHC -Wno-orphans #-}
7+
8+-- | A Shake implementation of the compiler service, built
9+-- using the "Shaker" abstraction layer for in-memory use.
10+--
11+module Development.IDE.State.RuleTypes(
12+ module Development.IDE.State.RuleTypes
13+ ) where
14+
15+import Control.DeepSeq
16+import Development.IDE.Functions.Compile (TcModuleResult, GhcModule, LoadPackageResult(..))
17+import qualified Development.IDE.Functions.Compile as Compile
18+import Development.IDE.Functions.FindImports (Import(..))
19+import Development.IDE.Functions.DependencyInformation
20+import Data.Binary (Binary)
21+import qualified Data.Binary as Binary
22+import Data.Hashable
23+import Data.Typeable
24+import Development.Shake hiding (Env, newCache)
25+import GHC.Generics (Generic)
26+
27+import "ghc-lib" GHC
28+import "ghc-lib-parser" Module
29+
30+import Development.IDE.State.Shake
31+import Development.IDE.Types.SpanInfo
32+
33+
34+-- NOTATION
35+-- Foo+ means Foo for the dependencies
36+-- Foo* means Foo for me and Foo+
37+
38+-- | Kick off things
39+type instance RuleResult OfInterest = IdeResult ()
40+
41+-- | The parse tree for the file using GetFileContents
42+type instance RuleResult GetParsedModule = IdeResult ParsedModule
43+
44+-- | The dependency information produced by following the imports recursively.
45+-- This rule will succeed even if there is an error, e.g., a module could not be located,
46+-- a module could not be parsed or an import cycle.
47+type instance RuleResult GetDependencyInformation = IdeResult DependencyInformation
48+
49+-- | Transitive module and pkg dependencies based on the information produced by GetDependencyInformation.
50+-- This rule is also responsible for calling ReportImportCycles for each file in the transitive closure.
51+type instance RuleResult GetDependencies = IdeResult TransitiveDependencies
52+
53+-- | The type checked version of this file, requires TypeCheck+
54+type instance RuleResult TypeCheck = IdeResult TcModuleResult
55+
56+-- | The result of loading a module from a package.
57+type instance RuleResult LoadPackage = IdeResult LoadPackageResult
58+
59+-- | Information about what spans occur where, requires TypeCheck
60+type instance RuleResult GetSpanInfo = IdeResult [SpanInfo]
61+
62+-- | Convert to Core, requires TypeCheck*
63+type instance RuleResult GenerateCore = IdeResult GhcModule
64+
65+-- | We capture the subset of `DynFlags` that is computed by package initialization in a rule to
66+-- make session initialization cheaper by reusing it.
67+type instance RuleResult GeneratePackageState = IdeResult Compile.PackageState
68+
69+-- | Resolve the imports in a module to the list of either external packages or absolute file paths
70+-- for modules in the same package.
71+type instance RuleResult GetLocatedImports = IdeResult [(Located ModuleName, Maybe Import)]
72+
73+-- | This rule is used to report import cycles. It depends on GetDependencyInformation.
74+-- We cannot report the cycles directly from GetDependencyInformation since
75+-- we can only report diagnostics for the current file.
76+type instance RuleResult ReportImportCycles = IdeResult ()
77+
78+
79+data OfInterest = OfInterest
80+ deriving (Eq, Show, Typeable, Generic)
81+instance Binary OfInterest
82+instance Hashable OfInterest
83+instance NFData OfInterest
84+
85+data GetParsedModule = GetParsedModule
86+ deriving (Eq, Show, Typeable, Generic)
87+instance Binary GetParsedModule
88+instance Hashable GetParsedModule
89+instance NFData GetParsedModule
90+
91+data GetLocatedImports = GetLocatedImports
92+ deriving (Eq, Show, Typeable, Generic)
93+instance Binary GetLocatedImports
94+instance Hashable GetLocatedImports
95+instance NFData GetLocatedImports
96+
97+data GetDependencyInformation = GetDependencyInformation
98+ deriving (Eq, Show, Typeable, Generic)
99+instance Binary GetDependencyInformation
100+instance Hashable GetDependencyInformation
101+instance NFData GetDependencyInformation
102+
103+data ReportImportCycles = ReportImportCycles
104+ deriving (Eq, Show, Typeable, Generic)
105+instance Binary ReportImportCycles
106+instance Hashable ReportImportCycles
107+instance NFData ReportImportCycles
108+
109+data GetDependencies = GetDependencies
110+ deriving (Eq, Show, Typeable, Generic)
111+instance Binary GetDependencies
112+instance Hashable GetDependencies
113+instance NFData GetDependencies
114+
115+data TypeCheck = TypeCheck
116+ deriving (Eq, Show, Typeable, Generic)
117+instance Binary TypeCheck
118+instance Hashable TypeCheck
119+instance NFData TypeCheck
120+
121+data LoadPackage = LoadPackage InstalledUnitId
122+ deriving (Eq, Show, Typeable, Generic)
123+instance Binary LoadPackage
124+instance Hashable LoadPackage
125+instance NFData LoadPackage
126+
127+data GetSpanInfo = GetSpanInfo
128+ deriving (Eq, Show, Typeable, Generic)
129+instance Binary GetSpanInfo
130+instance Hashable GetSpanInfo
131+instance NFData GetSpanInfo
132+
133+data GenerateCore = GenerateCore
134+ deriving (Eq, Show, Typeable, Generic)
135+instance Binary GenerateCore
136+instance Hashable GenerateCore
137+instance NFData GenerateCore
138+
139+data GeneratePackageState = GeneratePackageState [FilePath] Bool [(String, [(String, String)])]
140+ deriving (Eq, Show, Typeable, Generic)
141+instance Binary GeneratePackageState
142+instance Hashable GeneratePackageState
143+instance NFData GeneratePackageState
144+
145+------------------------------------------------------------
146+-- Orphan Instances
147+
148+instance NFData (GenLocated SrcSpan ModuleName) where
149+ rnf = rwhnf
150+
151+instance Show TcModuleResult where
152+ show = show . pm_mod_summary . tm_parsed_module . Compile.tmrModule
153+
154+instance NFData TcModuleResult where
155+ rnf = rwhnf
156+
157+instance Show ModSummary where
158+ show = show . ms_mod
159+
160+instance Show ParsedModule where
161+ show = show . pm_mod_summary
162+
163+instance NFData ModSummary where
164+ rnf = rwhnf
165+
166+instance NFData ParsedModule where
167+ rnf = rwhnf
168+
169+instance NFData SpanInfo where
170+ rnf = rwhnf
171+
172+instance NFData Import where
173+ rnf = rwhnf
174+
175+instance Binary InstalledUnitId where
176+ get = fmap stringToInstalledUnitId Binary.get
177+ put = Binary.put . installedUnitIdString
178+
179+instance Hashable InstalledUnitId where
180+ hashWithSalt salt = hashWithSalt salt . installedUnitIdString
181+
182+instance Show LoadPackageResult where
183+ show = installedUnitIdString . lprInstalledUnitId
184+
185+instance NFData LoadPackageResult where
186+ rnf = rwhnf
src/Development/IDE/State/Rules.hsnew file+358
@@ -0,0 +1,358 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+{-# LANGUAGE TypeFamilies #-}
5+{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE FlexibleInstances #-}
7+
8+-- | A Shake implementation of the compiler service, built
9+-- using the "Shaker" abstraction layer for in-memory use.
10+--
11+module Development.IDE.State.Rules(
12+ IdeState, GetDependencies(..), GetParsedModule(..), TransitiveDependencies(..),
13+ Priority(..),
14+ runAction, runActions, useE, usesE,
15+ toIdeResultNew, defineNoFile,
16+ mainRule,
17+ getGhcCore,
18+ getAtPoint,
19+ getDefinition,
20+ getDependencies,
21+ getDalfDependencies,
22+ fileFromParsedModule
23+ ) where
24+
25+import Control.Concurrent.Extra
26+import Control.Exception (evaluate)
27+import Control.Monad.Except
28+import Control.Monad.Extra (whenJust)
29+import qualified Development.IDE.Functions.Compile as Compile
30+import Development.IDE.Functions.DependencyInformation
31+import Development.IDE.Functions.FindImports
32+import Development.IDE.State.FileStore
33+import Development.IDE.Types.Diagnostics as Base
34+import Development.IDE.UtilGHC
35+import Data.Bifunctor
36+import Data.Either.Extra
37+import Data.Maybe
38+import Data.Foldable
39+import qualified Data.Map.Strict as Map
40+import qualified Data.Set as Set
41+import qualified Data.Text as T
42+import Development.IDE.Functions.GHCError
43+import Development.Shake hiding (Diagnostic, Env, newCache)
44+import Development.IDE.Types.LSP as Compiler
45+import Development.IDE.State.RuleTypes
46+
47+import "ghc-lib" GHC
48+import "ghc-lib-parser" UniqSupply
49+import "ghc-lib-parser" Module as M
50+
51+import qualified Development.IDE.Functions.AtPoint as AtPoint
52+import Development.IDE.State.Service
53+import Development.IDE.State.Shake
54+
55+-- LEGACY STUFF ON THE OLD STYLE
56+
57+toIdeResultNew :: Either [Diagnostic] v -> IdeResult v
58+toIdeResultNew = either (, Nothing) (([],) . Just)
59+
60+-- Convert to a legacy Ide result but dropping dependencies
61+toIdeResultSilent :: IdeResult v -> Either [Diagnostic] v
62+toIdeResultSilent (_, val) = maybe (Left []) Right val
63+
64+
65+defineNoFile :: IdeRule k v => (k -> Action v) -> Rules ()
66+defineNoFile f = define $ \k file -> do
67+ if file == "" then do res <- f k; return ([], Just res) else
68+ fail $ "Rule " ++ show k ++ " should always be called with the empty string for a file"
69+
70+
71+-- | Return a distinct supply of uniques.
72+getUniqSupply :: Action UniqSupply
73+getUniqSupply =
74+ getServiceEnv >>= liftIO . getUniqSupplyFrom
75+
76+getUniqSupplyFrom :: Env -> IO UniqSupply
77+getUniqSupplyFrom Env{..} =
78+ modifyVar envUniqSupplyVar $ evaluate . splitUniqSupply
79+
80+
81+------------------------------------------------------------
82+-- Exposed API
83+
84+
85+-- | Get GHC Core for the supplied file.
86+getGhcCore :: FilePath -> Action (Maybe [CoreModule])
87+getGhcCore file = eitherToMaybe <$> runExceptT (coresForFile file)
88+
89+-- | Get all transitive file dependencies of a given module.
90+-- Does not include the file itself.
91+getDependencies :: FilePath -> Action (Maybe [FilePath])
92+getDependencies file =
93+ eitherToMaybe <$>
94+ (runExceptT $ transitiveModuleDeps <$> useE GetDependencies file)
95+
96+getDalfDependencies :: FilePath -> Action (Maybe [InstalledUnitId])
97+getDalfDependencies file =
98+ eitherToMaybe <$>
99+ (runExceptT $ transitivePkgDeps <$> useE GetDependencies file)
100+
101+-- | Documentation at point.
102+getAtPoint :: FilePath -> Position -> Action (Maybe (Maybe Range, [HoverText]))
103+getAtPoint file pos = do
104+ fmap (either (const Nothing) id) . runExceptT $ getAtPointForFile file pos
105+
106+-- | Goto Definition.
107+getDefinition :: FilePath -> Position -> Action (Maybe Location)
108+getDefinition file pos = do
109+ fmap (either (const Nothing) id) . runExceptT $ getDefinitionForFile file pos
110+
111+
112+------------------------------------------------------------
113+-- Internal Actions
114+
115+useE
116+ :: IdeRule k v
117+ => k -> FilePath -> ExceptT [Diagnostic] Action v
118+useE k = ExceptT . fmap toIdeResultSilent . use k
119+
120+-- picks the first error
121+usesE
122+ :: IdeRule k v
123+ => k -> [FilePath] -> ExceptT [Diagnostic] Action [v]
124+usesE k = ExceptT . fmap (mapM toIdeResultSilent) . uses k
125+
126+-- | Generate the GHC Core for the supplied file and its dependencies.
127+coresForFile :: FilePath -> ExceptT [Diagnostic] Action [CoreModule]
128+coresForFile file = do
129+ files <- transitiveModuleDeps <$> useE GetDependencies file
130+ pms <- usesE GetParsedModule $ files ++ [file]
131+ fs <- liftIO
132+ . mapM fileFromParsedModule
133+ . filter (not . modIsInternal . ms_mod . pm_mod_summary)
134+ $ pms
135+ cores <- usesE GenerateCore fs
136+ pure (map Compile.gmCore cores)
137+
138+-- | Try to get hover text for the name under point.
139+getAtPointForFile
140+ :: FilePath
141+ -> Position
142+ -> ExceptT [Diagnostic] Action (Maybe (Maybe Range, [HoverText]))
143+getAtPointForFile file pos = do
144+ files <- transitiveModuleDeps <$> useE GetDependencies file
145+ tms <- usesE TypeCheck (file : files)
146+ spans <- useE GetSpanInfo file
147+ return $ AtPoint.atPoint (map Compile.tmrModule tms) spans pos
148+
149+getDefinitionForFile :: FilePath -> Position -> ExceptT [Diagnostic] Action (Maybe Location)
150+getDefinitionForFile file pos = do
151+ spans <- useE GetSpanInfo file
152+ return $ AtPoint.gotoDefinition spans pos
153+
154+getOpts :: Action Compile.CompileOpts
155+getOpts = envOptions <$> getServiceEnv
156+
157+------------------------------------------------------------
158+-- Rules
159+-- These typically go from key to value and are oracles.
160+
161+-- TODO (MK) This should be independent of DAML or move out of haskell-ide-core.
162+-- | We build artefacts based on the following high-to-low priority order.
163+data Priority
164+ = PriorityTypeCheck
165+ | PriorityGenerateDalf
166+ | PriorityFilesOfInterest
167+ deriving (Eq, Ord, Show, Enum)
168+
169+
170+getParsedModuleRule :: Rules ()
171+getParsedModuleRule =
172+ define $ \GetParsedModule file -> do
173+ contents <- getFileContents file
174+ packageState <- getPackageState
175+ opt <- getOpts
176+ liftIO $ Compile.parseModule opt packageState file contents
177+
178+getLocatedImportsRule :: Rules ()
179+getLocatedImportsRule =
180+ define $ \GetLocatedImports file -> do
181+ pm <- use_ GetParsedModule file
182+ let ms = pm_mod_summary pm
183+ let imports = ms_textual_imps ms
184+ packageState <- getPackageState
185+ opt <- getOpts
186+ dflags <- liftIO $ Compile.getGhcDynFlags opt pm packageState
187+ xs <- forM imports $ \(mbPkgName, modName) ->
188+ (modName, ) <$> locateModule dflags getFileExists modName mbPkgName
189+ return (concat $ lefts $ map snd xs, Just $ map (second eitherToMaybe) xs)
190+
191+
192+-- | Given a target file path, construct the raw dependency results by following
193+-- imports recursively.
194+rawDependencyInformation :: FilePath -> ExceptT [Diagnostic] Action RawDependencyInformation
195+rawDependencyInformation f = go (Set.singleton f) Map.empty Map.empty
196+ where go fs !modGraph !pkgs =
197+ case Set.minView fs of
198+ Nothing -> pure (RawDependencyInformation modGraph pkgs)
199+ Just (f, fs) -> do
200+ importsOrErr <- lift $ use GetLocatedImports f
201+ case snd importsOrErr of
202+ Nothing ->
203+ let modGraph' = Map.insert f (Left ModuleParseError) modGraph
204+ in go fs modGraph' pkgs
205+ Just imports -> do
206+ packageState <- lift getPackageState
207+ opt <- lift getOpts
208+ modOrPkgImports <- forM imports $ \imp -> do
209+ case imp of
210+ (_modName, Just (PackageImport pkg)) -> do
211+ pkgs <- ExceptT $ liftIO $ Compile.computePackageDeps opt packageState pkg
212+ pure $ Right $ pkg:pkgs
213+ (modName, Just (FileImport absFile)) -> pure $ Left (modName, Just absFile)
214+ (modName, Nothing) -> pure $ Left (modName, Nothing)
215+ let (modImports, pkgImports) = partitionEithers modOrPkgImports
216+ let newFiles = Set.fromList (mapMaybe snd modImports) Set.\\ Map.keysSet modGraph
217+ modGraph' = Map.insert f (Right modImports) modGraph
218+ pkgs' = Map.insert f (Set.fromList $ concat pkgImports) pkgs
219+ go (fs `Set.union` newFiles) modGraph' pkgs'
220+
221+getDependencyInformationRule :: Rules ()
222+getDependencyInformationRule =
223+ define $ \GetDependencyInformation file -> fmap toIdeResultNew $ runExceptT $ do
224+ rawDepInfo <- rawDependencyInformation file
225+ pure $ processDependencyInformation rawDepInfo
226+
227+reportImportCyclesRule :: Rules ()
228+reportImportCyclesRule =
229+ define $ \ReportImportCycles file -> fmap toIdeResultNew $ runExceptT $ do
230+ DependencyInformation{..} <- useE GetDependencyInformation file
231+ whenJust (Map.lookup file depErrorNodes) $ \errs -> do
232+ let cycles = mapMaybe (cycleErrorInFile file) (toList errs)
233+ when (not $ null cycles) $ do
234+ -- Convert cycles of files into cycles of module names
235+ diags <- forM cycles $ \(imp, files) -> do
236+ modNames <- mapM getModuleName files
237+ pure $ toDiag imp modNames
238+ throwError diags
239+ where cycleErrorInFile f (PartOfCycle imp fs)
240+ | f `elem` fs = Just (imp, fs)
241+ cycleErrorInFile _ _ = Nothing
242+ toDiag imp mods = Diagnostic
243+ { dFilePath = lFilePath loc
244+ , dRange = lRange loc
245+ , dSeverity = Error
246+ , dSource = "Import cycle detection"
247+ , dMessage = "Cyclic module dependency between " <> showCycle mods
248+ }
249+ where loc = srcSpanToLocation (getLoc imp)
250+ getModuleName file = do
251+ pm <- useE GetParsedModule file
252+ pure (moduleNameString . moduleName . ms_mod $ pm_mod_summary pm)
253+ showCycle mods = T.intercalate ", " (map T.pack mods)
254+
255+-- returns all transitive dependencies in topological order.
256+-- NOTE: result does not include the argument file.
257+getDependenciesRule :: Rules ()
258+getDependenciesRule =
259+ define $ \GetDependencies file -> do
260+ depInfo@DependencyInformation{..} <- use_ GetDependencyInformation file
261+ let allFiles = Map.keys depModuleDeps <> Map.keys depErrorNodes
262+ _ <- uses_ ReportImportCycles allFiles
263+ return ([], transitiveDeps depInfo file)
264+
265+-- Source SpanInfo is used by AtPoint and Goto Definition.
266+getSpanInfoRule :: Rules ()
267+getSpanInfoRule =
268+ define $ \GetSpanInfo file -> do
269+ pm <- use_ GetParsedModule file
270+ tc <- use_ TypeCheck file
271+ imports <- use_ GetLocatedImports file
272+ packageState <- getPackageState
273+ opt <- getOpts
274+ x <- liftIO $ Compile.getSrcSpanInfos opt pm packageState (fileImports imports) tc
275+ return ([], Just x)
276+
277+-- Typechecks a module.
278+typeCheckRule :: Rules ()
279+typeCheckRule =
280+ define $ \TypeCheck file -> do
281+ pm <- use_ GetParsedModule file
282+ deps <- use_ GetDependencies file
283+ lps <- mapM (flip use_ "" . LoadPackage) (transitivePkgDeps deps)
284+ tms <- uses_ TypeCheck (transitiveModuleDeps deps)
285+ setPriority PriorityTypeCheck
286+ us <- getUniqSupply
287+ packageState <- getPackageState
288+ opt <- getOpts
289+ liftIO $ Compile.typecheckModule opt pm packageState us tms lps pm
290+
291+
292+loadPackageRule :: Rules ()
293+loadPackageRule =
294+ defineNoFile $ \(LoadPackage pkg) -> do
295+ packageState <- getPackageState
296+ opt <- getOpts
297+ pkgs <- liftIO $ Compile.computePackageDeps opt packageState pkg
298+ case pkgs of
299+ Left e -> do
300+ reportSeriousErrorDie $ "LoadPackage " ++ show pkg ++ " computePackageDeps failed, " ++ show e
301+ Right v -> do
302+ lps <- mapM (flip use_ "" . LoadPackage) v
303+ us <- getUniqSupply
304+ res <- liftIO $ Compile.loadPackage opt packageState us lps pkg
305+ case res of
306+ Left e -> reportSeriousErrorDie $ "LoadPackage " ++ show pkg ++ " loadPackage failed, " ++ show e
307+ Right v -> return v
308+
309+
310+generateCoreRule :: Rules ()
311+generateCoreRule =
312+ define $ \GenerateCore file -> do
313+ deps <- use_ GetDependencies file
314+ lps <- mapM (flip use_ "" . LoadPackage) (transitivePkgDeps deps)
315+ (tm:tms) <- uses_ TypeCheck (file:transitiveModuleDeps deps)
316+ let pm = tm_parsed_module . Compile.tmrModule $ tm
317+ setPriority PriorityGenerateDalf
318+ us <- getUniqSupply
319+ packageState <- getPackageState
320+ opt <- getOpts
321+ liftIO $ Compile.compileModule opt pm packageState us tms lps tm
322+
323+generatePackageStateRule :: Rules ()
324+generatePackageStateRule =
325+ defineNoFile $ \(GeneratePackageState paths hideAllPkgs pkgImports) -> do
326+ liftIO $ Compile.generatePackageState paths hideAllPkgs pkgImports
327+
328+-- | A rule that wires per-file rules together
329+mainRule :: Rules ()
330+mainRule = do
331+ getParsedModuleRule
332+ getLocatedImportsRule
333+ getDependencyInformationRule
334+ reportImportCyclesRule
335+ getDependenciesRule
336+ typeCheckRule
337+ getSpanInfoRule
338+ generateCoreRule
339+ generatePackageStateRule
340+ loadPackageRule
341+
342+------------------------------------------------------------
343+
344+fileFromParsedModule :: ParsedModule -> IO FilePath
345+fileFromParsedModule = pure . ms_hspp_file . pm_mod_summary
346+
347+getPackageState :: Action PackageState
348+getPackageState = do
349+ opts <- envOptions <$> getServiceEnv
350+ use_ (GeneratePackageState (Compile.optPackageDbs opts) (Compile.optHideAllPkgs opts) (Compile.optPackageImports opts)) ""
351+
352+fileImports ::
353+ [(Located ModuleName, Maybe Import)]
354+ -> [(Located ModuleName, Maybe FilePath)]
355+fileImports = mapMaybe $ \case
356+ (modName, Nothing) -> Just (modName, Nothing)
357+ (modName, Just (FileImport absFile)) -> Just (modName, Just absFile)
358+ (_modName, Just (PackageImport _pkg)) -> Nothing
src/Development/IDE/State/Service.hsnew file+124
@@ -0,0 +1,124 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+{-# LANGUAGE TypeFamilies #-}
5+{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE FlexibleInstances #-}
7+
8+-- | A Shake implementation of the compiler service, built
9+-- using the "Shaker" abstraction layer for in-memory use.
10+--
11+module Development.IDE.State.Service(
12+ Env(..),
13+ getServiceEnv,
14+ IdeState, initialise, shutdown,
15+ runAction, runActions,
16+ setFilesOfInterest,
17+ writeProfile,
18+ getDiagnostics, unsafeClearDiagnostics,
19+ logDebug
20+ ) where
21+
22+import Control.Concurrent.Extra
23+import Control.Concurrent.STM
24+import Control.Monad.Except
25+import Development.IDE.Functions.Compile (CompileOpts(..))
26+import Development.IDE.State.FileStore
27+import qualified Development.IDE.Logger as Logger
28+import Data.Set (Set)
29+import qualified Data.Set as Set
30+import qualified Data.Text as T
31+import Development.IDE.Functions.GHCError
32+import Development.Shake hiding (Diagnostic, Env, newCache)
33+import Development.IDE.Types.LSP as Compiler
34+
35+import "ghc-lib-parser" UniqSupply
36+
37+import Development.IDE.State.Shake
38+
39+
40+-- | Environment threaded through the Shake actions.
41+data Env = Env
42+ { envOptions :: CompileOpts
43+ -- ^ Compiler options.
44+ , envOfInterestVar :: Var (Set FilePath)
45+ -- ^ The files of interest.
46+ , envUniqSupplyVar :: Var UniqSupply
47+ -- ^ The unique supply of names used by the compiler.
48+ }
49+instance IsIdeGlobal Env
50+
51+
52+mkEnv :: CompileOpts -> IO Env
53+mkEnv options = do
54+ ofInterestVar <- newVar Set.empty
55+ uniqSupplyVar <- mkSplitUniqSupply 'a' >>= newVar
56+ return Env
57+ { envOptions = options
58+ , envOfInterestVar = ofInterestVar
59+ , envUniqSupplyVar = uniqSupplyVar
60+ }
61+
62+getDiagnostics :: IdeState -> IO [Diagnostic]
63+getDiagnostics = getAllDiagnostics
64+
65+unsafeClearDiagnostics :: IdeState -> IO ()
66+unsafeClearDiagnostics = unsafeClearAllDiagnostics
67+
68+
69+------------------------------------------------------------
70+-- Exposed API
71+
72+-- | Initialise the Compiler Service.
73+initialise :: Rules ()
74+ -> Maybe (Event -> STM ())
75+ -> Logger.Handle IO
76+ -> CompileOpts
77+ -> IO IdeState
78+initialise mainRule toDiags logger options =
79+ shakeOpen
80+ (maybe (const $ pure ()) (atomically .) toDiags)
81+ logger
82+ (setProfiling options $
83+ shakeOptions { shakeThreads = optThreads options
84+ , shakeFiles = "/dev/null"
85+ }) $ do
86+ addIdeGlobal =<< liftIO (mkEnv options)
87+ fileStoreRules
88+ mainRule
89+
90+writeProfile :: IdeState -> FilePath -> IO ()
91+writeProfile = shakeProfile
92+
93+setProfiling :: CompileOpts -> ShakeOptions -> ShakeOptions
94+setProfiling opts shakeOpts =
95+ maybe shakeOpts (\p -> shakeOpts { shakeReport = [p], shakeTimings = True }) (optShakeProfiling opts)
96+
97+-- | Shutdown the Compiler Service.
98+shutdown :: IdeState -> IO ()
99+shutdown = shakeShut
100+
101+-- | Run a single action using the supplied service.
102+runAction :: IdeState -> Action a -> IO a
103+runAction service action = head <$> runActions service [action]
104+
105+-- | Run a list of actions in parallel using the supplied service.
106+runActions :: IdeState -> [Action a] -> IO [a]
107+runActions x = join . shakeRun x
108+
109+
110+-- | Set the files-of-interest which will be built and kept-up-to-date.
111+setFilesOfInterest :: IdeState -> Set FilePath -> IO ()
112+setFilesOfInterest state files = do
113+ Env{..} <- getIdeGlobalState state
114+ -- update vars synchronously
115+ modifyVar_ envOfInterestVar $ const $ return files
116+
117+ -- run shake to update results regarding the files of interest
118+ void $ shakeRun state []
119+
120+getServiceEnv :: Action Env
121+getServiceEnv = getIdeGlobalAction
122+
123+logDebug :: IdeState -> T.Text -> IO ()
124+logDebug = shakeLogDebug
src/Development/IDE/State/Shake.hsnew file+389
@@ -0,0 +1,389 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+{-# LANGUAGE ExistentialQuantification #-}
5+{-# LANGUAGE TypeFamilies #-}
6+{-# LANGUAGE ConstraintKinds #-}
7+
8+-- | A Shake implementation of the compiler service.
9+module Development.IDE.State.Shake(
10+ IdeState,
11+ IdeRule, IdeResult,
12+ shakeOpen, shakeShut,
13+ shakeRun,
14+ shakeProfile,
15+ useStale,
16+ use, uses,
17+ use_, uses_,
18+ define, defineEarlyCutoff,
19+ getAllDiagnostics, unsafeClearAllDiagnostics,
20+ reportSeriousError, reportSeriousErrorDie,
21+ IsIdeGlobal, addIdeGlobal, getIdeGlobalState, getIdeGlobalAction,
22+ garbageCollect,
23+ setPriority,
24+ sendEvent,
25+ shakeLogDebug,
26+ ) where
27+
28+import Development.Shake
29+import Development.Shake.Database
30+import Development.Shake.Classes
31+import Development.Shake.Rule
32+import qualified Data.HashMap.Strict as Map
33+import qualified Data.ByteString.Char8 as BS
34+import Data.Dynamic
35+import Data.Maybe
36+import Data.Either
37+import Data.List.Extra
38+import qualified Data.Text as T
39+import qualified Development.IDE.Logger as Logger
40+import Development.IDE.Types.LSP
41+import Development.IDE.Types.Diagnostics
42+import Control.Concurrent.Extra
43+import Control.Exception
44+import Control.DeepSeq
45+import System.Time.Extra
46+import Data.Typeable
47+import Data.Tuple.Extra
48+import System.FilePath
49+import qualified Development.Shake as Shake
50+import Control.Monad.Extra
51+import qualified Data.Set as Set
52+import Data.Time
53+import System.IO.Unsafe
54+import Numeric.Extra
55+
56+
57+
58+-- information we stash inside the shakeExtra field
59+data ShakeExtras = ShakeExtras
60+ {eventer :: Event -> IO ()
61+ ,logger :: Logger.Handle IO
62+ ,globals :: Var (Map.HashMap TypeRep Dynamic)
63+ ,state :: Var Values
64+ }
65+
66+getShakeExtras :: Action ShakeExtras
67+getShakeExtras = do
68+ Just x <- getShakeExtra @ShakeExtras
69+ return x
70+
71+getShakeExtrasRules :: Rules ShakeExtras
72+getShakeExtrasRules = do
73+ -- We'd like to use binding, but no MonadFail Rules https://github.com/ndmitchell/shake/issues/643
74+ x <- getShakeExtraRules @ShakeExtras
75+ return $ fromMaybe (error "Can't find ShakeExtras, serious error") x
76+
77+
78+
79+class Typeable a => IsIdeGlobal a where
80+
81+addIdeGlobal :: IsIdeGlobal a => a -> Rules ()
82+addIdeGlobal x@(typeOf -> ty) = do
83+ ShakeExtras{globals} <- getShakeExtrasRules
84+ liftIO $ modifyVar_ globals $ \mp -> case Map.lookup ty mp of
85+ Just _ -> error $ "Can't addIdeGlobal twice on the same type, got " ++ show ty
86+ Nothing -> return $! Map.insert ty (toDyn x) mp
87+
88+
89+getIdeGlobalExtras :: forall a . IsIdeGlobal a => ShakeExtras -> IO a
90+getIdeGlobalExtras ShakeExtras{globals} = do
91+ Just x <- Map.lookup (typeRep (Proxy :: Proxy a)) <$> readVar globals
92+ return $ fromDyn x $ error "Serious error, corrupt globals"
93+
94+getIdeGlobalAction :: forall a . IsIdeGlobal a => Action a
95+getIdeGlobalAction = liftIO . getIdeGlobalExtras =<< getShakeExtras
96+
97+getIdeGlobalState :: forall a . IsIdeGlobal a => IdeState -> IO a
98+getIdeGlobalState = getIdeGlobalExtras . shakeExtras
99+
100+
101+-- | The state of the all values - nested so you can easily find all errors at a given file.
102+type Values =
103+ Map.HashMap FilePath
104+ (Map.HashMap Key
105+ (IdeResult Dynamic)
106+ )
107+
108+
109+-- | Key type
110+data Key = forall k . (Typeable k, Hashable k, Eq k, Show k) => Key k
111+
112+instance Show Key where
113+ show (Key k) = show k
114+
115+instance Eq Key where
116+ Key k1 == Key k2 | Just k2' <- cast k2 = k1 == k2'
117+ | otherwise = False
118+
119+instance Hashable Key where
120+ hashWithSalt salt (Key key) = hashWithSalt salt key
121+
122+-- | The result of an IDE operation. Warnings and errors are in the Diagnostic,
123+-- and a value is in the Maybe. For operations that throw an error you
124+-- expect a non-empty list of diagnostics, at least one of which is an error,
125+-- and a Nothing. For operations that succeed you expect perhaps some warnings
126+-- and a Just. For operations that depend on other failing operations you may
127+-- get empty diagnostics and a Nothing, to indicate this phase throws no fresh
128+-- errors but still failed.
129+--
130+-- A rule on a file should only return diagnostics for that given file. It should
131+-- not propagate diagnostic errors through multiple phases.
132+type IdeResult v = ([Diagnostic], Maybe v)
133+
134+type IdeRule k v =
135+ ( Shake.RuleResult k ~ IdeResult v
136+ , Shake.ShakeValue k
137+ , Show v
138+ , Typeable v
139+ , NFData v
140+ )
141+
142+
143+-- | A Shake database plus persistent store. Can be thought of as storing
144+-- mappings from @(FilePath, k)@ to @RuleResult k@.
145+data IdeState = IdeState
146+ {shakeDb :: ShakeDatabase
147+ ,shakeAbort :: Var (IO ()) -- close whoever was running last
148+ ,shakeClose :: IO ()
149+ ,shakeExtras :: ShakeExtras
150+ }
151+
152+
153+profileDir :: Maybe FilePath
154+profileDir = Nothing -- set to Just the directory you want profile reports to appear in
155+
156+
157+-- This is debugging code that generates a series of profiles, if the Boolean is true
158+shakeRunDatabaseProfile :: ShakeDatabase -> [Action a] -> IO [a]
159+shakeRunDatabaseProfile shakeDb acts = do
160+ (time, (res,_)) <- duration $ shakeRunDatabase shakeDb acts
161+ whenJust profileDir $ \dir -> do
162+ count <- modifyVar profileCounter $ \x -> let y = x+1 in return (y,y)
163+ let file = "ide-" ++ profileStartTime ++ "-" ++ takeEnd 5 ("0000" ++ show count) ++ "-" ++ showDP 2 time <.> "html"
164+ shakeProfileDatabase shakeDb $ dir </> file
165+ return res
166+ where
167+
168+{-# NOINLINE profileStartTime #-}
169+profileStartTime :: String
170+profileStartTime = unsafePerformIO $ formatTime defaultTimeLocale "%Y%m%d-%H%M%S" <$> getCurrentTime
171+
172+{-# NOINLINE profileCounter #-}
173+profileCounter :: Var Int
174+profileCounter = unsafePerformIO $ newVar 0
175+
176+setValues :: IdeRule k v
177+ => Var Values
178+ -> k
179+ -> FilePath
180+ -> IdeResult v
181+ -> IO (Maybe [Diagnostic], [Diagnostic]) -- ^ (before, after)
182+setValues state key file val = modifyVar state $ \inVal -> do
183+ let k = Key key
184+ outVal = Map.insertWith Map.union file (Map.singleton k $ fmap toDyn <$> val) inVal
185+ f = concatMap fst . Map.elems
186+ return (outVal, (f <$> Map.lookup file inVal, f $ outVal Map.! file))
187+
188+getValues :: forall k v. IdeRule k v => Var Values -> k -> FilePath -> IO (Maybe (IdeResult v))
189+getValues state key file = flip fmap (readVar state) $ \vs -> do
190+ f <- Map.lookup file vs
191+ k <- Map.lookup (Key key) f
192+ pure $ fmap (fromJust . fromDynamic) <$> k
193+
194+-- | Open a 'IdeState', should be shut using 'shakeShut'.
195+shakeOpen :: (Event -> IO ()) -- ^ diagnostic handler
196+ -> Logger.Handle IO
197+ -> ShakeOptions
198+ -> Rules ()
199+ -> IO IdeState
200+shakeOpen diags shakeLogger opts rules = do
201+ shakeExtras <- ShakeExtras diags shakeLogger <$> newVar Map.empty <*> newVar Map.empty
202+ (shakeDb, shakeClose) <- shakeOpenDatabase opts{shakeExtra = addShakeExtra shakeExtras $ shakeExtra opts} rules
203+ shakeAbort <- newVar $ return ()
204+ shakeDb <- shakeDb
205+ return IdeState{..}
206+
207+shakeProfile :: IdeState -> FilePath -> IO ()
208+shakeProfile IdeState{..} = shakeProfileDatabase shakeDb
209+
210+shakeShut :: IdeState -> IO ()
211+shakeShut = shakeClose
212+
213+-- | Spawn immediately, add an action to collect the results syncronously.
214+-- If you are already inside a call to shakeRun that will be aborted with an exception.
215+shakeRun :: IdeState -> [Action a] -> IO (IO [a])
216+-- FIXME: If there is already a shakeRun queued up and waiting to send me a kill, I should probably
217+-- not even start, which would make issues with async exceptions less problematic.
218+shakeRun IdeState{shakeExtras=ShakeExtras{..}, ..} acts = modifyVar shakeAbort $ \stop -> do
219+ (stopTime,_) <- duration stop
220+ Logger.logInfo logger $ T.pack $ "Starting shakeRun (aborting the previous one took " ++ showDuration stopTime ++ ")"
221+ bar <- newBarrier
222+ start <- offsetTime
223+ thread <- forkFinally (shakeRunDatabaseProfile shakeDb acts) $ \res -> do
224+ signalBarrier bar res
225+ runTime <- start
226+ Logger.logInfo logger $ T.pack $
227+ "Finishing shakeRun (took " ++ showDuration runTime ++ ", " ++ (if isLeft res then "exception" else "completed") ++ ")"
228+ -- important: we send an async exception to the thread, then wait for it to die, before continuing
229+ return (do killThread thread; void $ waitBarrier bar, either throwIO return =<< waitBarrier bar)
230+
231+-- | Use the last stale value, if it's ever been computed.
232+useStale
233+ :: IdeRule k v
234+ => IdeState -> k -> FilePath -> IO (Maybe v)
235+useStale IdeState{shakeExtras=ShakeExtras{state}} k fp = do
236+ v <- getValues state k fp
237+ return $ maybe Nothing snd v
238+
239+
240+getAllDiagnostics :: IdeState -> IO [Diagnostic]
241+getAllDiagnostics IdeState{shakeExtras = ShakeExtras{state}} = do
242+ val <- readVar state
243+ return $ concatMap (concatMap fst . Map.elems) $ Map.elems val
244+
245+-- | FIXME: This function is temporary! Only required because the files of interest doesn't work
246+unsafeClearAllDiagnostics :: IdeState -> IO ()
247+unsafeClearAllDiagnostics IdeState{shakeExtras = ShakeExtras{state}} = modifyVar_ state $
248+ return . Map.map (Map.map (\(_, x) -> ([], x)))
249+
250+-- | Clear the results for all files that do not match the given predicate.
251+garbageCollect :: (FilePath -> Bool) -> Action ()
252+garbageCollect keep = do
253+ ShakeExtras{state} <- getShakeExtras
254+ liftIO $ modifyVar_ state $ return . Map.filterWithKey (\file _ -> keep file)
255+
256+define
257+ :: IdeRule k v
258+ => (k -> FilePath -> Action (IdeResult v)) -> Rules ()
259+define op = defineEarlyCutoff $ \k v -> (Nothing,) <$> op k v
260+
261+use :: IdeRule k v
262+ => k -> FilePath -> Action (IdeResult v)
263+use key file = head <$> uses key [file]
264+
265+use_ :: IdeRule k v => k -> FilePath -> Action v
266+use_ key file = head <$> uses_ key [file]
267+
268+uses_ :: IdeRule k v => k -> [FilePath] -> Action [v]
269+uses_ key files = do
270+ res <- uses key files
271+ case mapM snd res of
272+ Nothing -> liftIO $ throwIO BadDependency
273+ Just v -> return v
274+
275+reportSeriousError :: String -> Action ()
276+reportSeriousError t = do
277+ ShakeExtras{logger} <- getShakeExtras
278+ liftIO $ Logger.logInfo logger $ T.pack t
279+
280+reportSeriousErrorDie :: String -> Action a
281+reportSeriousErrorDie t = do
282+ ShakeExtras{logger} <- getShakeExtras
283+ liftIO $ Logger.logInfo logger $ T.pack t
284+ fail t
285+
286+
287+-- | When we depend on something that reported an error, and we fail as a direct result, throw BadDependency
288+-- which short-circuits the rest of the action
289+data BadDependency = BadDependency deriving Show
290+instance Exception BadDependency
291+
292+isBadDependency :: SomeException -> Bool
293+isBadDependency x
294+ | Just (x :: ShakeException) <- fromException x = isBadDependency $ shakeExceptionInner x
295+ | Just (_ :: BadDependency) <- fromException x = True
296+ | otherwise = False
297+
298+
299+newtype Q k = Q (k, FilePath)
300+ deriving (Eq,Hashable,Binary,NFData)
301+
302+instance Show k => Show (Q k) where
303+ show (Q (k, file)) = show k ++ "; " ++ file
304+
305+-- | Invariant: the 'v' must be in normal form (fully evaluated).
306+-- Otherwise we keep repeatedly 'rnf'ing values taken from the Shake database
307+data A v = A v (Maybe BS.ByteString)
308+ deriving Show
309+
310+instance NFData (A v) where rnf (A v x) = v `seq` rnf x
311+
312+type instance RuleResult (Q k) = A (RuleResult k)
313+
314+
315+-- | Compute the value
316+uses :: IdeRule k v
317+ => k -> [FilePath] -> Action [IdeResult v]
318+uses key files = map (\(A value _) -> value) <$> apply (map (Q . (key,)) files)
319+
320+defineEarlyCutoff
321+ :: IdeRule k v
322+ => (k -> FilePath -> Action (Maybe BS.ByteString, IdeResult v))
323+ -> Rules ()
324+defineEarlyCutoff op = addBuiltinRule noLint noIdentity $ \(Q (key, file)) old mode -> do
325+ ShakeExtras{state} <- getShakeExtras
326+ val <- case old of
327+ Just old | mode == RunDependenciesSame -> do
328+ v <- liftIO $ getValues state key file
329+ case v of
330+ Just v -> return $ Just $ RunResult ChangedNothing old $ A v (unwrap old)
331+ _ -> return Nothing
332+ _ -> return Nothing
333+ case val of
334+ Just res -> return res
335+ Nothing -> do
336+ (bs, res) <- actionCatch
337+ (do v <- op key file; liftIO $ evaluate $ force v) $
338+ \(e :: SomeException) -> pure (Nothing, ([ideErrorText file $ T.pack $ show e | not $ isBadDependency e],Nothing))
339+ res <- return $ first (map $ fixDiagnostic file) res
340+
341+ let badErrors = filter (\d -> null file || dRange d == noRange) $ fst res
342+ when (badErrors /= []) $
343+ reportSeriousError $ "Bad errors found for " ++ show (key, file) ++ " got " ++ show badErrors
344+
345+ (before, after) <- liftIO $ setValues state key file res
346+ updateFileDiagnostics file before after
347+ let eq = case (bs, fmap unwrap old) of
348+ (Just a, Just (Just b)) -> a == b
349+ _ -> False
350+ return $ RunResult
351+ (if eq then ChangedRecomputeSame else ChangedRecomputeDiff)
352+ (wrap bs)
353+ $ A res bs
354+ where
355+ wrap = maybe BS.empty (BS.cons '_')
356+ unwrap x = if BS.null x then Nothing else Just $ BS.tail x
357+
358+
359+-- | If any diagnostic has the wrong filename, generate a new diagnostic with the right file name
360+fixDiagnostic :: FilePath -> Diagnostic -> Diagnostic
361+fixDiagnostic x d
362+ | dFilePath d == x = d
363+ | otherwise = d{dFilePath = x, dRange = noRange, dMessage = T.pack ("Originally reported at " ++ dFilePath d ++ "\n") <> dMessage d}
364+
365+
366+updateFileDiagnostics ::
367+ FilePath
368+ -> Maybe [Diagnostic] -- ^ previous results for this file
369+ -> [Diagnostic] -- ^ current results
370+ -> Action ()
371+updateFileDiagnostics afp previousAll currentAll = do
372+ let filt = Set.fromList . filter (\x -> dFilePath x == afp)
373+ previous = fmap filt previousAll
374+ current = filt currentAll
375+ when (Just current /= previous) $
376+ sendEvent $ EventFileDiagnostics $ FileDiagnostics afp $ Set.toList current
377+
378+
379+setPriority :: (Enum a) => a -> Action ()
380+setPriority p =
381+ deprioritize (fromIntegral . negate $ fromEnum p)
382+
383+sendEvent :: Event -> Action ()
384+sendEvent e = do
385+ ShakeExtras{eventer} <- getShakeExtras
386+ liftIO $ eventer e
387+
388+shakeLogDebug :: IdeState -> T.Text -> IO ()
389+shakeLogDebug IdeState{shakeExtras=ShakeExtras{logger}} msg = Logger.logDebug logger msg
src/Development/IDE/Types/Diagnostics.hsnew file+139
@@ -0,0 +1,139 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+{-# LANGUAGE FlexibleInstances #-}
5+{-# LANGUAGE OverloadedStrings #-}
6+module Development.IDE.Types.Diagnostics (
7+ Diagnostic(..),
8+ FileDiagnostics(..),
9+ Location(..),
10+ Range(..),
11+ Severity(..),
12+ Position(..),
13+ noLocation,
14+ noRange,
15+ ideErrorText,
16+ ideErrorPretty,
17+ errorDiag,
18+ ideTryIOException,
19+ prettyFileDiagnostics,
20+ prettyDiagnostic
21+ ) where
22+
23+import Control.DeepSeq
24+import Control.Exception
25+import Data.Aeson (FromJSON, ToJSON)
26+import Data.Either.Combinators
27+import Data.List.Extra
28+import qualified Data.Text as T
29+import Data.Text.Prettyprint.Doc.Syntax
30+import GHC.Generics
31+import qualified Network.URI.Encode
32+import qualified Text.PrettyPrint.Annotated.HughesPJClass as Pretty
33+
34+import Development.IDE.Types.Location
35+
36+ideErrorText :: FilePath -> T.Text -> Diagnostic
37+ideErrorText absFile = errorDiag absFile "Ide Error"
38+
39+ideErrorPretty :: Pretty.Pretty e => FilePath -> e -> Diagnostic
40+ideErrorPretty absFile = ideErrorText absFile . T.pack . Pretty.prettyShow
41+
42+errorDiag :: FilePath -> T.Text -> T.Text -> Diagnostic
43+errorDiag fp src msg =
44+ Diagnostic
45+ { dFilePath = fp
46+ , dRange = noRange
47+ , dSeverity = Error
48+ , dSource = src
49+ , dMessage = msg
50+ }
51+
52+ideTryIOException :: FilePath -> IO a -> IO (Either Diagnostic a)
53+ideTryIOException fp act =
54+ mapLeft (\(e :: IOException) -> ideErrorText fp $ T.pack $ show e) <$> try act
55+
56+data Diagnostic = Diagnostic
57+ { dFilePath :: !FilePath
58+ -- ^ Specific file that the diagnostic refers to.
59+ , dRange :: !Range
60+ -- ^ The range to which the diagnostic applies.
61+ , dSeverity :: !Severity
62+ -- ^ The severity of the diagnostic, such as 'SError' or 'SWarning'.
63+ , dSource :: !T.Text
64+ -- ^ Human-readable description for the source of the diagnostic,
65+ -- for example 'parser'.
66+ , dMessage :: !T.Text
67+ -- ^ The diagnostic's message.
68+ }
69+ deriving (Eq, Ord, Show, Generic)
70+
71+instance NFData Diagnostic
72+
73+-- | The diagnostic severity.
74+data Severity
75+ = Error | Warning
76+ deriving (Eq, Ord, Show, Generic)
77+
78+instance NFData Severity
79+
80+-- | Human readable diagnostics for a specific file.
81+--
82+-- This type packages a pretty printed, human readable error message
83+-- along with the related source location so that we can display the error
84+-- on either the console or in the IDE at the right source location.
85+--
86+data FileDiagnostics = FileDiagnostics
87+ { fdFilePath :: !FilePath
88+ -- ^ Path of the module that we were trying to process.
89+ -- In a multi-module program this is the file that we started
90+ -- trying to compile, not necessarily the one in which we found the
91+ -- reported errors or warnings.
92+ , fdDiagnostics :: ![Diagnostic]
93+ -- ^ Diagnostics for the desired module,
94+ -- as well as any transitively imported modules.
95+ }
96+ deriving (Eq, Ord, Show, Generic)
97+
98+instance FromJSON Diagnostic
99+instance ToJSON Diagnostic
100+
101+instance FromJSON Severity
102+instance ToJSON Severity
103+
104+instance FromJSON FileDiagnostics
105+instance ToJSON FileDiagnostics
106+
107+prettyFileDiagnostics :: FileDiagnostics -> Doc SyntaxClass
108+prettyFileDiagnostics (FileDiagnostics filePath diagnostics) =
109+ label_ "Compiler error in" $ vcat
110+ [ label_ "File:" $ pretty filePath
111+ , label_ "Errors:" $ vcat $ map prettyDiagnostic $ nubOrd diagnostics
112+ ]
113+
114+stringParagraphs :: T.Text -> Doc a
115+stringParagraphs = vcat . map (fillSep . map pretty . T.words) . T.lines
116+
117+prettyDiagnostic :: Diagnostic -> Doc SyntaxClass
118+prettyDiagnostic (Diagnostic filePath range severity source msg) =
119+ vcat
120+ [ label_ "File: " $ pretty filePath
121+ , label_ "Range: "
122+ $ annotate (LinkSC uri title)
123+ $ pretty range
124+ , label_ "Source: " $ pretty source
125+ , label_ "Severity:" $ pretty $ show severity
126+ , label_ "Message: "
127+ $ case severity of
128+ Error -> annotate ErrorSC
129+ Warning -> annotate WarningSC
130+ $ stringParagraphs msg
131+ ]
132+ where
133+ -- FIXME(JM): Move uri construction to DA.Pretty?
134+ Position sline _ = rangeStart range
135+ Position eline _ = rangeEnd range
136+ uri = "command:daml.revealLocation?"
137+ <> Network.URI.Encode.encodeText ("[\"file://" <> T.pack filePath <> "\","
138+ <> T.pack (show sline) <> ", " <> T.pack (show eline) <> "]")
139+ title = T.pack filePath
src/Development/IDE/Types/LSP.hsnew file+60
@@ -0,0 +1,60 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+module Development.IDE.Types.LSP
5+ ( HoverText(..)
6+ , Event(..)
7+ , VirtualResource(..)
8+ , getHoverTextContent
9+ ) where
10+
11+import Control.DeepSeq
12+import qualified Data.Text as T
13+import GHC.Generics
14+
15+import Development.IDE.Types.Diagnostics
16+
17+-- | Different types of content we can show on hover.
18+data HoverText
19+ = HoverHeading !T.Text
20+ -- ^ A header that explains the content below it.
21+ | HoverDamlCode !T.Text
22+ -- ^ Highlighted DAML-Code
23+ | HoverMarkdown !T.Text
24+ -- ^ Markdown text.
25+ deriving Show
26+
27+getHoverTextContent :: HoverText -> T.Text
28+getHoverTextContent = \case
29+ HoverHeading t -> t
30+ HoverDamlCode t -> t
31+ HoverMarkdown t -> t
32+
33+-- | Virtual resources
34+data VirtualResource = VRScenario
35+ { vrScenarioFile :: !FilePath
36+ , vrScenarioName :: !T.Text
37+ } deriving (Eq, Ord, Read, Show, Generic)
38+ -- ^ VRScenario identifies a scenario in a given file.
39+ -- This virtual resource is associated with the HTML result of
40+ -- interpreting the corresponding scenario.
41+
42+instance NFData VirtualResource
43+
44+-- | Compiler service events
45+data Event
46+ = EventFileDiagnostics !FileDiagnostics
47+ -- ^ @EventFileDiagnostics fileDiagnostics@
48+ -- How many validations have we finished of how many total
49+ -- together with new file diagnostics for a given file.
50+ | EventVirtualResourceChanged !VirtualResource T.Text
51+ -- ^ @EventVirtualResourceChanged resource contents@ a virtual
52+ -- resource @resource@ changed to @contents
53+ -- NOTE(JM,MH): Keep the contents lazy as we rely on it in
54+ -- 'manageOpenVRs'.
55+ | EventFileValidation Int Int
56+ -- ^ @EventFileValidation finishedValidations totalValidations @
57+ -- How many validations have we finished of how many total.
58+ | EventFatalError !T.Text
59+ -- ^ @EventFatalError reason@: A fatal error occurred in the compiler and
60+ -- the compiler cannot continue.
src/Development/IDE/Types/Location.hsnew file+142
@@ -0,0 +1,142 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+{-# LANGUAGE OverloadedStrings #-}
5+
6+-- | Types and functions for working with source code locations.
7+module Development.IDE.Types.Location
8+ ( genLocation
9+ , inRange
10+ , inRangeClosed
11+ , isGenLocation
12+ , Location(..)
13+ , appendLocation
14+ , noLocation
15+ , noRange
16+ , Position(..)
17+ , Range(..)
18+ , appendRange
19+ ) where
20+
21+import Control.DeepSeq (NFData (..))
22+import Data.Aeson.Types (FromJSON, FromJSONKey, ToJSON, ToJSONKey)
23+import Data.Binary (Binary)
24+import Data.Data
25+import Data.Text.Prettyprint.Doc.Syntax
26+import GHC.Generics
27+
28+------------------------------------------------------------------------------
29+--- Types
30+------------------------------------------------------------------------------
31+
32+-- | Position in a text document expressed as zero-based line and
33+-- character offset.
34+data Position = Position
35+ { positionLine :: {-# UNPACK #-} !Int
36+ -- ^ Zero-based line position in the document.
37+ , positionCharacter :: {-# UNPACK #-} !Int
38+ -- ^ Zero-based character offset on the line.
39+ } deriving (Eq, Ord, Read, Show, Generic, Data)
40+
41+instance NFData Position
42+
43+instance Pretty Position where
44+ pretty pos =
45+ pretty (positionLine pos + 1) <> colon <> pretty (positionCharacter pos + 1)
46+
47+
48+-- | A range in a text document expressed as inclusive start-position and an
49+-- exclusive end-position.
50+data Range = Range
51+ { rangeStart :: {-# UNPACK #-} !Position
52+ -- ^ The start position of the range, which is considered to be part of
53+ -- the range.
54+ , rangeEnd :: {-# UNPACK #-} !Position
55+ -- ^ The end position of the range, which is not considered to be part
56+ -- of the range.
57+ } deriving (Eq, Ord, Read, Show, Generic, Data)
58+
59+instance NFData Range
60+
61+instance Pretty Range where
62+ pretty range =
63+ pretty (rangeStart range) <> "-" <> pretty (rangeEnd range)
64+
65+
66+-- | Represents a location inside a resource, such as a line inside a text file.
67+data Location = Location
68+ { lFilePath :: !FilePath
69+ -- ^ The uri of the document.
70+ , lRange :: !Range
71+ -- ^ The range within the document.
72+ } deriving (Eq, Ord, Read, Show, Generic, Data)
73+
74+instance NFData Location
75+
76+
77+-- | A dummy location to use when location information is missing.
78+noLocation :: Location
79+noLocation = Location
80+ { lFilePath = "<unknown>"
81+ , lRange = noRange
82+ }
83+
84+-- A dummy range to use when range is unknown
85+noRange :: Range
86+noRange = Range (Position 0 0) (Position 100000 0)
87+
88+
89+-- | A dummy location to use when location information is not present because
90+-- the code was generated.
91+genLocation :: Location
92+genLocation = Location
93+ { lFilePath = "<generated>"
94+ , lRange = Range (Position 0 0) (Position 0 0)
95+ }
96+
97+
98+-- | Is a location generated.
99+isGenLocation :: Location -> Bool
100+isGenLocation x = lFilePath x == "<generated>"
101+
102+
103+-- | Check if a position is inside a range.
104+-- Our definition states that the start of the range is included, but not the end.
105+inRange :: Position -> Range -> Bool
106+inRange pos (Range start end) = start <= pos && pos < end
107+
108+
109+-- | Check if a position is inside a range, including the end.
110+-- Both start and end of the range are included.
111+inRangeClosed :: Position -> Range -> Bool
112+inRangeClosed pos (Range start end) = start <= pos && pos <= end
113+
114+
115+-- | Produce a new range where the minimum position is the min of both,
116+-- and the maximum position is the max of both.
117+appendRange :: Range -> Range -> Range
118+appendRange r1 r2
119+ = Range { rangeStart = min (rangeStart r1) (rangeStart r2)
120+ , rangeEnd = max (rangeEnd r1) (rangeEnd r2) }
121+
122+
123+-- | Produce a new location where the ranges are the appended and we choose
124+-- the file path of the second.
125+appendLocation :: Location -> Location -> Location
126+appendLocation l1 l2
127+ = Location { lFilePath = lFilePath l2
128+ , lRange = appendRange (lRange l1) (lRange l2) }
129+
130+instance ToJSON Position
131+instance FromJSON Position
132+instance ToJSONKey Position
133+instance FromJSONKey Position
134+instance Binary Position
135+
136+instance ToJSON Range
137+instance FromJSON Range
138+instance Binary Range
139+
140+instance ToJSON Location
141+instance FromJSON Location
142+instance Binary Location
src/Development/IDE/Types/SpanInfo.hsnew file+63
@@ -0,0 +1,63 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+-- ORIGINALLY COPIED FROM https://github.com/commercialhaskell/intero
5+
6+-- | Types used separate to GHCi vanilla.
7+
8+module Development.IDE.Types.SpanInfo(
9+ SpanInfo(..)
10+ , SpanSource(..)
11+ , getNameM
12+ , getSrcSpan
13+ ) where
14+
15+import GHC
16+import Data.Maybe
17+import OccName
18+
19+
20+-- | Type of some span of source code. Most of these fields are
21+-- unboxed but Haddock doesn't show that.
22+data SpanInfo =
23+ SpanInfo {spaninfoStartLine :: {-# UNPACK #-} !Int
24+ -- ^ Start line of the span.
25+ ,spaninfoStartCol :: {-# UNPACK #-} !Int
26+ -- ^ Start column of the span.
27+ ,spaninfoEndLine :: {-# UNPACK #-} !Int
28+ -- ^ End line of the span (absolute).
29+ ,spaninfoEndCol :: {-# UNPACK #-} !Int
30+ -- ^ End column of the span (absolute).
31+ ,spaninfoType :: !(Maybe Type)
32+ -- ^ A pretty-printed representation fo the type.
33+ ,spaninfoSource :: !SpanSource
34+ -- ^ The actutal 'Name' associated with the span, if
35+ -- any. This can be useful for accessing a variety of
36+ -- information about the identifier such as module,
37+ -- locality, definition location, etc.
38+ }
39+instance Show SpanInfo where
40+ show (SpanInfo sl sc el ec t n) = show [show sl, show sc, show el, show ec, show $ isJust t, show n]
41+
42+-- we don't always get a name out so sometimes manually annotating source is more appropriate
43+data SpanSource = Named Name
44+ | Span SrcSpan
45+ | NoSource
46+ deriving (Eq)
47+
48+instance Show SpanSource where
49+ show = \case
50+ Named n -> "Named " ++ occNameString (occName n)
51+ Span sp -> "Span " ++ show sp
52+ NoSource -> "NoSource"
53+
54+getNameM :: SpanSource -> Maybe Name
55+getNameM = \case
56+ Named name -> Just name
57+ _ -> Nothing
58+
59+getSrcSpan :: SpanSource -> Maybe SrcSpan
60+getSrcSpan = \case
61+ NoSource -> Nothing
62+ Span sp -> Just sp
63+ Named name -> Just $ nameSrcSpan name
src/Development/IDE/UtilGHC.hsnew file+296
@@ -0,0 +1,296 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+{-# LANGUAGE FlexibleInstances #-}
5+{-# LANGUAGE PatternSynonyms #-}
6+{-# OPTIONS_GHC -Wno-missing-fields #-} -- to enable prettyPrint
7+{-# OPTIONS_GHC -Wno-orphans #-}
8+
9+-- | GHC utility functions. Importantly, code using our GHC should never:
10+--
11+-- * Call runGhc, use runGhcFast instead. It's faster and doesn't require config we don't have.
12+--
13+-- * Call setSessionDynFlags, use modifyDynFlags instead. It's faster and avoids loading packages.
14+module Development.IDE.UtilGHC where
15+
16+import "ghc-lib-parser" Config
17+import "ghc-lib-parser" Fingerprint
18+import "ghc-lib" GHC hiding (convertLit)
19+import "ghc-lib-parser" GHC.LanguageExtensions.Type
20+import "ghc-lib-parser" GhcMonad
21+import "ghc-lib" GhcPlugins as GHC hiding (PackageState, fst3, (<>))
22+import "ghc-lib" HscMain
23+import qualified "ghc-lib-parser" Packages
24+import "ghc-lib-parser" Platform
25+import qualified "ghc-lib-parser" StringBuffer as SB
26+import qualified "ghc-lib-parser" EnumSet
27+
28+import Control.DeepSeq
29+import Data.IORef
30+import Data.List
31+import qualified Data.Text as T
32+import GHC.Generics (Generic)
33+
34+----------------------------------------------------------------------
35+-- GHC setup
36+
37+-- | Language options enabled in the DAML-1.2 compilation
38+xExtensionsSet :: [Extension]
39+xExtensionsSet =
40+ [ -- syntactic convenience
41+ RecordPuns, RecordWildCards, LambdaCase, TupleSections, BlockArguments, ViewPatterns,
42+ NumericUnderscores
43+ -- records
44+ , DuplicateRecordFields, DisambiguateRecordFields
45+ -- types and kinds
46+ , ScopedTypeVariables, ExplicitForAll
47+ , DataKinds, KindSignatures, RankNTypes, TypeApplications
48+ , ConstraintKinds
49+ -- type classes
50+ , MultiParamTypeClasses, FlexibleInstances, GeneralizedNewtypeDeriving, TypeSynonymInstances
51+ , DefaultSignatures, StandaloneDeriving, FunctionalDependencies, DeriveFunctor
52+ -- replacing primitives
53+ , RebindableSyntax, OverloadedStrings
54+ -- strictness
55+ , Strict, StrictData
56+ -- avoiding letrec in list comp (see DEL-3841)
57+ , MonadComprehensions
58+ -- package imports
59+ , PackageImports
60+ -- our changes
61+ , NewColonConvention
62+ , DamlVersionRequired
63+ , WithRecordSyntax
64+ , DamlTemplate
65+ ]
66+
67+
68+-- | Language settings _disabled_ ($-XNo...$) in the DAML-1.2 compilation
69+xExtensionsUnset :: [Extension]
70+xExtensionsUnset = [ ]
71+
72+-- | Flags set for DAML-1.2 compilation
73+xFlagsSet :: [ GeneralFlag ]
74+xFlagsSet = [
75+ Opt_Haddock
76+ ]
77+
78+-- | Warning options set for DAML compilation. Note that these can be modified
79+-- (per file) by the user via file headers '{-# OPTIONS -fwarn-... #-} and
80+-- '{-# OPTIONS -no-warn-... #-}'.
81+wOptsSet :: [ WarningFlag ]
82+wOptsSet =
83+ [ Opt_WarnUnusedImports
84+--, Opt_WarnPrepositiveQualifiedModule
85+ , Opt_WarnOverlappingPatterns
86+ , Opt_WarnIncompletePatterns
87+ ]
88+
89+-- | Warning options set for DAML compilation, which become errors.
90+wOptsSetFatal :: [ WarningFlag ]
91+wOptsSetFatal =
92+ [ Opt_WarnMissingFields
93+ , Opt_WarnOverflowedLiterals
94+ ]
95+
96+-- | Warning options unset for DAML compilation. Note that these can be modified
97+-- (per file) by the user via file headers '{-# OPTIONS -fwarn-... #-} and
98+-- '{-# OPTIONS -no-warn-... #-}'.
99+wOptsUnset :: [ WarningFlag ]
100+wOptsUnset =
101+ [ Opt_WarnMissingMonadFailInstances -- failable pattern plus RebindableSyntax raises this error
102+ ]
103+
104+
105+adjustDynFlags :: [FilePath] -> PackageState -> Maybe String -> DynFlags -> DynFlags
106+adjustDynFlags paths packageState mbPackageName dflags
107+ = setImports paths
108+ $ setPackageState packageState
109+ $ setThisInstalledUnitId (maybe mainUnitId stringToUnitId mbPackageName)
110+ -- once we have package imports working, we want to import the base package and set this to
111+ -- the default instead of always compiling in the context of ghc-prim.
112+ $ apply wopt_set wOptsSet
113+ $ apply wopt_unset wOptsUnset
114+ $ apply wopt_set_fatal wOptsSetFatal
115+ $ apply xopt_set xExtensionsSet
116+ $ apply xopt_unset xExtensionsUnset
117+ $ apply gopt_set xFlagsSet
118+ dflags{
119+ mainModIs = mkModule primUnitId (mkModuleName "NotAnExistingName"), -- avoid DEL-6770
120+ ghcLink = NoLink, hscTarget = HscNothing -- avoid generating .o or .hi files
121+ {-, dumpFlags = Opt_D_ppr_debug `EnumSet.insert` dumpFlags dflags -- turn on debug output from GHC-}
122+ }
123+ where apply f xs d = foldl' f d xs
124+
125+setThisInstalledUnitId :: UnitId -> DynFlags -> DynFlags
126+setThisInstalledUnitId unitId dflags =
127+ dflags {thisInstalledUnitId = toInstalledUnitId unitId}
128+
129+setImports :: [FilePath] -> DynFlags -> DynFlags
130+setImports paths dflags = dflags { importPaths = paths }
131+
132+setPackageState :: PackageState -> DynFlags -> DynFlags
133+setPackageState state dflags =
134+ dflags
135+ { pkgDatabase = pkgStateDb state
136+ , pkgState = pkgStateState state
137+ , thisUnitIdInsts_ = pkgThisUnitIdInsts state
138+ }
139+
140+setPackageDbs :: [FilePath] -> DynFlags -> DynFlags
141+setPackageDbs paths dflags =
142+ dflags
143+ { packageDBFlags =
144+ [PackageDB $ PkgConfFile path | path <- paths] ++ [NoGlobalPackageDB, ClearPackageDBs]
145+ , pkgDatabase = if null paths then Just [] else Nothing
146+ -- if we don't load any packages set the package database to empty and loaded.
147+ , settings = (settings dflags)
148+ {sTopDir = case paths of p:_ -> p; _ -> error "No package db path available but used $topdir"
149+ , sSystemPackageConfig = case paths of p:_ -> p; _ -> error "No package db path available but used system package config"
150+ }
151+ }
152+
153+setPackageImports :: Bool -> [(String, [(String, String)])] -> DynFlags -> DynFlags
154+setPackageImports hideAllPkgs pkgImports dflags = dflags {
155+ packageFlags = packageFlags dflags ++
156+ [ExposePackage pkgName (UnitIdArg $ stringToUnitId pkgName)
157+ (ModRenaming False [(mkModuleName mod, mkModuleName alias) | (mod, alias) <- aliases])
158+ | (pkgName, aliases) <- pkgImports
159+ ]
160+ , generalFlags = if hideAllPkgs
161+ then Opt_HideAllPackages `EnumSet.insert` generalFlags dflags
162+ else generalFlags dflags
163+ }
164+
165+modifyDynFlags :: GhcMonad m => (DynFlags -> DynFlags) -> m ()
166+modifyDynFlags f = do
167+ newFlags <- f <$> getSessionDynFlags
168+ -- We do not use setSessionDynFlags here since we handle package
169+ -- initialization separately.
170+ modifySession $ \h ->
171+ h { hsc_dflags = newFlags, hsc_IC = (hsc_IC h) {ic_dflags = newFlags} }
172+
173+-- | This is the subset of `DynFlags` that is computed by package initialization.
174+data PackageState = PackageState
175+ { pkgStateDb :: !(Maybe [(FilePath, [Packages.PackageConfig])])
176+ , pkgStateState :: !Packages.PackageState
177+ , pkgThisUnitIdInsts :: !(Maybe [(ModuleName, Module)])
178+ } deriving (Generic, Show)
179+
180+instance NFData PackageState where
181+ rnf (PackageState db state insts) = db `seq` state `seq` rnf insts
182+
183+-- | Configures the @DynFlags@ for this session to DAML-1.2
184+-- compilation:
185+-- * Installs a custom log action;
186+-- * Sets up the package databases;
187+-- * Sets the import paths to the given list of 'FilePath'.
188+setupDamlGHC :: GhcMonad m => [FilePath] -> Maybe String -> PackageState -> m ()
189+setupDamlGHC importPaths mbPackageName packageState = do
190+ modifyDynFlags $ adjustDynFlags importPaths packageState mbPackageName
191+
192+-- | A version of `showSDoc` that uses default flags (to avoid uses of
193+-- `showSDocUnsafe`).
194+showSDocDefault :: SDoc -> String
195+showSDocDefault = showSDoc dynFlags
196+ where dynFlags = defaultDynFlags fakeSettings fakeLlvmConfig
197+
198+prettyPrint :: Outputable a => a -> String
199+prettyPrint = showSDocDefault . ppr
200+
201+textToStringBuffer :: T.Text -> SB.StringBuffer
202+-- would be nice to do this more efficiently...
203+textToStringBuffer = SB.stringToStringBuffer . T.unpack
204+
205+-- FIXME(#1203): This must move out of `haskell-ide-core` and into `damlc`.
206+internalModules :: [String]
207+internalModules =
208+ [ "Data.String"
209+ , "GHC.CString"
210+ , "GHC.Integer.Type"
211+ , "GHC.Natural"
212+ , "GHC.Real"
213+ , "GHC.Types"
214+ ]
215+
216+-- | Checks if a given module is internal, i.e. gets removed in the Core->LF
217+-- translation. TODO where should this live?
218+modIsInternal :: Module -> Bool
219+modIsInternal m = moduleNameString (moduleName m) `elem` internalModules
220+ -- TODO should we consider DA.Internal.* internal? Difference to GHC.*
221+ -- modules is that these do not disappear in the LF conversion.
222+
223+-- | This import was generated, not user written, so should not produce unused import warnings
224+importGenerated :: Bool -> ImportDecl phase -> ImportDecl phase
225+importGenerated qual i = i{ideclImplicit=True, ideclQualified=qual}
226+
227+mkImport :: Located ModuleName -> ImportDecl GhcPs
228+mkImport mname = GHC.ImportDecl GHC.NoExt GHC.NoSourceText mname Nothing False False False False Nothing Nothing
229+
230+-- FIXME(#1203): This needs to move out of haskell-ide-core.
231+removeTypeableInfo :: ModGuts -> ModGuts
232+removeTypeableInfo guts =
233+ guts{mg_binds = filter (not . isTypeableInfo) (mg_binds guts)}
234+ where
235+ isTypeableInfo = \case
236+ NonRec name _ -> any (`isPrefixOf` getOccString name) ["$krep", "$tc", "$trModule"]
237+ Rec _ -> False
238+
239+-- | Like 'runGhc' but much faster (400x), with less IO and no file dependency
240+runGhcFast :: Ghc a -> IO a
241+-- copied from GHC with the nasty bits dropped
242+runGhcFast act = do
243+ ref <- newIORef (error "empty session")
244+ let session = Session ref
245+ flip unGhc session $ do
246+ dflags <- liftIO $ initDynFlags $ defaultDynFlags fakeSettings fakeLlvmConfig
247+ liftIO $ setUnsafeGlobalDynFlags dflags
248+ env <- liftIO $ newHscEnv dflags
249+ setSession env
250+ withCleanupSession act
251+
252+-- These settings are mostly undefined, but define just enough for what we want to do (which isn't code gen)
253+fakeSettings :: Settings
254+fakeSettings = Settings
255+ {sTargetPlatform=platform
256+ ,sPlatformConstants=platformConstants
257+ ,sProjectVersion=cProjectVersion
258+ ,sProgramName="ghc"
259+ ,sOpt_P_fingerprint=fingerprint0
260+ }
261+ where
262+ platform = Platform{platformWordSize=8, platformOS=OSUnknown, platformUnregisterised=True}
263+ platformConstants = PlatformConstants{pc_DYNAMIC_BY_DEFAULT=False,pc_WORD_SIZE=8}
264+
265+fakeLlvmConfig :: (LlvmTargets, LlvmPasses)
266+fakeLlvmConfig = ([], [])
267+
268+
269+-- Orphan instances for types from the GHC API.
270+instance Show CoreModule where show = prettyPrint
271+instance NFData CoreModule where rnf !_ = ()
272+
273+instance Show RdrName where show = prettyPrint
274+instance NFData RdrName where rnf !_ = ()
275+
276+instance Show InstalledUnitId where
277+ show = installedUnitIdString
278+
279+instance NFData InstalledUnitId where
280+ rnf = rwhnf
281+
282+instance NFData SB.StringBuffer where
283+ rnf = rwhnf
284+
285+instance Show Module where
286+ show = moduleNameString . moduleName
287+
288+instance Show ComponentId where show = prettyPrint
289+instance Show SourcePackageId where show = prettyPrint
290+instance Show ModuleName where show = prettyPrint
291+instance Show (GenLocated SrcSpan ModuleName) where show = prettyPrint
292+instance Show PackageName where show = prettyPrint
293+instance Show Packages.PackageState where show _ = "PackageState"
294+instance Show Name where show = prettyPrint
295+
296+type RealLocated = GenLocated RealSrcSpan