Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add code action for hiding shadowed identifiers from imports #1322

Merged
merged 17 commits into from
Feb 13, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
103 changes: 99 additions & 4 deletions ghcide/src/Development/IDE/Plugin/CodeAction.hs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ import OccName
import qualified GHC.LanguageExtensions as Lang
import Control.Lens (alaf)
import Data.Monoid (Ap(..))
import TcRnTypes (TcGblEnv(..), ImportAvails(..))
import HscTypes (ImportedModsVal(..), importedByUser)
import RdrName (GlobalRdrElt(..), lookupGlobalRdrEnv)
import SrcLoc (realSrcSpanStart)
import Module (moduleEnvElts)
import qualified Data.Map as M
import qualified Data.Set as S

descriptor :: PluginId -> PluginDescriptor IdeState
descriptor plId =
Expand All @@ -80,11 +87,13 @@ codeAction lsp state _ (TextDocumentIdentifier uri) _range CodeActionContext{_di
let text = Rope.toText . (_text :: VirtualFile -> Rope.Rope) <$> contents
mbFile = toNormalizedFilePath' <$> uriToFilePath uri
diag <- fmap (\(_, _, d) -> d) . filter (\(p, _, _) -> mbFile == Just p) <$> getDiagnostics state
(ideOptions, join -> parsedModule, join -> env, join -> annotatedPS) <- runAction "CodeAction" state $
(,,,) <$> getIdeOptions
(ideOptions, join -> parsedModule, join -> env, join -> annotatedPS, join -> tcM, join -> har) <- runAction "CodeAction" state $
(,,,,,) <$> getIdeOptions
<*> getParsedModule `traverse` mbFile
<*> use GhcSession `traverse` mbFile
<*> use GetAnnotatedParsedSource `traverse` mbFile
<*> use TypeCheck `traverse` mbFile
<*> use GetHieAst `traverse` mbFile
-- This is quite expensive 0.6-0.7s on GHC
let pkgExports = envPackageExports <$> env
localExports <- readVar (exportsMap $ shakeExtras state)
Expand All @@ -93,7 +102,7 @@ codeAction lsp state _ (TextDocumentIdentifier uri) _range CodeActionContext{_di
df = ms_hspp_opts . pm_mod_summary <$> parsedModule
actions =
[ mkCA title [x] edit
| x <- xs, (title, tedit) <- suggestAction exportsMap ideOptions parsedModule text df annotatedPS x
| x <- xs, (title, tedit) <- suggestAction exportsMap ideOptions parsedModule text df annotatedPS tcM har x
, let edit = WorkspaceEdit (Just $ Map.singleton uri $ List tedit) Nothing
]
actions' = caRemoveRedundantImports parsedModule text diag xs uri
Expand Down Expand Up @@ -123,9 +132,11 @@ suggestAction
-> Maybe T.Text
-> Maybe DynFlags
-> Maybe (Annotated ParsedSource)
-> Maybe TcModuleResult
-> Maybe HieAstResult
-> Diagnostic
-> [(T.Text, [TextEdit])]
suggestAction packageExports ideOptions parsedModule text df annSource diag =
suggestAction packageExports ideOptions parsedModule text df annSource tcM har diag =
concat
-- Order these suggestions by priority
[ suggestSignature True diag
Expand All @@ -140,6 +151,7 @@ suggestAction packageExports ideOptions parsedModule text df annSource diag =
, suggestAddTypeAnnotationToSatisfyContraints text diag
, rewrite df annSource $ \df ps -> suggestConstraint df ps diag
, rewrite df annSource $ \_ ps -> suggestImplicitParameter ps diag
, rewrite df annSource $ \_ ps -> suggestHideShadow ps tcM har diag
] ++ concat
[ suggestNewDefinition ideOptions pm text diag
++ suggestNewImport packageExports pm diag
Expand Down Expand Up @@ -169,6 +181,89 @@ findInstanceHead df instanceHead decls =
findDeclContainingLoc :: Position -> [Located a] -> Maybe (Located a)
findDeclContainingLoc loc = find (\(L l _) -> loc `isInsideSrcSpan` l)

-- Single:
-- This binding for ‘mod’ shadows the existing binding
-- imported from ‘Prelude’ at haskell-language-server/ghcide/src/Development/IDE/Plugin/CodeAction.hs:10:8-40
-- (and originally defined in ‘GHC.Real’)typecheck(-Wname-shadowing)
-- Multi:
--This binding for ‘pack’ shadows the existing bindings
-- imported from ‘Data.ByteString’ at B.hs:6:1-22
-- imported from ‘Data.ByteString.Lazy’ at B.hs:8:1-27
-- imported from ‘Data.Text’ at B.hs:7:1-16

suggestHideShadow :: ParsedSource -> Maybe TcModuleResult -> Maybe HieAstResult -> Diagnostic -> [(T.Text, [Rewrite])]
suggestHideShadow pm@(L _ HsModule {hsmodImports}) mTcM mHar Diagnostic {_message, _range}
| Just [identifier, modName, s] <-
matchRegexUnifySpaces
_message
"This binding for ‘([^`]+)’ shadows the existing binding imported from ‘([^`]+)’ at ([^ ]*)"
= suggests identifier modName s
| Just [identifier] <-
matchRegexUnifySpaces
_message
"This binding for ‘([^`]+)’ shadows the existing bindings",
Just matched <- allMatchRegexUnifySpaces _message "imported from ‘([^’]+)’ at ([^ ]*)",
mods <- [(modName, s) | [_, modName, s] <- matched],
result <- nubOrdBy (compare `on` fst) $ mods >>= uncurry (suggests identifier),
hideAll <- ("Hide " <> identifier <> " from all occurence imports", concat $ snd <$> result)
= result <> [hideAll]
| otherwise = []
where
suggests identifier modName s
| Just tcM <- mTcM,
Just har <- mHar,
[s'] <- [x | (x, "") <- readSrcSpan $ T.unpack s],
isUnusedImportedId tcM har (T.unpack identifier) (T.unpack modName) (RealSrcSpan s'),
title <- "Hide " <> identifier <> " from " <> modName
= if modName == "Prelude" && null (findImportDeclByModuleName hsmodImports "Prelude")
then [(title, maybeToList $ hideImplicitPreludeSymbol (T.unpack identifier) pm)]
else [(title, hideOrRemoveId hsmodImports (T.unpack identifier) (T.unpack modName))]
| otherwise = []

hideOrRemoveId :: [LImportDecl GhcPs] -> String -> String -> [Rewrite]
hideOrRemoveId lImportDecls identifier modName
| Just decl <- findImportDeclByModuleName lImportDecls modName =
[hideSymbol identifier decl]
| otherwise =
[]

findImportDeclByModuleName :: [LImportDecl GhcPs] -> String -> Maybe (LImportDecl GhcPs)
findImportDeclByModuleName decls modName = flip find decls $ \case
(L _ ImportDecl {..}) -> modName == moduleNameString (unLoc ideclName)
_ -> error "impossible"

isTheSameLine :: SrcSpan -> SrcSpan -> Bool
isTheSameLine s1 s2
| Just sl1 <- getStartLine s1,
Just sl2 <- getStartLine s2 =
sl1 == sl2
| otherwise = False

getStartLine :: SrcSpan -> Maybe Int
getStartLine x = srcLocLine . realSrcSpanStart <$> realSpan x

isUnusedImportedId :: TcModuleResult -> HieAstResult -> String -> String -> SrcSpan -> Bool
isUnusedImportedId
TcModuleResult {tmrTypechecked = TcGblEnv {tcg_imports = ImportAvails {imp_mods}}}
HAR {refMap = rf}
identifier
modName
importSpan
| occ <- mkVarOcc identifier,
impModsVals <- importedByUser . concat $ moduleEnvElts imp_mods,
Just rdrEnv <-
listToMaybe
[ imv_all_exports
| ImportedModsVal {..} <- impModsVals,
imv_name == mkModuleName modName,
isTheSameLine imv_span importSpan
],
[GRE {..}] <- lookupGlobalRdrEnv rdrEnv occ,
importedIdentifier <- Right gre_name,
refs <- M.lookup importedIdentifier rf =
maybe True (not . any (\(_, IdentifierDetails{..}) -> identInfo == S.singleton Use)) refs
| otherwise = False

suggestDisableWarning :: ParsedModule -> Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
suggestDisableWarning pm contents Diagnostic{..}
| Just (StringValue (T.stripPrefix "-W" -> Just w)) <- _code =
Expand Down
20 changes: 13 additions & 7 deletions ghcide/src/Development/IDE/Plugin/CodeAction/ExactPrint.hs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import Development.IDE.GHC.Compat hiding (parseExpr)
import Development.IDE.GHC.ExactPrint
( Annotate, ASTElement(parseAST) )
import FieldLabel (flLabel)
import GhcPlugins (sigPrec)
import GhcPlugins (sigPrec, mkRealSrcLoc)
import Language.Haskell.GHC.ExactPrint
import Language.Haskell.GHC.ExactPrint.Types (DeltaPos (DP), KeywordId (G), mkAnnKey)
import Language.Haskell.LSP.Types
Expand All @@ -40,9 +40,9 @@ import Outputable (ppr, showSDocUnsafe, showSDoc)
import Retrie.GHC (rdrNameOcc, unpackFS, mkRealSrcSpan, realSrcSpanEnd)
import Development.IDE.Spans.Common
import Development.IDE.GHC.Error
import Safe (lastMay)
import Data.Generics (listify)
import GHC.Exts (IsList (fromList))
import Control.Monad.Extra (whenJust)

------------------------------------------------------------------------------

Expand Down Expand Up @@ -205,6 +205,7 @@ extendImport mparent identifier lDecl@(L l _) =
-- extendImportTopLevel "foo" AST:
--
-- import A --> Error
-- import A (foo) --> Error
-- import A (bar) --> import A (bar, foo)
extendImportTopLevel :: DynFlags -> String -> LImportDecl GhcPs -> TransformT (Either String) (LImportDecl GhcPs)
extendImportTopLevel df idnetifier (L l it@ImportDecl {..})
Expand Down Expand Up @@ -382,6 +383,8 @@ deleteFromImport (T.pack -> symbol) (L l idecl) llies@(L lieLoc lies) _ =do
lidecl' = L l $ idecl
{ ideclHiding = Just (False, edited)
}
-- avoid import A (foo,)
whenJust (lastMaybe deletedLies) removeTrailingCommaT
when (not (null lies) && null deletedLies) $ do
transferAnn llies edited id
addSimpleAnnT edited dp00
Expand All @@ -408,13 +411,16 @@ deleteFromImport (T.pack -> symbol) (L l idecl) llies@(L lieLoc lies) _ =do
(filter ((/= symbol) . T.pack . unpackFS . flLabel . unLoc) flds)
killLie v = Just v

-- | Insert a import declaration hiding a symbole from Prelude
hideImplicitPreludeSymbol
:: String -> ParsedSource -> Maybe Rewrite
hideImplicitPreludeSymbol symbol (L _ HsModule{..}) = do
existingImp <- lastMay hsmodImports
exisImpSpan <- realSpan $ getLoc existingImp
let indentation = srcSpanStartCol exisImpSpan
beg = realSrcSpanEnd exisImpSpan
let predLine old = mkRealSrcLoc (srcLocFile old) (srcLocLine old - 1) (srcLocCol old)
existingImpSpan = (fmap (id,) . realSpan . getLoc) =<< lastMaybe hsmodImports
existingDeclSpan = (fmap (predLine, ) . realSpan . getLoc) =<< headMaybe hsmodDecls
(f, s) <- existingImpSpan <|> existingDeclSpan
let beg = f $ realSrcSpanEnd s
indentation = srcSpanStartCol s
ran = RealSrcSpan $ mkRealSrcSpan beg beg
pure $ Rewrite ran $ \df -> do
let symOcc = mkVarOcc symbol
Expand All @@ -424,6 +430,6 @@ hideImplicitPreludeSymbol symbol (L _ HsModule{..}) = do
-- Re-labeling is needed to reflect annotations correctly
L _ idecl0 <- liftParseAST @(ImportDecl GhcPs) df $ T.unpack impStmt
let idecl = L ran idecl0
addSimpleAnnT idecl (DP (1,indentation - 1))
addSimpleAnnT idecl (DP (1, indentation - 1))
[(G AnnImport, DP (1, indentation - 1))]
pure idecl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module HideFunction where

import AVec (fromList)
import BVec (fromList,)
import BVec (fromList)
import CVec hiding ((++), cons)
import DVec hiding ((++), cons, snoc)
import EVec as E
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module HideFunction where

import AVec (fromList)
import BVec (fromList,)
import BVec (fromList)
import CVec hiding ((++), cons)
import DVec hiding ((++), cons, snoc)
import EVec as E hiding ((++))
Expand Down