on master help sign in
Guess all imports (#459)
da92de2Pepe Iborra
ghcide.cabal+2
@@ -139,6 +139,8 @@ library
139 Development.IDE.Spans.Documentation
139 Development.IDE.Spans.Documentation
140 Development.IDE.Spans.Type
140 Development.IDE.Spans.Type
141 Development.IDE.Plugin.CodeAction.PositionIndexed
141 Development.IDE.Plugin.CodeAction.PositionIndexed
142+ Development.IDE.Plugin.CodeAction.Rules
143+ Development.IDE.Plugin.CodeAction.RuleTypes
142 Development.IDE.Plugin.Completions.Logic
144 Development.IDE.Plugin.Completions.Logic
143 Development.IDE.Plugin.Completions.Types
145 Development.IDE.Plugin.Completions.Types
144 ghc-options: -Wall -Wno-name-shadowing
146 ghc-options: -Wall -Wno-name-shadowing
src/Development/IDE/Core/Shake.hs+4−4
@@ -488,13 +488,13 @@ uses_ :: IdeRule k v => k -> [NormalizedFilePath] -> Action [v]
488uses_ key files = do
488uses_ key files = do
489 res <- uses key files
489 res <- uses key files
490 case sequence res of
490 case sequence res of
491− Nothing -> liftIO $ throwIO BadDependency
491+ Nothing -> liftIO $ throwIO $ BadDependency (show key)
492 Just v -> return v
492 Just v -> return v
493
493
494
494
495-- | When we depend on something that reported an error, and we fail as a direct result, throw BadDependency
495-- | When we depend on something that reported an error, and we fail as a direct result, throw BadDependency
496-- which short-circuits the rest of the action
496-- which short-circuits the rest of the action
497−data BadDependency = BadDependency deriving Show
497+data BadDependency = BadDependency String deriving Show
498instance Exception BadDependency
498instance Exception BadDependency
499
499
500isBadDependency :: SomeException -> Bool
500isBadDependency :: SomeException -> Bool
@@ -659,12 +659,12 @@ defineOnDisk act = addBuiltinRule noLint noIdentity $
659needOnDisk :: (Shake.ShakeValue k, RuleResult k ~ ()) => k -> NormalizedFilePath -> Action ()
659needOnDisk :: (Shake.ShakeValue k, RuleResult k ~ ()) => k -> NormalizedFilePath -> Action ()
660needOnDisk k file = do
660needOnDisk k file = do
661 successfull <- apply1 (QDisk k file)
661 successfull <- apply1 (QDisk k file)
662− liftIO $ unless successfull $ throwIO BadDependency
662+ liftIO $ unless successfull $ throwIO $ BadDependency (show k)
663
663
664needOnDisks :: (Shake.ShakeValue k, RuleResult k ~ ()) => k -> [NormalizedFilePath] -> Action ()
664needOnDisks :: (Shake.ShakeValue k, RuleResult k ~ ()) => k -> [NormalizedFilePath] -> Action ()
665needOnDisks k files = do
665needOnDisks k files = do
666 successfulls <- apply $ map (QDisk k) files
666 successfulls <- apply $ map (QDisk k) files
667− liftIO $ unless (and successfulls) $ throwIO BadDependency
667+ liftIO $ unless (and successfulls) $ throwIO $ BadDependency (show k)
668
668
669toShakeValue :: (BS.ByteString -> ShakeValue) -> Maybe BS.ByteString -> ShakeValue
669toShakeValue :: (BS.ByteString -> ShakeValue) -> Maybe BS.ByteString -> ShakeValue
670toShakeValue = maybe ShakeNoCutoff
670toShakeValue = maybe ShakeNoCutoff
src/Development/IDE/GHC/Util.hs+21
@@ -9,6 +9,8 @@ module Development.IDE.GHC.Util(
9 runGhcEnv,
9 runGhcEnv,
10 -- * GHC wrappers
10 -- * GHC wrappers
11 prettyPrint,
11 prettyPrint,
12+ printRdrName,
13+ printName,
12 ParseResult(..), runParser,
14 ParseResult(..), runParser,
13 lookupPackageConfig,
15 lookupPackageConfig,
14 moduleImportPath,
16 moduleImportPath,
@@ -98,6 +100,16 @@ runParser flags str parser = unP parser parseState
98prettyPrint :: Outputable a => a -> String
100prettyPrint :: Outputable a => a -> String
99prettyPrint = showSDoc unsafeGlobalDynFlags . ppr
101prettyPrint = showSDoc unsafeGlobalDynFlags . ppr
100
102
103+-- | Pretty print a 'RdrName' wrapping operators in parens
104+printRdrName :: RdrName -> String
105+printRdrName name = showSDocUnsafe $ parenSymOcc rn (ppr rn)
106+ where
107+ rn = rdrNameOcc name
108+
109+-- | Pretty print a 'Name' wrapping operators in parens
110+printName :: Name -> String
111+printName = printRdrName . nameRdrName
112+
101-- | Run a 'Ghc' monad value using an existing 'HscEnv'. Sets up and tears down all the required
113-- | Run a 'Ghc' monad value using an existing 'HscEnv'. Sets up and tears down all the required
102-- pieces, but designed to be more efficient than a standard 'runGhc'.
114-- pieces, but designed to be more efficient than a standard 'runGhc'.
103runGhcEnv :: HscEnv -> Ghc a -> IO a
115runGhcEnv :: HscEnv -> Ghc a -> IO a
@@ -151,6 +163,15 @@ instance Eq HscEnvEq where
151instance NFData HscEnvEq where
163instance NFData HscEnvEq where
152 rnf (HscEnvEq a b) = rnf (hashUnique a) `seq` b `seq` ()
164 rnf (HscEnvEq a b) = rnf (hashUnique a) `seq` b `seq` ()
153
165
166+instance Hashable HscEnvEq where
167+ hashWithSalt salt (HscEnvEq u _) = hashWithSalt salt u
168+
169+-- Fake instance needed to persuade Shake to accept this type as a key.
170+-- No harm done as ghcide never persists these keys currently
171+instance Binary HscEnvEq where
172+ put _ = error "not really"
173+ get = error "not really"
174+
154-- | Read a UTF8 file, with lenient decoding, so it will never raise a decoding error.
175-- | Read a UTF8 file, with lenient decoding, so it will never raise a decoding error.
155readFileUtf8 :: FilePath -> IO T.Text
176readFileUtf8 :: FilePath -> IO T.Text
156readFileUtf8 f = T.decodeUtf8With T.lenientDecode <$> BS.readFile f
177readFileUtf8 f = T.decodeUtf8With T.lenientDecode <$> BS.readFile f
src/Development/IDE/Plugin.hs+5−2
@@ -1,5 +1,5 @@
1
1
2−module Development.IDE.Plugin(Plugin(..), codeActionPlugin) where
2+module Development.IDE.Plugin(Plugin(..), codeActionPlugin, codeActionPluginWithRules) where
3
3
4import Data.Default
4import Data.Default
5import Development.Shake
5import Development.Shake
@@ -27,7 +27,10 @@ instance Monoid (Plugin c) where
27
27
28
28
29codeActionPlugin :: (LSP.LspFuncs c -> IdeState -> TextDocumentIdentifier -> Range -> CodeActionContext -> IO (Either ResponseError [CAResult])) -> Plugin c
29codeActionPlugin :: (LSP.LspFuncs c -> IdeState -> TextDocumentIdentifier -> Range -> CodeActionContext -> IO (Either ResponseError [CAResult])) -> Plugin c
30+codeActionPlugin = codeActionPluginWithRules mempty
31+
32+codeActionPluginWithRules :: Rules () -> (LSP.LspFuncs c -> IdeState -> TextDocumentIdentifier -> Range -> CodeActionContext -> IO (Either ResponseError [CAResult])) -> Plugin c
30−codeActionPlugin f = Plugin mempty $ PartialHandlers $ \WithMessage{..} x -> return x{
33+codeActionPluginWithRules rr f = Plugin rr $ PartialHandlers $ \WithMessage{..} x -> return x{
31 LSP.codeActionHandler = withResponse RspCodeAction g
34 LSP.codeActionHandler = withResponse RspCodeAction g
32 }
35 }
33 where
36 where
src/Development/IDE/Plugin/CodeAction.hs+47−66
@@ -8,7 +8,6 @@
8-- | Go to the definition of a variable.
8-- | Go to the definition of a variable.
9module Development.IDE.Plugin.CodeAction(plugin) where
9module Development.IDE.Plugin.CodeAction(plugin) where
10
10
11−import Avail (AvailInfo(Avail), AvailInfo(AvailTC), availNames)
12import Language.Haskell.LSP.Types
11import Language.Haskell.LSP.Types
13import Control.Monad (join)
12import Control.Monad (join)
14import Development.IDE.Plugin
13import Development.IDE.Plugin
@@ -21,8 +20,11 @@ import Development.IDE.GHC.Error
21import Development.IDE.GHC.Util
20import Development.IDE.GHC.Util
22import Development.IDE.LSP.Server
21import Development.IDE.LSP.Server
23import Development.IDE.Plugin.CodeAction.PositionIndexed
22import Development.IDE.Plugin.CodeAction.PositionIndexed
23+import Development.IDE.Plugin.CodeAction.RuleTypes
24+import Development.IDE.Plugin.CodeAction.Rules
24import Development.IDE.Types.Location
25import Development.IDE.Types.Location
25import Development.IDE.Types.Options
26import Development.IDE.Types.Options
27+import Development.Shake (Rules)
26import qualified Data.HashMap.Strict as Map
28import qualified Data.HashMap.Strict as Map
27import qualified Language.Haskell.LSP.Core as LSP
29import qualified Language.Haskell.LSP.Core as LSP
28import Language.Haskell.LSP.VFS
30import Language.Haskell.LSP.VFS
@@ -36,20 +38,18 @@ import Data.List.Extra
36import qualified Data.Text as T
38import qualified Data.Text as T
37import Data.Tuple.Extra ((&&&))
39import Data.Tuple.Extra ((&&&))
38import HscTypes
40import HscTypes
39−import OccName
40import Parser
41import Parser
41−import RdrName
42import Text.Regex.TDFA ((=~), (=~~))
42import Text.Regex.TDFA ((=~), (=~~))
43import Text.Regex.TDFA.Text()
43import Text.Regex.TDFA.Text()
44import Outputable (ppr, showSDocUnsafe)
44import Outputable (ppr, showSDocUnsafe)
45import DynFlags (xFlags, FlagSpec(..))
45import DynFlags (xFlags, FlagSpec(..))
46import GHC.LanguageExtensions.Type (Extension)
46import GHC.LanguageExtensions.Type (Extension)
47−import Data.IORef (readIORef)
48−import Name (isDataConName, nameModule_maybe, nameOccName)
49−import Packages (exposedModules, lookupPackage)
50
47
51plugin :: Plugin c
48plugin :: Plugin c
52−plugin = codeActionPlugin codeAction <> Plugin mempty setHandlersCodeLens
49+plugin = codeActionPluginWithRules rules codeAction <> Plugin mempty setHandlersCodeLens
50+
51+rules :: Rules ()
52+rules = rulePackageExports
53
53
54-- | Generate code actions.
54-- | Generate code actions.
55codeAction
55codeAction
@@ -65,15 +65,15 @@ codeAction lsp state (TextDocumentIdentifier uri) _range CodeActionContext{_diag
65 contents <- LSP.getVirtualFileFunc lsp $ toNormalizedUri uri
65 contents <- LSP.getVirtualFileFunc lsp $ toNormalizedUri uri
66 let text = Rope.toText . (_text :: VirtualFile -> Rope.Rope) <$> contents
66 let text = Rope.toText . (_text :: VirtualFile -> Rope.Rope) <$> contents
67 mbFile = toNormalizedFilePath <$> uriToFilePath uri
67 mbFile = toNormalizedFilePath <$> uriToFilePath uri
68− (ideOptions, parsedModule, env) <- runAction state $
68+ (ideOptions, parsedModule, join -> env) <- runAction state $
69 (,,) <$> getIdeOptions
69 (,,) <$> getIdeOptions
70− <*> getParsedModule `traverse` mbFile
70+ <*> getParsedModule `traverse` mbFile
71− <*> use_ GhcSession `traverse` mbFile
71+ <*> use GhcSession `traverse` mbFile
72+ pkgExports <- runAction state $ (useNoFile_ . PackageExports) `traverse` env
72 let dflags = hsc_dflags . hscEnv <$> env
73 let dflags = hsc_dflags . hscEnv <$> env
73− eps <- traverse readIORef (hsc_EPS . hscEnv <$> env)
74 pure $ Right
74 pure $ Right
75 [ CACodeAction $ CodeAction title (Just CodeActionQuickFix) (Just $ List [x]) (Just edit) Nothing
75 [ CACodeAction $ CodeAction title (Just CodeActionQuickFix) (Just $ List [x]) (Just edit) Nothing
76− | x <- xs, (title, tedit) <- suggestAction dflags eps ideOptions ( join parsedModule ) text x
76+ | x <- xs, (title, tedit) <- suggestAction dflags (fromMaybe mempty pkgExports) ideOptions ( join parsedModule ) text x
77 , let edit = WorkspaceEdit (Just $ Map.singleton uri $ List tedit) Nothing
77 , let edit = WorkspaceEdit (Just $ Map.singleton uri $ List tedit) Nothing
78 ]
78 ]
79
79
@@ -114,13 +114,13 @@ executeAddSignatureCommand _lsp _ideState ExecuteCommandParams{..}
114
114
115suggestAction
115suggestAction
116 :: Maybe DynFlags
116 :: Maybe DynFlags
117− -> Maybe ExternalPackageState
117+ -> PackageExportsMap
118 -> IdeOptions
118 -> IdeOptions
119 -> Maybe ParsedModule
119 -> Maybe ParsedModule
120 -> Maybe T.Text
120 -> Maybe T.Text
121 -> Diagnostic
121 -> Diagnostic
122 -> [(T.Text, [TextEdit])]
122 -> [(T.Text, [TextEdit])]
123−suggestAction dflags eps ideOptions parsedModule text diag = concat
123+suggestAction dflags packageExports ideOptions parsedModule text diag = concat
124 [ suggestAddExtension diag
124 [ suggestAddExtension diag
125 , suggestExtendImport dflags text diag
125 , suggestExtendImport dflags text diag
126 , suggestFillHole diag
126 , suggestFillHole diag
@@ -132,7 +132,7 @@ suggestAction dflags eps ideOptions parsedModule text diag = concat
132 ] ++ concat
132 ] ++ concat
133 [ suggestNewDefinition ideOptions pm text diag
133 [ suggestNewDefinition ideOptions pm text diag
134 ++ suggestRemoveRedundantImport pm text diag
134 ++ suggestRemoveRedundantImport pm text diag
135− ++ concat [suggestNewImport dflags eps pm diag | Just eps <- [eps], Just dflags <- [dflags]]
135+ ++ suggestNewImport packageExports pm diag
136 | Just pm <- [parsedModule]]
136 | Just pm <- [parsedModule]]
137
137
138
138
@@ -304,15 +304,10 @@ suggestExtendImport (Just dflags) contents Diagnostic{_range=_range,..}
304 _ -> error "bug in srcspan parser"
304 _ -> error "bug in srcspan parser"
305 importLine = textInRange range c
305 importLine = textInRange range c
306 in [("Add " <> binding <> " to the import list of " <> mod
306 in [("Add " <> binding <> " to the import list of " <> mod
307− , [TextEdit range (addBindingToImportList (printRdrName name) importLine)])]
307+ , [TextEdit range (addBindingToImportList (T.pack $ printRdrName name) importLine)])]
308 | otherwise = []
308 | otherwise = []
309suggestExtendImport Nothing _ _ = []
309suggestExtendImport Nothing _ _ = []
310
310
311−printRdrName :: RdrName -> T.Text
312−printRdrName name = T.pack $ showSDocUnsafe $ parenSymOcc rn (ppr rn)
313− where
314− rn = rdrNameOcc name
315−
316suggestFixConstructorImport :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
311suggestFixConstructorImport :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
317suggestFixConstructorImport _ Diagnostic{_range=_range,..}
312suggestFixConstructorImport _ Diagnostic{_range=_range,..}
318 -- ‘Success’ is a data constructor of ‘Result’
313 -- ‘Success’ is a data constructor of ‘Result’
@@ -353,8 +348,8 @@ suggestSignature _ _ = []
353
348
354-------------------------------------------------------------------------------------------------
349-------------------------------------------------------------------------------------------------
355
350
356−suggestNewImport :: DynFlags -> ExternalPackageState -> ParsedModule -> Diagnostic -> [(T.Text, [TextEdit])]
351+suggestNewImport :: PackageExportsMap -> ParsedModule -> Diagnostic -> [(T.Text, [TextEdit])]
357−suggestNewImport dflags eps ParsedModule {pm_parsed_source = L _ HsModule {..}} Diagnostic{_message}
352+suggestNewImport packageExportsMap ParsedModule {pm_parsed_source = L _ HsModule {..}} Diagnostic{_message}
358 | msg <- unifySpaces _message
353 | msg <- unifySpaces _message
359 , Just name <- extractNotInScopeName msg
354 , Just name <- extractNotInScopeName msg
360 , Just insertLine <- case hsmodImports of
355 , Just insertLine <- case hsmodImports of
@@ -365,52 +360,36 @@ suggestNewImport dflags eps ParsedModule {pm_parsed_source = L _ HsModule {..}}
365 RealSrcLoc s -> Just $ srcLocLine s
360 RealSrcLoc s -> Just $ srcLocLine s
366 _ -> Nothing
361 _ -> Nothing
367 , insertPos <- Position insertLine 0
362 , insertPos <- Position insertLine 0
368− , extendImportSuggestions <- -- Just [binding, mod, srcspan] <-
363+ , extendImportSuggestions <- matchRegex msg
369− matchRegex msg
370 "Perhaps you want to add ‘[^’]*’ to the import list in the import of ‘([^’]*)’"
364 "Perhaps you want to add ‘[^’]*’ to the import list in the import of ‘([^’]*)’"
371 = [(imp, [TextEdit (Range insertPos insertPos) (imp <> "\n")])
365 = [(imp, [TextEdit (Range insertPos insertPos) (imp <> "\n")])
372− | imp <- constructNewImportSuggestions dflags eps name extendImportSuggestions
366+ | imp <- constructNewImportSuggestions packageExportsMap name extendImportSuggestions
373 ]
367 ]
374−suggestNewImport _ _ _ _ = []
368+suggestNewImport _ _ _ = []
375−
369+
376−constructNewImportSuggestions :: DynFlags -> ExternalPackageState -> NotInScope -> Maybe [T.Text] -> [T.Text]
370+constructNewImportSuggestions
371+ :: PackageExportsMap -> NotInScope -> Maybe [T.Text] -> [T.Text]
377−constructNewImportSuggestions dflags eps thingMissing notTheseModules = nubOrd
372+constructNewImportSuggestions exportsMap thingMissing notTheseModules = nubOrd
378− [ case qual of
373+ [ renderNewImport identInfo m
379− Nothing -> "import " <> modName <> " (" <> importWhat candidate avail <> ")"
374+ | (identInfo, m) <- fromMaybe [] $ Map.lookup name exportsMap
380− Just q -> "import qualified " <> modName <> " as " <> q
375+ , canUseIdent thingMissing identInfo
381− | item <- items,
382− avail <- tyThingAvailInfo item,
383− canUseAvail thingMissing avail,
384− candidate <- availNames avail,
385− canUseName thingMissing candidate,
386− occNameString (nameOccName candidate) == T.unpack name,
387− Just m <- [nameModule_maybe candidate],
388− Just package <- [lookupPackage dflags (moduleUnitId m)],
389− moduleName m `elem` map fst (exposedModules package),
390− let modName = T.pack $ moduleNameString $ moduleName m,
391− modName `notElem` fromMaybe [] notTheseModules
376+ , m `notElem` fromMaybe [] notTheseModules
392− ]
377+ ]
393− where
378+ where
379+ renderNewImport identInfo m
380+ | Just q <- qual = "import qualified " <> m <> " as " <> q
381+ | otherwise = "import " <> m <> " (" <> importWhat identInfo <> ")"
382+
394− (qual, name) = case T.splitOn "." (notInScope thingMissing) of
383+ (qual, name) = case T.splitOn "." (notInScope thingMissing) of
395− [n] -> (Nothing, n)
384+ [n] -> (Nothing, n)
396− segments -> (Just (T.concat $ init segments), last segments)
385+ segments -> (Just (T.concat $ init segments), last segments)
397− items = typeEnvElts $ eps_PTE eps
398− importWhat this (AvailTC parent _ _)
386+ importWhat IdentInfo {parent, rendered}
399− -- "Maybe(Just)"
400− | this /= parent
387+ | Just p <- parent = p <> "(" <> rendered <> ")"
401− = T.pack (occNameString (nameOccName parent)) <>
388+ | otherwise = rendered
402− "(" <> printName this <> ")"
389+
403− importWhat this _ = printName this
404−
405− printName = printRdrName . nameRdrName
406−
407−canUseAvail :: NotInScope -> AvailInfo -> Bool
408−canUseAvail NotInScopeDataConstructor{} Avail{} = False
409−canUseAvail _ _ = True
410−
411−canUseName :: NotInScope -> Name -> Bool
390+canUseIdent :: NotInScope -> IdentInfo -> Bool
412−canUseName NotInScopeDataConstructor{} = isDataConName
391+canUseIdent NotInScopeDataConstructor{} = isDatacon
413−canUseName _ = const True
392+canUseIdent _ = const True
414
393
415data NotInScope
394data NotInScope
416 = NotInScopeDataConstructor T.Text
395 = NotInScopeDataConstructor T.Text
@@ -427,6 +406,8 @@ extractNotInScopeName :: T.Text -> Maybe NotInScope
427extractNotInScopeName x
406extractNotInScopeName x
428 | Just [name] <- matchRegex x "Data constructor not in scope: ([^ ]+)"
407 | Just [name] <- matchRegex x "Data constructor not in scope: ([^ ]+)"
429 = Just $ NotInScopeDataConstructor name
408 = Just $ NotInScopeDataConstructor name
409+ | Just [name] <- matchRegex x "Not in scope: data constructor [^‘]*‘([^’]*)’"
410+ = Just $ NotInScopeDataConstructor name
430 | Just [name] <- matchRegex x "ot in scope: type constructor or class [^‘]*‘([^’]*)’"
411 | Just [name] <- matchRegex x "ot in scope: type constructor or class [^‘]*‘([^’]*)’"
431 = Just $ NotInScopeTypeConstructorOrClass name
412 = Just $ NotInScopeTypeConstructorOrClass name
432 | Just [name] <- matchRegex x "ot in scope: ([^‘ ]+)"
413 | Just [name] <- matchRegex x "ot in scope: ([^‘ ]+)"
src/Development/IDE/Plugin/CodeAction/RuleTypes.hsnew file+61
@@ -0,0 +1,61 @@
1+{-# LANGUAGE TypeFamilies #-}
2+module Development.IDE.Plugin.CodeAction.RuleTypes
3+ (PackageExports(..), PackageExportsMap
4+ ,IdentInfo(..)
5+ ,mkIdentInfos
6+ ) where
7+
8+import Avail (AvailInfo(..))
9+import Data.Hashable (Hashable)
10+import Control.DeepSeq (NFData)
11+import Data.Binary (Binary)
12+import Data.Text (pack, Text)
13+import Development.IDE.GHC.Util
14+import Development.Shake (RuleResult)
15+import Data.HashMap.Strict (HashMap)
16+import Data.Typeable (Typeable)
17+import GHC.Generics (Generic)
18+import Name
19+import FieldLabel (flSelector)
20+
21+type Identifier = Text
22+type ModuleName = Text
23+
24+data IdentInfo = IdentInfo
25+ { name :: !Identifier
26+ , rendered :: Text
27+ , parent :: !(Maybe Text)
28+ , isDatacon :: !Bool
29+ }
30+ deriving (Eq, Generic, Show)
31+
32+instance NFData IdentInfo
33+
34+mkIdentInfos :: AvailInfo -> [IdentInfo]
35+mkIdentInfos (Avail n) =
36+ [IdentInfo (pack (prettyPrint n)) (pack (printName n)) Nothing (isDataConName n)]
37+mkIdentInfos (AvailTC parent (n:nn) flds)
38+ -- Following the GHC convention that parent == n if parent is exported
39+ | n == parent
40+ = [ IdentInfo (pack (prettyPrint n)) (pack (printName n)) (Just $! parentP) True
41+ | n <- nn ++ map flSelector flds
42+ ] ++
43+ [ IdentInfo (pack (prettyPrint n)) (pack (printName n)) Nothing False]
44+ where
45+ parentP = pack $ prettyPrint parent
46+
47+mkIdentInfos (AvailTC _ nn flds)
48+ = [ IdentInfo (pack (prettyPrint n)) (pack (printName n)) Nothing True
49+ | n <- nn ++ map flSelector flds
50+ ]
51+
52+-- Rule type for caching Package Exports
53+type instance RuleResult PackageExports = PackageExportsMap
54+type PackageExportsMap = HashMap Identifier [(IdentInfo,ModuleName)]
55+
56+newtype PackageExports = PackageExports HscEnvEq
57+ deriving (Eq, Show, Typeable, Generic)
58+
59+instance Hashable PackageExports
60+instance NFData PackageExports
61+instance Binary PackageExports
src/Development/IDE/Plugin/CodeAction/Rules.hsnew file+61
@@ -0,0 +1,61 @@
1+module Development.IDE.Plugin.CodeAction.Rules
2+ ( rulePackageExports
3+ )
4+where
5+
6+import Data.HashMap.Strict ( fromListWith )
7+import Data.Text ( Text
8+ , pack
9+ )
10+import Data.Traversable ( forM )
11+import Development.IDE.Core.Rules
12+import Development.IDE.GHC.Util
13+import Development.IDE.Plugin.CodeAction.RuleTypes
14+import Development.Shake
15+import GHC ( DynFlags(pkgState) )
16+import HscTypes ( IfaceExport
17+ , hsc_dflags
18+ , mi_exports
19+ )
20+import LoadIface
21+import Maybes
22+import Module ( Module(..)
23+ , ModuleName
24+ , moduleNameString
25+ )
26+import Packages ( explicitPackages
27+ , exposedModules
28+ , packageConfigId
29+ )
30+import TcRnMonad ( WhereFrom(ImportByUser)
31+ , initIfaceLoad
32+ )
33+
34+rulePackageExports :: Rules ()
35+rulePackageExports = defineNoFile $ \(PackageExports session) -> do
36+ let env = hscEnv session
37+ pkgst = pkgState (hsc_dflags env)
38+ depends = explicitPackages pkgst
39+ targets =
40+ [ (pkg, mn)
41+ | d <- depends
42+ , Just pkg <- [lookupPackageConfig d env]
43+ , (mn, _) <- exposedModules pkg
44+ ]
45+
46+ results <- forM targets $ \(pkg, mn) -> do
47+ modIface <- liftIO $ initIfaceLoad env $ loadInterface
48+ ""
49+ (Module (packageConfigId pkg) mn)
50+ (ImportByUser False)
51+ case modIface of
52+ Failed _err -> return mempty
53+ Succeeded mi -> do
54+ let avails = mi_exports mi
55+ return $ concatMap (unpackAvail mn) avails
56+ return $ fromListWith (++) $ concat results
57+
58+unpackAvail :: ModuleName -> IfaceExport -> [(Text, [(IdentInfo, Text)])]
59+unpackAvail mod =
60+ map (\id@IdentInfo {..} -> (name, [(id, pack $ moduleNameString mod)]))
61+ . mkIdentInfos
test/exe/Main.hs+19−12
@@ -901,34 +901,41 @@ suggestImportTests :: TestTree
901suggestImportTests = testGroup "suggest import actions"
901suggestImportTests = testGroup "suggest import actions"
902 [ testGroup "Dont want suggestion"
902 [ testGroup "Dont want suggestion"
903 [ -- extend import
903 [ -- extend import
904− test False ["Data.List.NonEmpty ()"] "f = nonEmpty" [] "import Data.List.NonEmpty (nonEmpty)"
904+ test False ["Data.List.NonEmpty ()"] "f = nonEmpty" [] "import Data.List.NonEmpty (nonEmpty)"
905 -- data constructor
905 -- data constructor
906− , test False [] "f = First" [] "import Data.Monoid (First)"
906+ , test False [] "f = First" [] "import Data.Monoid (First)"
907 -- internal module
907 -- internal module
908 , test False [] "f :: Typeable a => a" ["f = undefined"] "import Data.Typeable.Internal (Typeable)"
908 , test False [] "f :: Typeable a => a" ["f = undefined"] "import Data.Typeable.Internal (Typeable)"
909+ -- package not in scope
910+ , test False [] "f = quickCheck" [] "import Test.QuickCheck (quickCheck)"
909 ]
911 ]
910 , testGroup "want suggestion"
912 , testGroup "want suggestion"
911 [ test True [] "f = nonEmpty" [] "import Data.List.NonEmpty (nonEmpty)"
913 [ test True [] "f = nonEmpty" [] "import Data.List.NonEmpty (nonEmpty)"
912− , test True [] "f = (:|)" [] "import GHC.Base (NonEmpty((:|)))"
914+ , test True [] "f = (:|)" [] "import Data.List.NonEmpty (NonEmpty((:|)))"
913− , test True [] "f :: Natural" ["f = undefined"] "import GHC.Natural (Natural)"
915+ , test True [] "f :: Natural" ["f = undefined"] "import Numeric.Natural (Natural)"
914− , test True [] "f :: NonEmpty ()" ["f = () :| []"] "import GHC.Base (NonEmpty)"
916+ , test True [] "f :: NonEmpty ()" ["f = () :| []"] "import Data.List.NonEmpty (NonEmpty)"
915 , test True [] "f = First" [] "import Data.Monoid (First(First))"
917 , test True [] "f = First" [] "import Data.Monoid (First(First))"
918+ , test True [] "f = Endo" [] "import Data.Monoid (Endo(Endo))"
919+ , test True [] "f = Version" [] "import Data.Version (Version(Version))"
920+ , test True [] "f ExitSuccess = ()" [] "import System.Exit (ExitCode(ExitSuccess))"
921+ , test True [] "f = AssertionFailed" [] "import Control.Exception (AssertionFailed(AssertionFailed))"
916 , test True ["Prelude"] "f = nonEmpty" [] "import Data.List.NonEmpty (nonEmpty)"
922 , test True ["Prelude"] "f = nonEmpty" [] "import Data.List.NonEmpty (nonEmpty)"
917− , test True [] "f :: Alternative f => f ()" ["f = undefined"] "import GHC.Base (Alternative)"
923+ , test True [] "f :: Alternative f => f ()" ["f = undefined"] "import Control.Applicative (Alternative)"
918− , test True [] "f = empty" [] "import GHC.Base (Alternative(empty))"
924+ , test True [] "f = empty" [] "import Control.Applicative (Alternative(empty))"
919 , test True [] "f = (&)" [] "import Data.Function ((&))"
925 , test True [] "f = (&)" [] "import Data.Function ((&))"
920 , test True [] "f = NE.nonEmpty" [] "import qualified Data.List.NonEmpty as NE"
926 , test True [] "f = NE.nonEmpty" [] "import qualified Data.List.NonEmpty as NE"
921− , expectFailBecause "known broken - reexported name" $
922− test True [] "f :: Typeable a => a" ["f = undefined"] "import Data.Typeable (Typeable)"
927+ , test True [] "f :: Typeable a => a" ["f = undefined"] "import Data.Typeable (Typeable)"
928+ , test True [] "f = pack" [] "import Data.Text (pack)"
929+ , test True [] "f :: Text" ["f = undefined"] "import Data.Text (Text)"
923 ]
930 ]
924 ]
931 ]
925 where
932 where
926− test wanted imps def other newImp = testSession (T.unpack def) $ do
933+ test wanted imps def other newImp = testSession' (T.unpack def) $ \dir -> do
927 let before = T.unlines $ "module A where" : ["import " <> x | x <- imps] ++ def : other
934 let before = T.unlines $ "module A where" : ["import " <> x | x <- imps] ++ def : other
928 after = T.unlines $ "module A where" : ["import " <> x | x <- imps] ++ [newImp] ++ def : other
935 after = T.unlines $ "module A where" : ["import " <> x | x <- imps] ++ [newImp] ++ def : other
936+ cradle = "cradle: {direct: {arguments: [-hide-all-packages, -package, base, -package, text, -package-env, -]}}"
937+ liftIO $ writeFileUTF8 (dir </> "hie.yaml") cradle
929 doc <- openDoc' "Test.hs" "haskell" before
938 doc <- openDoc' "Test.hs" "haskell" before
930− -- load another module in the session to exercise the package cache
931− _ <- openDoc' "Other.hs" "haskell" after
932 _diags <- waitForDiagnostics
939 _diags <- waitForDiagnostics
933 let defLine = length imps + 1
940 let defLine = length imps + 1
934 range = Range (Position defLine 0) (Position defLine maxBound)
941 range = Range (Position defLine 0) (Position defLine maxBound)