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.State.Rules(
13 IdeState, GetDependencies(..), GetParsedModule(..), TransitiveDependencies(..),
15 runAction, runActions, useE, usesE,
16 toIdeResultNew, defineNoFile,
26import Control.Concurrent.Extra
27import Control.Exception (evaluate)
28import Control.Monad.Except
29import Control.Monad.Extra (whenJust)
30import qualified Development.IDE.Functions.Compile as Compile
31import Development.IDE.Functions.DependencyInformation
32import Development.IDE.Functions.FindImports
33import Development.IDE.State.FileStore
34import Development.IDE.Types.Diagnostics as Base
35import Development.IDE.UtilGHC
37import Data.Either.Extra
40import qualified Data.Map.Strict as Map
41import qualified Data.Set as Set
42import qualified Data.Text as T
43import Development.IDE.Functions.GHCError
44import Development.Shake hiding (Diagnostic, Env, newCache)
45import Development.IDE.Types.LSP as Compiler
46import Development.IDE.State.RuleTypes
52import qualified Development.IDE.Functions.AtPoint as AtPoint
53import Development.IDE.State.Service
54import Development.IDE.State.Shake
56-- LEGACY STUFF ON THE OLD STYLE
58toIdeResultNew :: Either [FileDiagnostic] v -> IdeResult v
59toIdeResultNew = either (, Nothing) (([],) . Just)
61-- Convert to a legacy Ide result but dropping dependencies
62toIdeResultSilent :: Maybe v -> Either [FileDiagnostic] v
63toIdeResultSilent val = maybe (Left []) Right val
66defineNoFile :: IdeRule k v => (k -> Action v) -> Rules ()
67defineNoFile f = define $ \k file -> do
68 if file == "" then do res <- f k; return ([], Just res) else
69 fail $ "Rule " ++ show k ++ " should always be called with the empty string for a file"
72-- | Return a distinct supply of uniques.
73getUniqSupply :: Action UniqSupply
75 getServiceEnv >>= liftIO . getUniqSupplyFrom
77getUniqSupplyFrom :: Env -> IO UniqSupply
78getUniqSupplyFrom Env{..} =
79 modifyVar envUniqSupplyVar $ evaluate . splitUniqSupply
82------------------------------------------------------------
86-- | Get GHC Core for the supplied file.
87getGhcCore :: FilePath -> Action (Maybe [CoreModule])
88getGhcCore file = eitherToMaybe <$> runExceptT (coresForFile file)
90-- | Get all transitive file dependencies of a given module.
91-- Does not include the file itself.
92getDependencies :: FilePath -> Action (Maybe [FilePath])
95 (runExceptT $ transitiveModuleDeps <$> useE GetDependencies file)
97getDalfDependencies :: FilePath -> Action (Maybe [InstalledUnitId])
98getDalfDependencies file =
100 (runExceptT $ transitivePkgDeps <$> useE GetDependencies file)
102-- | Documentation at point.
103getAtPoint :: FilePath -> Position -> Action (Maybe (Maybe Range, [HoverText]))
104getAtPoint file pos = do
105 fmap (either (const Nothing) id) . runExceptT $ getAtPointForFile file pos
108getDefinition :: FilePath -> Position -> Action (Maybe Location)
109getDefinition file pos = do
110 fmap (either (const Nothing) id) . runExceptT $ getDefinitionForFile file pos
113------------------------------------------------------------
118 => k -> FilePath -> ExceptT [FileDiagnostic] Action v
119useE k = ExceptT . fmap toIdeResultSilent . use k
121-- picks the first error
124 => k -> [FilePath] -> ExceptT [FileDiagnostic] Action [v]
125usesE k = ExceptT . fmap (mapM toIdeResultSilent) . uses k
127-- | Generate the GHC Core for the supplied file and its dependencies.
128coresForFile :: FilePath -> ExceptT [FileDiagnostic] Action [CoreModule]
129coresForFile file = do
130 files <- transitiveModuleDeps <$> useE GetDependencies file
131 pms <- usesE GetParsedModule $ files ++ [file]
133 . mapM fileFromParsedModule
134 . filter (not . modIsInternal . ms_mod . pm_mod_summary)
136 cores <- usesE GenerateCore fs
137 pure (map Compile.gmCore cores)
139-- | Try to get hover text for the name under point.
143 -> ExceptT [FileDiagnostic] Action (Maybe (Maybe Range, [HoverText]))
144getAtPointForFile file pos = do
145 files <- transitiveModuleDeps <$> useE GetDependencies file
146 tms <- usesE TypeCheck (file : files)
147 spans <- useE GetSpanInfo file
148 return $ AtPoint.atPoint (map Compile.tmrModule tms) spans pos
150getDefinitionForFile :: FilePath -> Position -> ExceptT [FileDiagnostic] Action (Maybe Location)
151getDefinitionForFile file pos = do
152 spans <- useE GetSpanInfo file
153 return $ AtPoint.gotoDefinition spans pos
155getOpts :: Action Compile.CompileOpts
156getOpts = envOptions <$> getServiceEnv
158------------------------------------------------------------
160-- These typically go from key to value and are oracles.
162-- TODO (MK) This should be independent of DAML or move out of haskell-ide-core.
163-- | We build artefacts based on the following high-to-low priority order.
166 | PriorityGenerateDalf
167 | PriorityFilesOfInterest
168 deriving (Eq, Ord, Show, Enum)
171getParsedModuleRule :: Rules ()
173 define $ \GetParsedModule file -> do
174 contents <- getFileContents file
175 packageState <- use_ LoadPackageState ""
177 liftIO $ Compile.parseModule opt packageState file contents
179getLocatedImportsRule :: Rules ()
180getLocatedImportsRule =
181 define $ \GetLocatedImports file -> do
182 pm <- use_ GetParsedModule file
183 let ms = pm_mod_summary pm
184 let imports = ms_textual_imps ms
185 packageState <- use_ LoadPackageState ""
187 dflags <- liftIO $ Compile.getGhcDynFlags opt pm packageState
188 xs <- forM imports $ \(mbPkgName, modName) ->
189 (modName, ) <$> locateModule dflags getFileExists modName mbPkgName
190 return (concat $ lefts $ map snd xs, Just $ map (second eitherToMaybe) xs)
193-- | Given a target file path, construct the raw dependency results by following
194-- imports recursively.
195rawDependencyInformation :: FilePath -> ExceptT [FileDiagnostic] Action RawDependencyInformation
196rawDependencyInformation f = go (Set.singleton f) Map.empty Map.empty
197 where go fs !modGraph !pkgs =
198 case Set.minView fs of
199 Nothing -> pure (RawDependencyInformation modGraph pkgs)
201 importsOrErr <- lift $ use GetLocatedImports f
204 let modGraph' = Map.insert f (Left ModuleParseError) modGraph
205 in go fs modGraph' pkgs
207 packageState <- lift $ use_ LoadPackageState ""
209 modOrPkgImports <- forM imports $ \imp -> do
211 (_modName, Just (PackageImport pkg)) -> do
212 pkgs <- ExceptT $ liftIO $ Compile.computePackageDeps opt packageState pkg
213 pure $ Right $ pkg:pkgs
214 (modName, Just (FileImport absFile)) -> pure $ Left (modName, Just absFile)
215 (modName, Nothing) -> pure $ Left (modName, Nothing)
216 let (modImports, pkgImports) = partitionEithers modOrPkgImports
217 let newFiles = Set.fromList (mapMaybe snd modImports) Set.\\ Map.keysSet modGraph
218 modGraph' = Map.insert f (Right modImports) modGraph
219 pkgs' = Map.insert f (Set.fromList $ concat pkgImports) pkgs
220 go (fs `Set.union` newFiles) modGraph' pkgs'
222getDependencyInformationRule :: Rules ()
223getDependencyInformationRule =
224 define $ \GetDependencyInformation file -> fmap toIdeResultNew $ runExceptT $ do
225 rawDepInfo <- rawDependencyInformation file
226 pure $ processDependencyInformation rawDepInfo
228reportImportCyclesRule :: Rules ()
229reportImportCyclesRule =
230 define $ \ReportImportCycles file -> fmap toIdeResultNew $ runExceptT $ do
231 DependencyInformation{..} <- useE GetDependencyInformation file
232 whenJust (Map.lookup file depErrorNodes) $ \errs -> do
233 let cycles = mapMaybe (cycleErrorInFile file) (toList errs)
234 when (not $ null cycles) $ do
235 -- Convert cycles of files into cycles of module names
236 diags <- forM cycles $ \(imp, files) -> do
237 modNames <- mapM getModuleName files
238 pure $ toDiag imp modNames
240 where cycleErrorInFile f (PartOfCycle imp fs)
241 | f `elem` fs = Just (imp, fs)
242 cycleErrorInFile _ _ = Nothing
243 toDiag imp mods = (fp ,) $ Diagnostic
244 { _range = (_range :: Location -> Range) loc
245 , _severity = Just DsError
246 , _source = Just "Import cycle detection"
247 , _message = "Cyclic module dependency between " <> showCycle mods
249 , _relatedInformation = Nothing
251 where loc = srcSpanToLocation (getLoc imp)
252 fp = srcSpanToFilename (getLoc imp)
253 getModuleName file = do
254 pm <- useE GetParsedModule file
255 pure (moduleNameString . moduleName . ms_mod $ pm_mod_summary pm)
256 showCycle mods = T.intercalate ", " (map T.pack mods)
258-- returns all transitive dependencies in topological order.
259-- NOTE: result does not include the argument file.
260getDependenciesRule :: Rules ()
262 define $ \GetDependencies file -> do
263 depInfo@DependencyInformation{..} <- use_ GetDependencyInformation file
264 let allFiles = Map.keys depModuleDeps <> Map.keys depErrorNodes
265 _ <- uses_ ReportImportCycles allFiles
266 return ([], transitiveDeps depInfo file)
268-- Source SpanInfo is used by AtPoint and Goto Definition.
269getSpanInfoRule :: Rules ()
271 define $ \GetSpanInfo file -> do
272 pm <- use_ GetParsedModule file
273 tc <- use_ TypeCheck file
274 imports <- use_ GetLocatedImports file
275 packageState <- use_ LoadPackageState ""
277 x <- liftIO $ Compile.getSrcSpanInfos opt pm packageState (fileImports imports) tc
280-- Typechecks a module.
281typeCheckRule :: Rules ()
283 define $ \TypeCheck file -> do
284 pm <- use_ GetParsedModule file
285 deps <- use_ GetDependencies file
286 lps <- mapM (flip use_ "" . LoadPackage) (transitivePkgDeps deps)
287 tms <- uses_ TypeCheck (transitiveModuleDeps deps)
288 setPriority PriorityTypeCheck
290 packageState <- use_ LoadPackageState ""
292 liftIO $ Compile.typecheckModule opt pm packageState us tms lps pm
295loadPackageRule :: Rules ()
297 defineNoFile $ \(LoadPackage pkg) -> do
298 packageState <- use_ LoadPackageState ""
300 pkgs <- liftIO $ Compile.computePackageDeps opt packageState pkg
303 reportSeriousErrorDie $ "LoadPackage " ++ show pkg ++ " computePackageDeps failed, " ++ show e
305 lps <- mapM (flip use_ "" . LoadPackage) v
307 res <- liftIO $ Compile.loadPackage opt packageState us lps pkg
309 Left e -> reportSeriousErrorDie $ "LoadPackage " ++ show pkg ++ " loadPackage failed, " ++ show e
313generateCoreRule :: Rules ()
315 define $ \GenerateCore file -> do
316 deps <- use_ GetDependencies file
317 lps <- mapM (flip use_ "" . LoadPackage) (transitivePkgDeps deps)
318 (tm:tms) <- uses_ TypeCheck (file:transitiveModuleDeps deps)
319 let pm = tm_parsed_module . Compile.tmrModule $ tm
320 setPriority PriorityGenerateDalf
322 packageState <- use_ LoadPackageState ""
324 liftIO $ Compile.compileModule opt pm packageState us tms lps tm
326loadPackageStateRule :: Rules ()
327loadPackageStateRule =
328 defineNoFile $ \LoadPackageState -> do
329 opts <- envOptions <$> getServiceEnv
330 liftIO $ Compile.generatePackageState
331 (Compile.optPackageDbs opts) (Compile.optHideAllPkgs opts) (Compile.optPackageImports opts)
333-- | A rule that wires per-file rules together
337 getLocatedImportsRule
338 getDependencyInformationRule
339 reportImportCyclesRule
347------------------------------------------------------------
349fileFromParsedModule :: ParsedModule -> IO FilePath
350fileFromParsedModule = pure . ms_hspp_file . pm_mod_summary
353 [(Located ModuleName, Maybe Import)]
354 -> [(Located ModuleName, Maybe FilePath)]
355fileImports = mapMaybe $ \case
356 (modName, Nothing) -> Just (modName, Nothing)
357 (modName, Just (FileImport absFile)) -> Just (modName, Just absFile)
358 (_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