1-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2-- SPDX-License-Identifier: Apache-2.0
4{-# LANGUAGE TypeFamilies #-}
5{-# LANGUAGE OverloadedStrings #-}
6{-# LANGUAGE FlexibleInstances #-}
7{-# LANGUAGE DuplicateRecordFields #-}
9-- | A Shake implementation of the compiler service, built
10-- using the "Shaker" abstraction layer for in-memory use.
12module Development.IDE.Core.Rules(
13 IdeState, GetDependencies(..), GetParsedModule(..), TransitiveDependencies(..),
15 runAction, runActions, useE, usesE,
16 toIdeResult, defineNoFile,
26import Control.Monad.Except
27import Control.Monad.Trans.Maybe
28import qualified Development.IDE.Core.Compile as Compile
29import qualified Development.IDE.Types.Options as Compile
30import Development.IDE.Import.DependencyInformation
31import Development.IDE.Import.FindImports
32import Development.IDE.Core.FileStore
33import Development.IDE.Types.Diagnostics as Base
34import Development.IDE.Types.Location
35import qualified Data.ByteString.UTF8 as BS
36import Control.Exception
37import Control.Concurrent.Extra
39import Data.Either.Extra
42import qualified Data.Map.Strict as Map
43import qualified Data.Set as Set
44import qualified Data.Text as T
45import Development.IDE.GHC.Error
46import Development.Shake hiding (Diagnostic, Env, newCache)
47import Development.IDE.Core.RuleTypes
50import Development.IDE.GHC.Compat
54import qualified Development.IDE.Spans.AtPoint as AtPoint
55import Development.IDE.Core.Service
56import Development.IDE.Core.Shake
58-- | This is useful for rules to convert rules that can only produce errors or
59-- a result into the more general IdeResult type that supports producing
60-- warnings while also producing a result.
61toIdeResult :: Either [FileDiagnostic] v -> IdeResult v
62toIdeResult = either (, Nothing) (([],) . Just)
64-- | useE is useful to implement functions that aren’t rules but need shortcircuiting
66useE :: IdeRule k v => k -> NormalizedFilePath -> MaybeT Action v
67useE k = MaybeT . use k
69usesE :: IdeRule k v => k -> [NormalizedFilePath] -> MaybeT Action [v]
70usesE k = MaybeT . fmap sequence . uses k
72defineNoFile :: IdeRule k v => (k -> Action v) -> Rules ()
73defineNoFile f = define $ \k file -> do
74 if file == "" then do res <- f k; return ([], Just res) else
75 fail $ "Rule " ++ show k ++ " should always be called with the empty string for a file"
78------------------------------------------------------------
81getFilesOfInterestRule :: Rules ()
82getFilesOfInterestRule = do
83 defineEarlyCutoff $ \GetFilesOfInterest _file -> assert (null $ fromNormalizedFilePath _file) $ do
85 Env{..} <- getServiceEnv
86 filesOfInterest <- liftIO $ readVar envOfInterestVar
87 pure (Just $ BS.fromString $ show filesOfInterest, ([], Just filesOfInterest))
90-- | Generate the GHC Core for the supplied file and its dependencies.
91getGhcCore :: NormalizedFilePath -> Action (Maybe [CoreModule])
92getGhcCore file = runMaybeT $ do
93 files <- transitiveModuleDeps <$> useE GetDependencies file
94 pms <- usesE GetParsedModule $ files ++ [file]
95 cores <- usesE GenerateCore $ map fileFromParsedModule pms
96 pure (map Compile.gmCore cores)
100-- | Get all transitive file dependencies of a given module.
101-- Does not include the file itself.
102getDependencies :: NormalizedFilePath -> Action (Maybe [NormalizedFilePath])
103getDependencies file = fmap transitiveModuleDeps <$> use GetDependencies file
105-- | Try to get hover text for the name under point.
106getAtPoint :: NormalizedFilePath -> Position -> Action (Maybe (Maybe Range, [T.Text]))
107getAtPoint file pos = fmap join $ runMaybeT $ do
109 files <- transitiveModuleDeps <$> useE GetDependencies file
110 tms <- usesE TypeCheck (file : files)
111 spans <- useE GetSpanInfo file
112 return $ AtPoint.atPoint opts (map Compile.tmrModule tms) spans pos
115getDefinition :: NormalizedFilePath -> Position -> Action (Maybe Location)
116getDefinition file pos = fmap join $ runMaybeT $ do
117 spans <- useE GetSpanInfo file
118 pkgState <- useE GhcSession ""
120 let getHieFile x = use (GetHieFile x) ""
121 lift $ AtPoint.gotoDefinition getHieFile opts pkgState spans pos
123-- | Parse the contents of a daml file.
124getParsedModule :: NormalizedFilePath -> Action (Maybe ParsedModule)
125getParsedModule file = use GetParsedModule file
127getOpts :: Action Compile.IdeOptions
128getOpts = envOptions <$> getServiceEnv
130------------------------------------------------------------
132-- These typically go from key to value and are oracles.
134-- TODO (MK) This should be independent of DAML or move out of hie-core.
135-- | We build artefacts based on the following high-to-low priority order.
138 | PriorityGenerateDalf
139 | PriorityFilesOfInterest
140 deriving (Eq, Ord, Show, Enum)
143getParsedModuleRule :: Rules ()
145 define $ \GetParsedModule file -> do
146 (_, contents) <- getFileContents file
147 packageState <- use_ GhcSession ""
149 liftIO $ Compile.parseModule opt packageState (fromNormalizedFilePath file) contents
151getLocatedImportsRule :: Rules ()
152getLocatedImportsRule =
153 define $ \GetLocatedImports file -> do
154 pm <- use_ GetParsedModule file
155 let ms = pm_mod_summary pm
156 let imports = ms_textual_imps ms
157 packageState <- use_ GhcSession ""
159 dflags <- liftIO $ Compile.getGhcDynFlags opt pm packageState
160 xs <- forM imports $ \(mbPkgName, modName) ->
161 (modName, ) <$> locateModule dflags (Compile.optExtensions opt) getFileExists modName mbPkgName
162 return (concat $ lefts $ map snd xs, Just $ map (second eitherToMaybe) xs)
165-- | Given a target file path, construct the raw dependency results by following
166-- imports recursively.
167rawDependencyInformation :: NormalizedFilePath -> ExceptT [FileDiagnostic] Action RawDependencyInformation
168rawDependencyInformation f = go (Set.singleton f) Map.empty Map.empty
169 where go fs !modGraph !pkgs =
170 case Set.minView fs of
171 Nothing -> pure (RawDependencyInformation modGraph pkgs)
173 importsOrErr <- lift $ use GetLocatedImports f
176 let modGraph' = Map.insert f (Left ModuleParseError) modGraph
177 in go fs modGraph' pkgs
179 packageState <- lift $ use_ GhcSession ""
181 modOrPkgImports <- forM imports $ \imp -> do
183 (_modName, Just (PackageImport pkg)) -> do
184 pkgs <- ExceptT $ liftIO $ Compile.computePackageDeps opt packageState pkg
185 pure $ Right $ pkg:pkgs
186 (modName, Just (FileImport absFile)) -> pure $ Left (modName, Just absFile)
187 (modName, Nothing) -> pure $ Left (modName, Nothing)
188 let (modImports, pkgImports) = partitionEithers modOrPkgImports
189 let newFiles = Set.fromList (mapMaybe snd modImports) Set.\\ Map.keysSet modGraph
190 modGraph' = Map.insert f (Right modImports) modGraph
191 pkgs' = Map.insert f (Set.fromList $ concat pkgImports) pkgs
192 go (fs `Set.union` newFiles) modGraph' pkgs'
194getDependencyInformationRule :: Rules ()
195getDependencyInformationRule =
196 define $ \GetDependencyInformation file -> fmap toIdeResult $ runExceptT $ do
197 rawDepInfo <- rawDependencyInformation file
198 pure $ processDependencyInformation rawDepInfo
200reportImportCyclesRule :: Rules ()
201reportImportCyclesRule =
202 define $ \ReportImportCycles file -> fmap (\errs -> if null errs then ([], Just ()) else (errs, Nothing)) $ do
203 DependencyInformation{..} <- use_ GetDependencyInformation file
204 case Map.lookup file depErrorNodes of
207 let cycles = mapMaybe (cycleErrorInFile file) (toList errs)
208 -- Convert cycles of files into cycles of module names
209 forM cycles $ \(imp, files) -> do
210 modNames <- mapM getModuleName files
211 pure $ toDiag imp modNames
212 where cycleErrorInFile f (PartOfCycle imp fs)
213 | f `elem` fs = Just (imp, fs)
214 cycleErrorInFile _ _ = Nothing
215 toDiag imp mods = (fp ,) $ Diagnostic
216 { _range = (_range :: Location -> Range) loc
217 , _severity = Just DsError
218 , _source = Just "Import cycle detection"
219 , _message = "Cyclic module dependency between " <> showCycle mods
221 , _relatedInformation = Nothing
223 where loc = srcSpanToLocation (getLoc imp)
224 fp = toNormalizedFilePath $ srcSpanToFilename (getLoc imp)
225 getModuleName file = do
226 pm <- use_ GetParsedModule file
227 pure (moduleNameString . moduleName . ms_mod $ pm_mod_summary pm)
228 showCycle mods = T.intercalate ", " (map T.pack mods)
230-- returns all transitive dependencies in topological order.
231-- NOTE: result does not include the argument file.
232getDependenciesRule :: Rules ()
234 define $ \GetDependencies file -> do
235 depInfo@DependencyInformation{..} <- use_ GetDependencyInformation file
236 let allFiles = Map.keys depModuleDeps <> Map.keys depErrorNodes
237 _ <- uses_ ReportImportCycles allFiles
238 return ([], transitiveDeps depInfo file)
240-- Source SpanInfo is used by AtPoint and Goto Definition.
241getSpanInfoRule :: Rules ()
243 define $ \GetSpanInfo file -> do
244 pm <- use_ GetParsedModule file
245 tc <- use_ TypeCheck file
246 imports <- use_ GetLocatedImports file
247 packageState <- use_ GhcSession ""
249 x <- liftIO $ Compile.getSrcSpanInfos opt pm packageState (fileImports imports) tc
252-- Typechecks a module.
253typeCheckRule :: Rules ()
255 define $ \TypeCheck file -> do
256 pm <- use_ GetParsedModule file
257 deps <- use_ GetDependencies file
258 tms <- uses_ TypeCheck (transitiveModuleDeps deps)
259 setPriority PriorityTypeCheck
260 packageState <- use_ GhcSession ""
262 liftIO $ Compile.typecheckModule opt packageState tms pm
265generateCoreRule :: Rules ()
267 define $ \GenerateCore file -> do
268 deps <- use_ GetDependencies file
269 (tm:tms) <- uses_ TypeCheck (file:transitiveModuleDeps deps)
270 let pm = tm_parsed_module . Compile.tmrModule $ tm
271 setPriority PriorityGenerateDalf
272 packageState <- use_ GhcSession ""
274 liftIO $ Compile.compileModule opt pm packageState tms tm
276loadGhcSession :: Rules ()
278 defineNoFile $ \GhcSession -> do
279 opts <- envOptions <$> getServiceEnv
280 Compile.optGhcSession opts
283getHieFileRule :: Rules ()
285 defineNoFile $ \(GetHieFile f) -> do
286 u <- liftIO $ mkSplitUniqSupply 'a'
287 let nameCache = initNameCache u []
288 liftIO $ fmap fst $ readHieFile nameCache f
290-- | A rule that wires per-file rules together
294 getLocatedImportsRule
295 getDependencyInformationRule
296 reportImportCyclesRule
303 getFilesOfInterestRule
305------------------------------------------------------------
307fileFromParsedModule :: ParsedModule -> NormalizedFilePath
308fileFromParsedModule = toNormalizedFilePath . ms_hspp_file . pm_mod_summary
311 [(Located ModuleName, Maybe Import)]
312 -> [(Located ModuleName, Maybe NormalizedFilePath)]
313fileImports = mapMaybe $ \case
314 (modName, Nothing) -> Just (modName, Nothing)
315 (modName, Just (FileImport absFile)) -> Just (modName, Just absFile)
316 (_modName, Just (PackageImport _pkg)) -> Nothing
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list