Skip to content

Commit 9a7b0d2

Browse files
committed
Fix multi unit session when some packages have reexported modules.
If we are loading multiple home packages, we need to explicitly take reexports into account when searching for target files. If we can't find a module via the usual mean, but it is listed as a reexport of a unit in scope, we need to look for the module from the perspective of that unit. This is not necessary for non-home modules because GHC already handles this for modules in the package DB. Unfortunately we can't fix this in GHC 9.2 because it doesn't support multiple home units and we have no way of knowing if a unit reexports modules
1 parent 2156ac2 commit 9a7b0d2

File tree

12 files changed

+151
-24
lines changed

12 files changed

+151
-24
lines changed

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,7 @@ import Development.IDE.GHC.Compat.CmdLine
121121
import qualified Data.Set as OS
122122

123123
import GHC.Data.Bag
124-
import GHC.Driver.Env (hscSetActiveUnitId,
125-
hsc_all_home_unit_ids)
124+
import GHC.Driver.Env (hsc_all_home_unit_ids)
126125
import GHC.Driver.Errors.Types
127126
import GHC.Driver.Make (checkHomeUnitsClosed)
128127
import GHC.Types.Error (errMsgDiagnostic)

ghcide/src/Development/IDE/GHC/Compat/Env.hs

+13-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ module Development.IDE.GHC.Compat.Env (
5757
Development.IDE.GHC.Compat.Env.platformDefaultBackend,
5858
workingDirectory,
5959
setWorkingDirectory,
60+
hscSetActiveUnitId,
61+
reexportedModules,
6062
) where
6163

6264
import GHC (setInteractiveDynFlags)
@@ -78,10 +80,20 @@ import GHC.Utils.TmpFs
7880

7981
#if !MIN_VERSION_ghc(9,3,0)
8082
import GHC.Driver.Env (HscEnv, hsc_EPS)
83+
import qualified Data.Set as S
8184
#endif
8285

8386
#if MIN_VERSION_ghc(9,3,0)
84-
import GHC.Driver.Env (HscEnv)
87+
import GHC.Driver.Env (HscEnv, hscSetActiveUnitId)
88+
#endif
89+
90+
91+
#if !MIN_VERSION_ghc(9,3,0)
92+
hscSetActiveUnitId :: UnitId -> HscEnv -> HscEnv
93+
hscSetActiveUnitId _ env = env
94+
95+
reexportedModules :: HscEnv -> S.Set a
96+
reexportedModules _ = S.empty
8597
#endif
8698

8799
#if MIN_VERSION_ghc(9,3,0)

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

+48-21
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import Development.IDE.Types.Location
2323
-- standard imports
2424
import Control.Monad.Extra
2525
import Control.Monad.IO.Class
26-
import Data.List (isSuffixOf)
26+
import Data.List (isSuffixOf, find)
27+
import qualified Data.Set as S
2728
import Data.Maybe
2829
import System.FilePath
2930

@@ -70,19 +71,30 @@ modSummaryToArtifactsLocation nfp ms = ArtifactsLocation nfp (ms_location <$> ms
7071
Just modSum -> isSource (ms_hsc_src modSum)
7172
mbMod = ms_mod <$> ms
7273

74+
data LocateResult
75+
= LocateNotFound
76+
| LocateFoundReexport UnitId
77+
| LocateFoundFile UnitId NormalizedFilePath
78+
7379
-- | locate a module in the file system. Where we go from *daml to Haskell
7480
locateModuleFile :: MonadIO m
75-
=> [(UnitId, [FilePath])]
81+
=> [(UnitId, [FilePath], S.Set ModuleName)]
7682
-> [String]
7783
-> (ModuleName -> NormalizedFilePath -> m (Maybe NormalizedFilePath))
7884
-> Bool
7985
-> ModuleName
80-
-> m (Maybe (UnitId, NormalizedFilePath))
86+
-> m LocateResult
8187
locateModuleFile import_dirss exts targetFor isSource modName = do
8288
let candidates import_dirs =
8389
[ toNormalizedFilePath' (prefix </> moduleNameSlashes modName <.> maybeBoot ext)
8490
| prefix <- import_dirs , ext <- exts]
85-
firstJustM go (concat [map (uid,) (candidates dirs) | (uid, dirs) <- import_dirss])
91+
mf <- firstJustM go (concat [map (uid,) (candidates dirs) | (uid, dirs, _) <- import_dirss])
92+
case mf of
93+
Nothing ->
94+
case find (\(_ , _, reexports) -> S.member modName reexports) import_dirss of
95+
Just (uid,_,_) -> pure $ LocateFoundReexport uid
96+
Nothing -> pure $ LocateNotFound
97+
Just (uid,file) -> pure $ LocateFoundFile uid file
8698
where
8799
go (uid, candidate) = fmap ((uid,) <$>) $ targetFor modName candidate
88100
maybeBoot ext
@@ -94,11 +106,11 @@ locateModuleFile import_dirss exts targetFor isSource modName = do
94106
-- current module. In particular, it will return Nothing for 'main' components
95107
-- as they can never be imported into another package.
96108
#if MIN_VERSION_ghc(9,3,0)
97-
mkImportDirs :: HscEnv -> (UnitId, DynFlags) -> Maybe (UnitId, [FilePath])
98-
mkImportDirs _env (i, flags) = Just (i, importPaths flags)
109+
mkImportDirs :: HscEnv -> (UnitId, DynFlags) -> Maybe (UnitId, ([FilePath], S.Set ModuleName))
110+
mkImportDirs _env (i, flags) = Just (i, (importPaths flags, reexportedModules flags))
99111
#else
100-
mkImportDirs :: HscEnv -> (UnitId, DynFlags) -> Maybe (PackageName, (UnitId, [FilePath]))
101-
mkImportDirs env (i, flags) = (, (i, importPaths flags)) <$> getUnitName env i
112+
mkImportDirs :: HscEnv -> (UnitId, DynFlags) -> Maybe (PackageName, (UnitId, [FilePath], S.Set ModuleName))
113+
mkImportDirs env (i, flags) = (, (i, importPaths flags, S.empty)) <$> getUnitName env i
102114
#endif
103115

104116
-- | locate a module in either the file system or the package database. Where we go from *daml to
@@ -125,16 +137,16 @@ locateModule env comp_info exts targetFor modName mbPkgName isSource = do
125137
#else
126138
Just "this" -> do
127139
#endif
128-
lookupLocal (homeUnitId_ dflags) (importPaths dflags)
140+
lookupLocal (homeUnitId_ dflags) (importPaths dflags) S.empty
129141
-- if a package name is given we only go look for a package
130142
#if MIN_VERSION_ghc(9,3,0)
131143
OtherPkg uid
132-
| Just dirs <- lookup uid import_paths
133-
-> lookupLocal uid dirs
144+
| Just (dirs, reexports) <- lookup uid import_paths
145+
-> lookupLocal uid dirs reexports
134146
#else
135147
Just pkgName
136-
| Just (uid, dirs) <- lookup (PackageName pkgName) import_paths
137-
-> lookupLocal uid dirs
148+
| Just (uid, dirs, reexports) <- lookup (PackageName pkgName) import_paths
149+
-> lookupLocal uid dirs reexports
138150
#endif
139151
| otherwise -> lookupInPackageDB
140152
#if MIN_VERSION_ghc(9,3,0)
@@ -143,10 +155,15 @@ locateModule env comp_info exts targetFor modName mbPkgName isSource = do
143155
Nothing -> do
144156
#endif
145157

146-
mbFile <- locateModuleFile ((homeUnitId_ dflags, importPaths dflags) : other_imports) exts targetFor isSource $ unLoc modName
158+
-- Reexports for current unit have to be empty because they only apply to other units depending on the
159+
-- current unit. If we set the reexports to be the actual reexports then we risk looping forever trying
160+
-- to find the module from the perspective of the current unit.
161+
mbFile <- locateModuleFile ((homeUnitId_ dflags, importPaths dflags, S.empty) : other_imports) exts targetFor isSource $ unLoc modName
147162
case mbFile of
148-
Nothing -> lookupInPackageDB
149-
Just (uid, file) -> toModLocation uid file
163+
LocateNotFound -> lookupInPackageDB
164+
-- Lookup again with the perspective of the unit reexporting the file
165+
LocateFoundReexport uid -> locateModule (hscSetActiveUnitId uid env) comp_info exts targetFor modName noPkgQual isSource
166+
LocateFoundFile uid file -> toModLocation uid file
150167
where
151168
dflags = hsc_dflags env
152169
import_paths = mapMaybe (mkImportDirs env) comp_info
@@ -160,7 +177,7 @@ locateModule env comp_info exts targetFor modName mbPkgName isSource = do
160177
-- about which module unit a imports.
161178
-- Without multi-component support it is hard to recontruct the dependency environment so
162179
-- unit a will have both unit b and unit c in scope.
163-
map (\uid -> (uid, importPaths (homeUnitEnv_dflags (ue_findHomeUnitEnv uid ue)))) hpt_deps
180+
map (\uid -> let this_df = homeUnitEnv_dflags (ue_findHomeUnitEnv uid ue) in (uid, importPaths this_df, reexportedModules this_df)) hpt_deps
164181
ue = hsc_unit_env env
165182
units = homeUnitEnv_units $ ue_findHomeUnitEnv (homeUnitId_ dflags) ue
166183
hpt_deps :: [UnitId]
@@ -186,11 +203,13 @@ locateModule env comp_info exts targetFor modName mbPkgName isSource = do
186203
let genMod = mkModule (RealUnit $ Definite uid) (unLoc modName) -- TODO support backpack holes
187204
return $ Right $ FileImport $ ArtifactsLocation file (Just loc) (not isSource) (Just genMod)
188205

189-
lookupLocal uid dirs = do
190-
mbFile <- locateModuleFile [(uid, dirs)] exts targetFor isSource $ unLoc modName
206+
lookupLocal uid dirs reexports = do
207+
mbFile <- locateModuleFile [(uid, dirs, reexports)] exts targetFor isSource $ unLoc modName
191208
case mbFile of
192-
Nothing -> return $ Left $ notFoundErr env modName $ LookupNotFound []
193-
Just (uid', file) -> toModLocation uid' file
209+
LocateNotFound -> return $ Left $ notFoundErr env modName $ LookupNotFound []
210+
-- Lookup again with the perspective of the unit reexporting the file
211+
LocateFoundReexport uid' -> locateModule (hscSetActiveUnitId uid' env) comp_info exts targetFor modName noPkgQual isSource
212+
LocateFoundFile uid' file -> toModLocation uid' file
194213

195214
lookupInPackageDB = do
196215
case Compat.lookupModuleWithSuggestions env (unLoc modName) mbPkgName of
@@ -239,3 +258,11 @@ notFound = NotFound
239258
, fr_unusables = []
240259
, fr_suggestions = []
241260
}
261+
262+
#if MIN_VERSION_ghc(9,3,0)
263+
noPkgQual :: PkgQual
264+
noPkgQual = NoPkgQual
265+
#else
266+
noPkgQual :: Maybe a
267+
noPkgQual = Nothing
268+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-this-package-name
2+
a
3+
-working-dir
4+
a
5+
-fbuilding-cabal-package
6+
-O0
7+
-i.
8+
-this-unit-id
9+
a-1.0.0-inplace
10+
-hide-all-packages
11+
-Wmissing-home-modules
12+
-no-user-package-db
13+
-package
14+
base
15+
-package
16+
text
17+
-XHaskell98
18+
A
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module A(foo) where
2+
import Data.Text
3+
foo = ()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-this-package-name
2+
b
3+
-working-dir
4+
b
5+
-fbuilding-cabal-package
6+
-O0
7+
-i
8+
-i.
9+
-this-unit-id
10+
b-1.0.0-inplace
11+
-hide-all-packages
12+
-Wmissing-home-modules
13+
-no-user-package-db
14+
-package-id
15+
a-1.0.0-inplace
16+
-reexported-module
17+
A
18+
-package
19+
base
20+
-XHaskell98
21+
B
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module B(module B) where
2+
import A
3+
qux = foo
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-this-package-name
2+
c
3+
-working-dir
4+
c
5+
-fbuilding-cabal-package
6+
-O0
7+
-i
8+
-i.
9+
-this-unit-id
10+
c-1.0.0-inplace
11+
-hide-all-packages
12+
-Wmissing-home-modules
13+
-no-user-package-db
14+
-package-id
15+
b-1.0.0-inplace
16+
-package
17+
base
18+
-XHaskell98
19+
C
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module C(module C) where
2+
import A
3+
import B
4+
cux = foo `seq` qux
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
packages: a b c
2+
multi-repl: True
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
cradle:
2+
direct:
3+
arguments: ["-unit" ,"@a-1.0.0-inplace"
4+
,"-unit" ,"@b-1.0.0-inplace"
5+
,"-unit" ,"@c-1.0.0-inplace"
6+
]

ghcide/test/exe/CradleTests.hs

+13
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ tests = testGroup "cradle"
4444
,ignoreFor (BrokenForGHC [GHC92]) "multiple units not supported on 9.2"
4545
$ testGroup "multi-unit" (multiTests "multi-unit")
4646
,testGroup "sub-directory" [simpleSubDirectoryTest]
47+
,ignoreFor (BrokenForGHC [GHC92]) "multiple units not supported on 9.2"
48+
$ testGroup "multi-unit-rexport" [multiRexportTest]
4749
]
4850

4951
loadCradleOnlyonce :: TestTree
@@ -187,6 +189,17 @@ simpleMultiDefTest variant = testCase (multiTestName variant "def-test") $ runWi
187189
checkDefs locs (pure [fooL])
188190
expectNoMoreDiagnostics 0.5
189191

192+
multiRexportTest :: TestTree
193+
multiRexportTest =
194+
testCase "multi-unit-reexport-test" $ runWithExtraFiles "multi-unit-reexport" $ \dir -> do
195+
let cPath = dir </> "c/C.hs"
196+
cdoc <- openDoc cPath "haskell"
197+
WaitForIdeRuleResult {} <- waitForAction "TypeCheck" cdoc
198+
locs <- getDefinitions cdoc (Position 3 7)
199+
let aPath = dir </> "a/A.hs"
200+
let fooL = mkL (filePathToUri aPath) 2 0 2 3
201+
checkDefs locs (pure [fooL])
202+
expectNoMoreDiagnostics 0.5
190203

191204
sessionDepsArePickedUp :: TestTree
192205
sessionDepsArePickedUp = testSession'

0 commit comments

Comments
 (0)