on master help sign in
Implement cross-package goto definition (#972)
56d7dacMoritz Kiefer
src/Development/IDE/Functions/AtPoint.hs+48−9
@@ -14,27 +14,39 @@ import Development.IDE.Functions.GHCError
14import Development.IDE.Orphans()
14import Development.IDE.Orphans()
15
15
16-- DAML compiler and infrastructure
16-- DAML compiler and infrastructure
17+import Development.Shake
18+import Development.IDE.UtilGHC
19+import Development.IDE.State.Shake
20+import Development.IDE.State.RuleTypes
17import Development.IDE.Types.Diagnostics
21import Development.IDE.Types.Diagnostics
18import Development.IDE.Types.LSP
22import Development.IDE.Types.LSP
23+import Development.IDE.Types.Options
19import Development.IDE.Types.SpanInfo as SpanInfo
24import Development.IDE.Types.SpanInfo as SpanInfo
20
25
21-- GHC API imports
26-- GHC API imports
27+import Avail
22import GHC
28import GHC
23import DynFlags
29import DynFlags
24−import Outputable hiding ((<>))
30+import HieTypes
31+import FastString
25import Name
32import Name
33+import Outputable hiding ((<>))
26
34
35+import Control.Monad.Extra
36+import Control.Monad.Trans.Maybe
27import Data.Maybe
37import Data.Maybe
28import Data.List
38import Data.List
29import qualified Data.Text as T
39import qualified Data.Text as T
30
40
31-- | Locate the definition of the name at a given position.
41-- | Locate the definition of the name at a given position.
32gotoDefinition
42gotoDefinition
43+ :: IdeOptions
44+ -> PackageDynFlags
33− :: [SpanInfo]
45+ -> [SpanInfo]
34 -> Position
46 -> Position
35− -> Maybe Location
47+ -> Action (Maybe Location)
36−gotoDefinition srcSpans pos =
48+gotoDefinition ideOpts pkgState srcSpans pos =
37− listToMaybe $ locationsAtPoint pos srcSpans
49+ listToMaybe <$> locationsAtPoint ideOpts pkgState pos srcSpans
38
50
39-- | Synopsis for the name at a given position.
51-- | Synopsis for the name at a given position.
40atPoint
52atPoint
@@ -74,10 +86,37 @@ atPoint tcs srcSpans pos = do
74 Just name -> any (`isInfixOf` show name) ["==", "showsPrec"]
86 Just name -> any (`isInfixOf` show name) ["==", "showsPrec"]
75 Nothing -> False
87 Nothing -> False
76
88
77−locationsAtPoint :: Position -> [SpanInfo] -> [Location]
89+locationsAtPoint :: IdeOptions -> PackageDynFlags -> Position -> [SpanInfo] -> Action [Location]
90+locationsAtPoint IdeOptions{..} pkgState pos =
78−locationsAtPoint pos = map srcSpanToLocation
91+ fmap (map srcSpanToLocation) .
79− . mapMaybe (SpanInfo.getSrcSpan . spaninfoSource)
92+ mapMaybeM (getSpan . spaninfoSource) .
80− . spansAtPoint pos
93+ spansAtPoint pos
94+ where getSpan :: SpanSource -> Action (Maybe SrcSpan)
95+ getSpan NoSource = pure Nothing
96+ getSpan (Span sp) = pure $ Just sp
97+ getSpan (Named name) = case nameSrcSpan name of
98+ sp@(RealSrcSpan _) -> pure $ Just sp
99+ UnhelpfulSpan _ -> runMaybeT $ do
100+ -- This case usually arises when the definition is in an external package.
101+ -- In this case the interface files contain garbage source spans
102+ -- so we instead read the .hie files to get useful source spans.
103+ let mod = nameModule name
104+ let unitId = moduleUnitId mod
105+ pkgConfig <- MaybeT $ pure $ lookupPackageConfig unitId pkgState
106+ hiePath <- MaybeT $ liftIO $ optLocateHieFile optPkgLocationOpts pkgConfig mod
107+ hieFile <- MaybeT $ use (GetHieFile hiePath) ""
108+ avail <- MaybeT $ pure $ listToMaybe (filterAvails (eqName name) $ hie_exports hieFile)
109+ srcPath <- MaybeT $ liftIO $ optLocateSrcFile optPkgLocationOpts pkgConfig mod
110+ -- The location will point to the source file used during compilation.
111+ -- This file might no longer exists and even if it does the path will be relative
112+ -- to the compilation directory which we don’t know.
113+ let span = setFileName srcPath $ nameSrcSpan $ availName avail
114+ pure span
115+ -- We ignore uniques and source spans and only compare the name and the module.
116+ eqName :: Name -> Name -> Bool
117+ eqName n n' = nameOccName n == nameOccName n' && nameModule n == nameModule n'
118+ setFileName f (RealSrcSpan span) = RealSrcSpan (span { srcSpanFile = mkFastString f })
119+ setFileName _ span@(UnhelpfulSpan _) = span
81
120
82spansAtPoint :: Position -> [SpanInfo] -> [SpanInfo]
121spansAtPoint :: Position -> [SpanInfo] -> [SpanInfo]
83spansAtPoint pos = filter atp where
122spansAtPoint pos = filter atp where
src/Development/IDE/Functions/Compile.hs+9−2
@@ -29,6 +29,9 @@ import Development.IDE.Functions.SpanInfo
29import Development.IDE.UtilGHC
29import Development.IDE.UtilGHC
30import Development.IDE.Types.Options
30import Development.IDE.Types.Options
31
31
32+import HieBin
33+import HieAst
34+
32import GHC hiding (parseModule, typecheckModule)
35import GHC hiding (parseModule, typecheckModule)
33import qualified Parser
36import qualified Parser
34import Lexer
37import Lexer
@@ -270,9 +273,13 @@ mkTcModuleResult (WriteInterface writeIface) tcm = do
270 session <- getSession
273 session <- getSession
271 nc <- liftIO $ readIORef (hsc_NC session)
274 nc <- liftIO $ readIORef (hsc_NC session)
272 (iface,_) <- liftIO $ mkIfaceTc session Nothing Sf_None details tcGblEnv
275 (iface,_) <- liftIO $ mkIfaceTc session Nothing Sf_None details tcGblEnv
273− when writeIface $
276+ liftIO $ when writeIface $ do
274− liftIO $ do
275 writeIfaceFile (hsc_dflags session) (replaceExtension (file tcm) ".hi") iface
277 writeIfaceFile (hsc_dflags session) (replaceExtension (file tcm) ".hi") iface
278+ -- For now, we write .hie files whenever we write .hi files which roughly corresponds to
279+ -- when we are building a package. It should be easily decoupable if that turns out to be
280+ -- useful.
281+ hieFile <- runHsc session $ mkHieFile (tcModSummary tcm) tcGblEnv (fromJust $ renamedSource tcm)
282+ writeHieFile (replaceExtension (file tcm) ".hie") hieFile
276 let mod_info = HomeModInfo iface details Nothing
283 let mod_info = HomeModInfo iface details Nothing
277 origNc = nsNames nc
284 origNc = nsNames nc
278 case lookupModuleEnv origNc (tcmModule tcm) of
285 case lookupModuleEnv origNc (tcmModule tcm) of
src/Development/IDE/State/RuleTypes.hs+17
@@ -24,6 +24,7 @@ import Development.Shake hiding (Env, newCache)
24import GHC.Generics (Generic)
24import GHC.Generics (Generic)
25
25
26import GHC
26import GHC
27+import HieTypes
27import Module
28import Module
28
29
29import Development.IDE.Types.SpanInfo
30import Development.IDE.Types.SpanInfo
@@ -73,6 +74,9 @@ type instance RuleResult GetLocatedImports = [(Located ModuleName, Maybe Import)
73-- we can only report diagnostics for the current file.
74-- we can only report diagnostics for the current file.
74type instance RuleResult ReportImportCycles = ()
75type instance RuleResult ReportImportCycles = ()
75
76
77+-- | Read the given HIE file.
78+type instance RuleResult GetHieFile = HieFile
79+
76
80
77data OfInterest = OfInterest
81data OfInterest = OfInterest
78 deriving (Eq, Show, Typeable, Generic)
82 deriving (Eq, Show, Typeable, Generic)
@@ -129,6 +133,13 @@ data LoadPackageState = LoadPackageState
129instance Hashable LoadPackageState
133instance Hashable LoadPackageState
130instance NFData LoadPackageState
134instance NFData LoadPackageState
131
135
136+-- Note that we embed the filepath here instead of using the filepath associated with Shake keys.
137+-- Otherwise we will garbage collect the result since files in package dependencies will not be declared reachable.
138+data GetHieFile = GetHieFile FilePath
139+ deriving (Eq, Show, Typeable, Generic)
140+instance Hashable GetHieFile
141+instance NFData GetHieFile
142+
132------------------------------------------------------------
143------------------------------------------------------------
133-- Orphan Instances
144-- Orphan Instances
134
145
@@ -167,3 +178,9 @@ instance Show LoadPackageResult where
167
178
168instance NFData LoadPackageResult where
179instance NFData LoadPackageResult where
169 rnf = rwhnf
180 rnf = rwhnf
181+
182+instance Show HieFile where
183+ show = show . hie_module
184+
185+instance NFData HieFile where
186+ rnf = rwhnf
src/Development/IDE/State/Rules.hs+13−1
@@ -46,8 +46,10 @@ import Development.IDE.Types.LSP as Compiler
46import Development.IDE.State.RuleTypes
46import Development.IDE.State.RuleTypes
47
47
48import GHC
48import GHC
49+import HieBin
49import UniqSupply
50import UniqSupply
50import Module as M
51import Module as M
52+import NameCache
51
53
52import qualified Development.IDE.Functions.AtPoint as AtPoint
54import qualified Development.IDE.Functions.AtPoint as AtPoint
53import Development.IDE.State.Service
55import Development.IDE.State.Service
@@ -151,7 +153,9 @@ getAtPointForFile file pos = do
151getDefinitionForFile :: FilePath -> Position -> ExceptT [FileDiagnostic] Action (Maybe Location)
153getDefinitionForFile :: FilePath -> Position -> ExceptT [FileDiagnostic] Action (Maybe Location)
152getDefinitionForFile file pos = do
154getDefinitionForFile file pos = do
153 spans <- useE GetSpanInfo file
155 spans <- useE GetSpanInfo file
156+ pkgState <- useE LoadPackageState ""
157+ opts <- lift getOpts
154− return $ AtPoint.gotoDefinition spans pos
158+ lift $ AtPoint.gotoDefinition opts pkgState spans pos
155
159
156getOpts :: Action Compile.IdeOptions
160getOpts :: Action Compile.IdeOptions
157getOpts = envOptions <$> getServiceEnv
161getOpts = envOptions <$> getServiceEnv
@@ -331,6 +335,13 @@ loadPackageStateRule =
331 liftIO $ Compile.generatePackageState
335 liftIO $ Compile.generatePackageState
332 (Compile.optPackageDbs opts) (Compile.optHideAllPkgs opts) (Compile.optPackageImports opts)
336 (Compile.optPackageDbs opts) (Compile.optHideAllPkgs opts) (Compile.optPackageImports opts)
333
337
338+getHieFileRule :: Rules ()
339+getHieFileRule =
340+ defineNoFile $ \(GetHieFile f) -> do
341+ u <- liftIO $ mkSplitUniqSupply 'a'
342+ let nameCache = initNameCache u []
343+ liftIO $ fmap fst $ readHieFile nameCache f
344+
334-- | A rule that wires per-file rules together
345-- | A rule that wires per-file rules together
335mainRule :: Rules ()
346mainRule :: Rules ()
336mainRule = do
347mainRule = do
@@ -344,6 +355,7 @@ mainRule = do
344 generateCoreRule
355 generateCoreRule
345 loadPackageStateRule
356 loadPackageStateRule
346 loadPackageRule
357 loadPackageRule
358+ getHieFileRule
347
359
348------------------------------------------------------------
360------------------------------------------------------------
349
361
src/Development/IDE/Types/Options.hs+14
@@ -6,6 +6,7 @@
6-- | Options
6-- | Options
7module Development.IDE.Types.Options
7module Development.IDE.Types.Options
8 ( IdeOptions(..)
8 ( IdeOptions(..)
9+ , IdePkgLocationOptions(..)
9 ) where
10 ) where
10
11
11import Development.IDE.UtilGHC
12import Development.IDE.UtilGHC
@@ -18,6 +19,7 @@ data IdeOptions = IdeOptions
18 , optRunGhcSession :: forall a. Maybe ParsedModule -> PackageDynFlags -> Ghc a -> IO a
19 , optRunGhcSession :: forall a. Maybe ParsedModule -> PackageDynFlags -> Ghc a -> IO a
19 -- ^ Setup a GHC session using a given package state. If a `ParsedModule` is supplied,
20 -- ^ Setup a GHC session using a given package state. If a `ParsedModule` is supplied,
20 -- the import path should be setup for that module.
21 -- the import path should be setup for that module.
22+ , optPkgLocationOpts :: IdePkgLocationOptions
21 , optWriteIface :: Bool
23 , optWriteIface :: Bool
22
24
23 , optMbPackageName :: Maybe String
25 , optMbPackageName :: Maybe String
@@ -29,3 +31,15 @@ data IdeOptions = IdeOptions
29 , optThreads :: Int
31 , optThreads :: Int
30 , optShakeProfiling :: Maybe FilePath
32 , optShakeProfiling :: Maybe FilePath
31 }
33 }
34+
35+
36+-- | The set of options used to locate files belonging to external packages.
37+data IdePkgLocationOptions = IdePkgLocationOptions
38+ { optLocateHieFile :: PackageConfig -> Module -> IO (Maybe FilePath)
39+ -- ^ Locate the HIE file for the given module. The PackageConfig can be
40+ -- used to lookup settings like importDirs.
41+ , optLocateSrcFile :: PackageConfig -> Module -> IO (Maybe FilePath)
42+ -- ^ Locate the source file for the given module. The PackageConfig can be
43+ -- used to lookup settings like importDirs. For DAML, we place them in the package DB.
44+ -- For cabal this could point somewhere in ~/.cabal/packages.
45+ }
src/Development/IDE/UtilGHC.hs+11
@@ -10,6 +10,7 @@
10-- * Call setSessionDynFlags, use modifyDynFlags instead. It's faster and avoids loading packages.
10-- * Call setSessionDynFlags, use modifyDynFlags instead. It's faster and avoids loading packages.
11module Development.IDE.UtilGHC(
11module Development.IDE.UtilGHC(
12 PackageDynFlags(..), setPackageDynFlags, getPackageDynFlags,
12 PackageDynFlags(..), setPackageDynFlags, getPackageDynFlags,
13+ lookupPackageConfig,
13 modifyDynFlags,
14 modifyDynFlags,
14 setPackageImports,
15 setPackageImports,
15 setPackageDbs,
16 setPackageDbs,
@@ -86,6 +87,16 @@ getPackageDynFlags DynFlags{..} = PackageDynFlags
86 , pdfThisUnitIdInsts = thisUnitIdInsts_
87 , pdfThisUnitIdInsts = thisUnitIdInsts_
87 }
88 }
88
89
90+lookupPackageConfig :: UnitId -> PackageDynFlags -> Maybe PackageConfig
91+lookupPackageConfig unitId PackageDynFlags {..} =
92+ lookupPackage' False pkgConfigMap unitId
93+ where
94+ pkgConfigMap =
95+ -- For some weird reason, the GHC API does not provide a way to get the PackageConfigMap
96+ -- from PackageState so we have to wrap it in DynFlags first.
97+ getPackageConfigMap fakeDynFlags { pkgState = pdfPkgState }
98+
99+
89
100
90prettyPrint :: Outputable a => a -> String
101prettyPrint :: Outputable a => a -> String
91prettyPrint = showSDoc fakeDynFlags . ppr
102prettyPrint = showSDoc fakeDynFlags . ppr