-
-
Notifications
You must be signed in to change notification settings - Fork 436
Expand file tree
/
Copy pathFindImports.hs
More file actions
229 lines (207 loc) · 9.91 KB
/
FindImports.hs
File metadata and controls
229 lines (207 loc) · 9.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
-- Copyright (c) 2019 The DAML Authors. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE CPP #-}
module Development.IDE.Import.FindImports
( locateModule
, locateModuleFile
, Import(..)
, ArtifactsLocation(..)
, modSummaryToArtifactsLocation
, isBootLocation
, mkImportDirs
) where
import Control.DeepSeq
import Control.Monad.Extra
import Control.Monad.IO.Class
import Data.List (find, isSuffixOf)
import Data.Maybe
import qualified Data.Set as S
import Development.IDE.GHC.Compat as Compat
import Development.IDE.GHC.Error as ErrUtils
import Development.IDE.GHC.Orphans ()
import Development.IDE.Types.Diagnostics
import Development.IDE.Types.Location
import GHC.Types.PkgQual
import GHC.Unit.State
import System.FilePath
#if MIN_VERSION_ghc(9,11,0)
import GHC.Driver.DynFlags
#endif
data Import
= FileImport !ArtifactsLocation
| PackageImport
deriving (Show)
data ArtifactsLocation = ArtifactsLocation
{ artifactFilePath :: !NormalizedFilePath
, artifactModLocation :: !(Maybe ModLocation)
, artifactIsSource :: !Bool -- ^ 'True' for a real Haskell source file ('HsSrcFile');
-- 'False' for a boot ('HsBootFile') or signature ('HsigFile') file.
, artifactModule :: !(Maybe Module)
} deriving Show
instance NFData ArtifactsLocation where
rnf ArtifactsLocation{..} = rnf artifactFilePath `seq` rwhnf artifactModLocation `seq` rnf artifactIsSource `seq` rnf artifactModule
isBootLocation :: ArtifactsLocation -> Bool
isBootLocation = not . artifactIsSource
instance NFData Import where
rnf (FileImport x) = rnf x
rnf PackageImport = ()
modSummaryToArtifactsLocation :: NormalizedFilePath -> Maybe ModSummary -> ArtifactsLocation
modSummaryToArtifactsLocation nfp ms = ArtifactsLocation nfp (ms_location <$> ms) source mbMod
where
isSource HsSrcFile = True
isSource _ = False
source = case ms of
Nothing -> "-boot" `isSuffixOf` fromNormalizedFilePath nfp
Just modSum -> isSource (ms_hsc_src modSum)
mbMod = ms_mod <$> ms
data LocateResult
= LocateNotFound
| LocateFoundReexport UnitId
| LocateFoundFile UnitId NormalizedFilePath
-- | locate a module in the file system. Where we go from *daml to Haskell
locateModuleFile :: MonadIO m
=> [(UnitId, [FilePath], S.Set ModuleName)]
-> [String]
-> (ModuleName -> NormalizedFilePath -> m (Maybe NormalizedFilePath))
-> Bool
-> ModuleName
-> m LocateResult
locateModuleFile import_dirss exts targetFor isSource modName = do
let candidates import_dirs =
[ toNormalizedFilePath' (prefix </> moduleNameSlashes modName <.> maybeBoot ext)
| prefix <- import_dirs , ext <- exts]
mf <- firstJustM go (concat [map (uid,) (candidates dirs) | (uid, dirs, _) <- import_dirss])
case mf of
Nothing ->
case find (\(_ , _, reexports) -> S.member modName reexports) import_dirss of
Just (uid,_,_) -> pure $ LocateFoundReexport uid
Nothing -> pure LocateNotFound
Just (uid,file) -> pure $ LocateFoundFile uid file
where
go (uid, candidate) = fmap ((uid,) <$>) $ targetFor modName candidate
maybeBoot ext
| isSource = ext ++ "-boot"
| otherwise = ext
-- | This function is used to map a package name to a set of import paths.
-- It only returns Just for unit-ids which are possible to import into the
-- current module. In particular, it will return Nothing for 'main' components
-- as they can never be imported into another package.
mkImportDirs :: HscEnv -> (UnitId, DynFlags) -> Maybe (UnitId, ([FilePath], S.Set ModuleName))
#if MIN_VERSION_ghc(9,11,0)
mkImportDirs _env (i, flags) = Just (i, (importPaths flags, S.fromList $ map reexportTo $ reexportedModules flags))
#else
mkImportDirs _env (i, flags) = Just (i, (importPaths flags, reexportedModules flags))
#endif
-- | locate a module in either the file system or the package database. Where we go from *daml to
-- Haskell
locateModule
:: MonadIO m
=> HscEnv
-> [(UnitId, DynFlags)] -- ^ Import directories
-> [String] -- ^ File extensions
-> (ModuleName -> NormalizedFilePath -> m (Maybe NormalizedFilePath)) -- ^ does file exist predicate
-> Located ModuleName -- ^ Module name
-> PkgQual -- ^ Package name
-> Bool -- ^ Is boot module
-> m (Either [FileDiagnostic] Import)
locateModule env comp_info exts targetFor modName mbPkgName isSource = do
case mbPkgName of
-- 'ThisPkg' just means some home module, not the current unit
ThisPkg uid
| Just (dirs, reexports) <- lookup uid import_paths
-> lookupLocal uid dirs reexports
| otherwise -> return $ Left $ notFoundErr env modName $ LookupNotFound []
-- if a package name is given we only go look for a package
OtherPkg uid
| Just (dirs, reexports) <- lookup uid import_paths
-> lookupLocal uid dirs reexports
| otherwise -> lookupInPackageDB
NoPkgQual -> do
-- Reexports for current unit have to be empty because they only apply to other units depending on the
-- current unit. If we set the reexports to be the actual reexports then we risk looping forever trying
-- to find the module from the perspective of the current unit.
mbFile <- locateModuleFile ((homeUnitId_ dflags, importPaths dflags, S.empty) : other_imports) exts targetFor isSource $ unLoc modName
case mbFile of
LocateNotFound -> lookupInPackageDB
-- Lookup again with the perspective of the unit reexporting the file
LocateFoundReexport uid -> locateModule (hscSetActiveUnitId uid env) comp_info exts targetFor modName noPkgQual isSource
LocateFoundFile uid file -> toModLocation uid file
where
dflags = hsc_dflags env
import_paths = mapMaybe (mkImportDirs env) comp_info
other_imports =
-- Instead of bringing all the units into scope, only bring into scope the units
-- this one depends on.
-- This way if you have multiple units with the same module names, we won't get confused
-- For example if unit a imports module M from unit B, when there is also a module M in unit C,
-- and unit a only depends on unit b, without this logic there is the potential to get confused
-- about which module unit a imports.
-- Without multi-component support it is hard to recontruct the dependency environment so
-- unit a will have both unit b and unit c in scope.
#if MIN_VERSION_ghc(9,11,0)
map (\uid -> let this_df = homeUnitEnv_dflags (ue_findHomeUnitEnv uid ue) in (uid, importPaths this_df, S.fromList $ map reexportTo $ reexportedModules this_df)) hpt_deps
#else
map (\uid -> let this_df = homeUnitEnv_dflags (ue_findHomeUnitEnv uid ue) in (uid, importPaths this_df, reexportedModules this_df)) hpt_deps
#endif
ue = hsc_unit_env env
units = homeUnitEnv_units $ ue_findHomeUnitEnv (homeUnitId_ dflags) ue
hpt_deps :: [UnitId]
hpt_deps = homeUnitDepends units
toModLocation uid file = liftIO $ do
loc <- mkHomeModLocation dflags (unLoc modName) (fromNormalizedFilePath file)
let genMod = mkModule (RealUnit $ Definite uid) (unLoc modName) -- TODO support backpack holes
return $ Right $ FileImport $ ArtifactsLocation file (Just loc) (not isSource) (Just genMod)
lookupLocal uid dirs reexports = do
mbFile <- locateModuleFile [(uid, dirs, reexports)] exts targetFor isSource $ unLoc modName
case mbFile of
LocateNotFound -> return $ Left $ notFoundErr env modName $ LookupNotFound []
-- Lookup again with the perspective of the unit reexporting the file
LocateFoundReexport uid' -> locateModule (hscSetActiveUnitId uid' env) comp_info exts targetFor modName noPkgQual isSource
LocateFoundFile uid' file -> toModLocation uid' file
lookupInPackageDB = do
case Compat.lookupModuleWithSuggestions env (unLoc modName) mbPkgName of
LookupFound _m _pkgConfig -> return $ Right PackageImport
reason -> return $ Left $ notFoundErr env modName reason
-- | Don't call this on a found module.
notFoundErr :: HscEnv -> Located ModuleName -> LookupResult -> [FileDiagnostic]
notFoundErr env modName reason =
mkError' $ ppr' $ cannotFindModule env modName0 $ lookupToFindResult reason
where
dfs = hsc_dflags env
mkError' doc = diagFromString "not found" DiagnosticSeverity_Error (Compat.getLoc modName) doc Nothing
modName0 = unLoc modName
ppr' = showSDoc dfs
-- We convert the lookup result to a find result to reuse GHC's cannotFindModule pretty printer.
lookupToFindResult =
\case
LookupFound _m _pkgConfig ->
pprPanic "Impossible: called lookupToFind on found module." (ppr modName0)
LookupMultiple rs -> FoundMultiple rs
LookupHidden pkg_hiddens mod_hiddens ->
notFound
{ fr_pkgs_hidden = map (moduleUnit . fst) pkg_hiddens
, fr_mods_hidden = map (moduleUnit . fst) mod_hiddens
}
LookupUnusable unusable ->
let unusables' = map get_unusable unusable
#if MIN_VERSION_ghc(9,6,4) && (!MIN_VERSION_ghc(9,8,1) || MIN_VERSION_ghc(9,8,2))
get_unusable (_m, ModUnusable r) = r
#else
get_unusable (m, ModUnusable r) = (moduleUnit m, r)
#endif
get_unusable (_, r) =
pprPanic "findLookupResult: unexpected origin" (ppr r)
in notFound {fr_unusables = unusables'}
LookupNotFound suggest ->
notFound {fr_suggestions = suggest}
notFound :: FindResult
notFound = NotFound
{ fr_paths = []
, fr_pkg = Nothing
, fr_pkgs_hidden = []
, fr_mods_hidden = []
, fr_unusables = []
, fr_suggestions = []
}
noPkgQual :: PkgQual
noPkgQual = NoPkgQual