Skip to content

Commit 47cf162

Browse files
authored
Some fixes for multi component stuff (#3686)
* Only bring units actually depended on into scope on 9.4+ * Cabal uses `main` as the unit id of all executable packages. This confused multi component sessions. Solution: include the hash of the options in the unit id when the unit id is called "main". Fixes #3513 * Fix call hierarchy tests
1 parent c9519af commit 47cf162

File tree

3 files changed

+72
-16
lines changed

3 files changed

+72
-16
lines changed

ghcide/session-loader/Development/IDE/Session.hs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,25 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} dir = do
484484
packageSetup (hieYaml, cfp, opts, libDir) = do
485485
-- Parse DynFlags for the newly discovered component
486486
hscEnv <- emptyHscEnv ideNc libDir
487-
(df, targets) <- evalGhcEnv hscEnv $ setOptions opts (hsc_dflags hscEnv)
487+
(df', targets) <- evalGhcEnv hscEnv $ setOptions opts (hsc_dflags hscEnv)
488+
let df =
489+
#if MIN_VERSION_ghc(9,3,0)
490+
case unitIdString (homeUnitId_ df') of
491+
-- cabal uses main for the unit id of all executable packages
492+
-- This makes multi-component sessions confused about what
493+
-- options to use for that component.
494+
-- Solution: hash the options and use that as part of the unit id
495+
-- This works because there won't be any dependencies on the
496+
-- executable unit.
497+
"main" ->
498+
let hash = B.unpack $ B16.encode $ H.finalize $ H.updates H.init (map B.pack $ componentOptions opts)
499+
hashed_uid = Compat.toUnitId (Compat.stringToUnit ("main-"++hash))
500+
in setHomeUnitId_ hashed_uid df'
501+
_ -> df'
502+
#else
503+
df'
504+
#endif
505+
488506
let deps = componentDependencies opts ++ maybeToList hieYaml
489507
dep_info <- getDependencyInfo deps
490508
-- Now lookup to see whether we are combining with an existing HscEnv
@@ -499,6 +517,7 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} dir = do
499517
-- We will modify the unitId and DynFlags used for
500518
-- compilation but these are the true source of
501519
-- information.
520+
502521
new_deps = RawComponentInfo (homeUnitId_ df) df targets cfp opts dep_info
503522
: maybe [] snd oldDeps
504523
-- Get all the unit-ids for things in this component

ghcide/src/Development/IDE/Import/FindImports.hs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import Data.Maybe
2929
import System.FilePath
3030
#if MIN_VERSION_ghc(9,3,0)
3131
import GHC.Types.PkgQual
32+
import GHC.Unit.State
3233
#endif
3334

3435
data Import
@@ -135,25 +136,45 @@ locateModule env comp_info exts targetFor modName mbPkgName isSource = do
135136
#else
136137
Nothing -> do
137138
#endif
139+
140+
mbFile <- locateModuleFile ((homeUnitId_ dflags, importPaths dflags) : other_imports) exts targetFor isSource $ unLoc modName
141+
case mbFile of
142+
Nothing -> lookupInPackageDB env
143+
Just (uid, file) -> toModLocation uid file
144+
where
145+
dflags = hsc_dflags env
146+
import_paths = mapMaybe (mkImportDirs env) comp_info
147+
other_imports =
148+
#if MIN_VERSION_ghc(9,4,0)
149+
-- On 9.4+ instead of bringing all the units into scope, only bring into scope the units
150+
-- this one depends on
151+
-- This way if you have multiple units with the same module names, we won't get confused
152+
-- For example if unit a imports module M from unit B, when there is also a module M in unit C,
153+
-- and unit a only depends on unit b, without this logic there is the potential to get confused
154+
-- about which module unit a imports.
155+
-- Without multi-component support it is hard to recontruct the dependency environment so
156+
-- unit a will have both unit b and unit c in scope.
157+
map (\uid -> (uid, importPaths (homeUnitEnv_dflags (ue_findHomeUnitEnv uid ue)))) hpt_deps
158+
ue = hsc_unit_env env
159+
units = homeUnitEnv_units $ ue_findHomeUnitEnv (homeUnitId_ dflags) ue
160+
hpt_deps :: [UnitId]
161+
hpt_deps = homeUnitDepends units
162+
#else
163+
import_paths'
164+
#endif
165+
138166
-- first try to find the module as a file. If we can't find it try to find it in the package
139167
-- database.
140168
-- Here the importPaths for the current modules are added to the front of the import paths from the other components.
141169
-- This is particularly important for Paths_* modules which get generated for every component but unless you use it in
142170
-- each component will end up being found in the wrong place and cause a multi-cradle match failure.
143-
let import_paths' =
171+
import_paths' =
144172
#if MIN_VERSION_ghc(9,3,0)
145173
import_paths
146174
#else
147175
map snd import_paths
148176
#endif
149177

150-
mbFile <- locateModuleFile ((homeUnitId_ dflags, importPaths dflags) : import_paths') exts targetFor isSource $ unLoc modName
151-
case mbFile of
152-
Nothing -> lookupInPackageDB env
153-
Just (uid, file) -> toModLocation uid file
154-
where
155-
dflags = hsc_dflags env
156-
import_paths = mapMaybe (mkImportDirs env) comp_info
157178
toModLocation uid file = liftIO $ do
158179
loc <- mkHomeModLocation dflags (unLoc modName) (fromNormalizedFilePath file)
159180
#if MIN_VERSION_ghc(9,0,0)

plugins/hls-call-hierarchy-plugin/test/Main.hs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{-# LANGUAGE LambdaCase #-}
2+
{-# LANGUAGE RankNTypes #-}
23
{-# LANGUAGE OverloadedStrings #-}
34
{-# LANGUAGE StandaloneDeriving #-}
45
{-# LANGUAGE TupleSections #-}
@@ -504,25 +505,40 @@ outgoingCallMultiFileTestCase filepath queryX queryY mp =
504505
_ -> liftIO $ assertFailure "Not one element"
505506
closeDoc doc
506507

507-
oneCaseWithCreate :: T.Text -> Int -> Int -> (Uri -> CallHierarchyItem) -> Assertion
508+
oneCaseWithCreate :: T.Text -> Int -> Int -> (Uri -> CallHierarchyItem -> Assertion) -> Assertion
508509
oneCaseWithCreate contents queryX queryY expected = withCanonicalTempDir $ \dir ->
509510
runSessionWithServer plugin dir $ do
510511
doc <- createDoc "A.hs" "haskell" contents
511512
waitForIndex (dir </> "A.hs")
512513
Test.prepareCallHierarchy (mkPrepareCallHierarchyParam doc queryX queryY) >>=
513514
\case
514-
[item] -> liftIO $ item @?= expected (doc ^. L.uri)
515+
[item] -> liftIO $ expected (doc ^. L.uri) item
515516
res -> liftIO $ assertFailure "Not one element"
516517
closeDoc doc
517518

518-
mkCallHierarchyItem' :: String -> T.Text -> SymbolKind -> Range -> Range -> Uri -> CallHierarchyItem
519-
mkCallHierarchyItem' prefix name kind range selRange uri =
520-
CallHierarchyItem name kind Nothing (Just "Main") uri range selRange (Just v)
519+
mkCallHierarchyItem' :: String -> T.Text -> SymbolKind -> Range -> Range -> Uri -> CallHierarchyItem -> Assertion
520+
mkCallHierarchyItem' prefix name kind range selRange uri c@(CallHierarchyItem name' kind' tags' detail' uri' range' selRange' xdata') = do
521+
assertHierarchyItem name name'
522+
assertHierarchyItem kind kind'
523+
assertHierarchyItem tags tags'
524+
assertHierarchyItem detail detail'
525+
assertHierarchyItem uri uri'
526+
assertHierarchyItem range range'
527+
assertHierarchyItem selRange selRange'
528+
case xdata' of
529+
Nothing -> assertFailure ("In " ++ show c ++ ", got Nothing for data but wanted " ++ show xdata)
530+
Just v -> case fromJSON v of
531+
Success v -> assertBool ("In " ++ show c ++ " wanted data prefix: " ++ show xdata) (xdata `T.isPrefixOf` v)
532+
Error err -> assertFailure ("In " ++ show c ++ " wanted data prefix: " ++ show xdata ++ " but json parsing failed with " ++ show err)
521533
where
522-
v = toJSON $ prefix <> ":" <> T.unpack name <> ":Main:main"
534+
tags = Nothing
535+
detail = Just "Main"
536+
assertHierarchyItem :: forall a. (Eq a, Show a) => a -> a -> Assertion
537+
assertHierarchyItem = assertEqual ("In " ++ show c ++ ", got unexpected value for field")
538+
xdata = T.pack prefix <> ":" <> name <> ":Main:main"
523539

524540
mkCallHierarchyItemC, mkCallHierarchyItemT, mkCallHierarchyItemV ::
525-
T.Text -> SymbolKind -> Range -> Range -> Uri -> CallHierarchyItem
541+
T.Text -> SymbolKind -> Range -> Range -> Uri -> CallHierarchyItem -> Assertion
526542
mkCallHierarchyItemC = mkCallHierarchyItem' "c"
527543
mkCallHierarchyItemT = mkCallHierarchyItem' "t"
528544
mkCallHierarchyItemV = mkCallHierarchyItem' "v"

0 commit comments

Comments
 (0)