on master help sign in
Implement cross-package goto definition (#972)

* Implement cross-package goto definition This is more tricky than one might think at first: - The interface files do not contain proper source spans so we cannot use the information in there. - We could theoretically try to get the source location from the DALFs but that is the wrong layer and also not an option when we want to act as a Haskell IDE. So what we do instead is whenever we write interface files we also write .hie files and consult those instead when we get useless source spans otherwise. * Move optLocateHieFile and optLocateSrcFile to a separate type

commit56d7dac830794c653f4c9401fb4b1b0cbb446155 authorMoritz Kiefer <moritz.kiefer@purelyfunctional.org> dateTue 7 May 2019, 15:39 (7 years ago) parentd3aacb3
6 files changed+112−12
src/Development/IDE/Functions/AtPoint.hs+48−9 src/Development/IDE/Functions/Compile.hs+9−2 src/Development/IDE/State/RuleTypes.hs+17 src/Development/IDE/State/Rules.hs+13−1 src/Development/IDE/Types/Options.hs+14 src/Development/IDE/UtilGHC.hs+11
src/Development/IDE/Functions/AtPoint.hs+48−9
@@ -14,27 +14,39 @@ import Development.IDE.Functions.GHCError
1414import Development.IDE.Orphans()
1515
1616-- 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
1721import Development.IDE.Types.Diagnostics
1822import Development.IDE.Types.LSP
23+import Development.IDE.Types.Options
1924import Development.IDE.Types.SpanInfo as SpanInfo
2025
2126-- GHC API imports
27+import Avail
2228import GHC
2329import DynFlags
24−import Outputable hiding ((<>))
30+import HieTypes
31+import FastString
2532import Name
33+import Outputable hiding ((<>))
2634
35+import Control.Monad.Extra
36+import Control.Monad.Trans.Maybe
2737import Data.Maybe
2838import Data.List
2939import qualified Data.Text as T
3040
3141-- | Locate the definition of the name at a given position.
3242gotoDefinition
33− :: [SpanInfo]
43+ :: IdeOptions
44+ -> PackageDynFlags
45+ -> [SpanInfo]
3446 -> Position
35− -> Maybe Location
36−gotoDefinition srcSpans pos =
37− listToMaybe $ locationsAtPoint pos srcSpans
47+ -> Action (Maybe Location)
48+gotoDefinition ideOpts pkgState srcSpans pos =
49+ listToMaybe <$> locationsAtPoint ideOpts pkgState pos srcSpans
3850
3951-- | Synopsis for the name at a given position.
4052atPoint
@@ -74,10 +86,37 @@ atPoint tcs srcSpans pos = do
7486 Just name -> any (`isInfixOf` show name) ["==", "showsPrec"]
7587 Nothing -> False
7688
77−locationsAtPoint :: Position -> [SpanInfo] -> [Location]
78−locationsAtPoint pos = map srcSpanToLocation
79− . mapMaybe (SpanInfo.getSrcSpan . spaninfoSource)
80− . spansAtPoint pos
89+locationsAtPoint :: IdeOptions -> PackageDynFlags -> Position -> [SpanInfo] -> Action [Location]
90+locationsAtPoint IdeOptions{..} pkgState pos =
91+ fmap (map srcSpanToLocation) .
92+ mapMaybeM (getSpan . spaninfoSource) .
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
81120
82121spansAtPoint :: Position -> [SpanInfo] -> [SpanInfo]
83122spansAtPoint pos = filter atp where
src/Development/IDE/Functions/Compile.hs+9−2
@@ -29,6 +29,9 @@ import Development.IDE.Functions.SpanInfo
2929import Development.IDE.UtilGHC
3030import Development.IDE.Types.Options
3131
32+import HieBin
33+import HieAst
34+
3235import GHC hiding (parseModule, typecheckModule)
3336import qualified Parser
3437import Lexer
@@ -270,9 +273,13 @@ mkTcModuleResult (WriteInterface writeIface) tcm = do
270273 session <- getSession
271274 nc <- liftIO $ readIORef (hsc_NC session)
272275 (iface,_) <- liftIO $ mkIfaceTc session Nothing Sf_None details tcGblEnv
273− when writeIface $
274− liftIO $ do
276+ liftIO $ when writeIface $ do
275277 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
276283 let mod_info = HomeModInfo iface details Nothing
277284 origNc = nsNames nc
278285 case lookupModuleEnv origNc (tcmModule tcm) of
src/Development/IDE/State/RuleTypes.hs+17
@@ -24,6 +24,7 @@ import Development.Shake hiding (Env, newCache)
2424import GHC.Generics (Generic)
2525
2626import GHC
27+import HieTypes
2728import Module
2829
2930import Development.IDE.Types.SpanInfo
@@ -73,6 +74,9 @@ type instance RuleResult GetLocatedImports = [(Located ModuleName, Maybe Import)
7374-- we can only report diagnostics for the current file.
7475type instance RuleResult ReportImportCycles = ()
7576
77+-- | Read the given HIE file.
78+type instance RuleResult GetHieFile = HieFile
79+
7680
7781data OfInterest = OfInterest
7882 deriving (Eq, Show, Typeable, Generic)
@@ -129,6 +133,13 @@ data LoadPackageState = LoadPackageState
129133instance Hashable LoadPackageState
130134instance NFData LoadPackageState
131135
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+
132143------------------------------------------------------------
133144-- Orphan Instances
134145
@@ -167,3 +178,9 @@ instance Show LoadPackageResult where
167178
168179instance NFData LoadPackageResult where
169180 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
4646import Development.IDE.State.RuleTypes
4747
4848import GHC
49+import HieBin
4950import UniqSupply
5051import Module as M
52+import NameCache
5153
5254import qualified Development.IDE.Functions.AtPoint as AtPoint
5355import Development.IDE.State.Service
@@ -151,7 +153,9 @@ getAtPointForFile file pos = do
151153getDefinitionForFile :: FilePath -> Position -> ExceptT [FileDiagnostic] Action (Maybe Location)
152154getDefinitionForFile file pos = do
153155 spans <- useE GetSpanInfo file
154− return $ AtPoint.gotoDefinition spans pos
156+ pkgState <- useE LoadPackageState ""
157+ opts <- lift getOpts
158+ lift $ AtPoint.gotoDefinition opts pkgState spans pos
155159
156160getOpts :: Action Compile.IdeOptions
157161getOpts = envOptions <$> getServiceEnv
@@ -331,6 +335,13 @@ loadPackageStateRule =
331335 liftIO $ Compile.generatePackageState
332336 (Compile.optPackageDbs opts) (Compile.optHideAllPkgs opts) (Compile.optPackageImports opts)
333337
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+
334345-- | A rule that wires per-file rules together
335346mainRule :: Rules ()
336347mainRule = do
@@ -344,6 +355,7 @@ mainRule = do
344355 generateCoreRule
345356 loadPackageStateRule
346357 loadPackageRule
358+ getHieFileRule
347359
348360------------------------------------------------------------
349361
src/Development/IDE/Types/Options.hs+14
@@ -6,6 +6,7 @@
66-- | Options
77module Development.IDE.Types.Options
88 ( IdeOptions(..)
9+ , IdePkgLocationOptions(..)
910 ) where
1011
1112import Development.IDE.UtilGHC
@@ -18,6 +19,7 @@ data IdeOptions = IdeOptions
1819 , optRunGhcSession :: forall a. Maybe ParsedModule -> PackageDynFlags -> Ghc a -> IO a
1920 -- ^ Setup a GHC session using a given package state. If a `ParsedModule` is supplied,
2021 -- the import path should be setup for that module.
22+ , optPkgLocationOpts :: IdePkgLocationOptions
2123 , optWriteIface :: Bool
2224
2325 , optMbPackageName :: Maybe String
@@ -29,3 +31,15 @@ data IdeOptions = IdeOptions
2931 , optThreads :: Int
3032 , optShakeProfiling :: Maybe FilePath
3133 }
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 @@
1010-- * Call setSessionDynFlags, use modifyDynFlags instead. It's faster and avoids loading packages.
1111module Development.IDE.UtilGHC(
1212 PackageDynFlags(..), setPackageDynFlags, getPackageDynFlags,
13+ lookupPackageConfig,
1314 modifyDynFlags,
1415 setPackageImports,
1516 setPackageDbs,
@@ -86,6 +87,16 @@ getPackageDynFlags DynFlags{..} = PackageDynFlags
8687 , pdfThisUnitIdInsts = thisUnitIdInsts_
8788 }
8889
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+
89100
90101prettyPrint :: Outputable a => a -> String
91102prettyPrint = showSDoc fakeDynFlags . ppr