Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ packages:
./hls-test-utils


index-state: 2025-06-07T14:57:40Z
index-state: 2025-06-16T09:44:13Z

tests: True
test-show-details: direct
Expand Down
2 changes: 1 addition & 1 deletion ghcide/ghcide.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ library
, hashable
, hie-bios ^>=0.15.0
, hie-compat ^>=0.3.0.0
, hiedb ^>= 0.6.0.2
, hiedb ^>= 0.7.0.0
, hls-graph == 2.11.0.0
, hls-plugin-api == 2.11.0.0
, implicit-hie >= 0.1.4.0 && < 0.1.5
Expand Down
4 changes: 2 additions & 2 deletions haskell-language-server.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ library hls-call-hierarchy-plugin
, containers
, extra
, ghcide == 2.11.0.0
, hiedb ^>= 0.6.0.2
, hiedb ^>= 0.7.0.0
, hls-plugin-api == 2.11.0.0
, lens
, lsp >=2.7
Expand Down Expand Up @@ -594,7 +594,7 @@ library hls-rename-plugin
, containers
, ghcide == 2.11.0.0
, hashable
, hiedb ^>= 0.6.0.2
, hiedb ^>= 0.7.0.0
, hie-compat
, hls-plugin-api == 2.11.0.0
, haskell-language-server:hls-refactor-plugin
Expand Down
33 changes: 25 additions & 8 deletions plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import Data.List.NonEmpty (NonEmpty ((:|)),
import qualified Data.Map as M
import Data.Maybe
import Data.Mod.Word
import qualified Data.Set as S
import qualified Data.Text as T
import Development.IDE (Recorder, WithPriority,
usePropertyAction)
Expand All @@ -42,7 +41,9 @@ import qualified Development.IDE.GHC.ExactPrint as E
import Development.IDE.Plugin.CodeAction
import Development.IDE.Spans.AtPoint
import Development.IDE.Types.Location
import HieDb ((:.) (..))
import HieDb.Query
import HieDb.Types (RefRow (refIsGenerated))
import Ide.Plugin.Error
import Ide.Plugin.Properties
import Ide.PluginUtils
Expand Down Expand Up @@ -196,6 +197,8 @@ refsAtName state nfp name = do
dbRefs <- case nameModule_maybe name of
Nothing -> pure []
Just mod -> liftIO $ mapMaybe rowToLoc <$> withHieDb (\hieDb ->
-- See Note [Generated references]
filter (\(refRow HieDb.:. _) -> refIsGenerated refRow) <$>
findReferences
hieDb
True
Expand Down Expand Up @@ -230,15 +233,29 @@ handleGetHieAst state nfp =
-- which is bad (see https://github.com/haskell/haskell-language-server/issues/3799)
fmap removeGenerated $ runActionE "Rename.GetHieAst" state $ useE GetHieAst nfp

-- | We don't want to rename in code generated by GHC as this gives false positives.
-- So we restrict the HIE file to remove all the generated code.
{- Note [Generated references]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
GHC inserts `Use`s of record constructor everywhere where its record selectors are used,
which leads to record fields being renamed whenever corresponding constructor is renamed.
see https://github.com/haskell/haskell-language-server/issues/2915
To work around this, we filter out compiler-generated references.
-}
removeGenerated :: HieAstResult -> HieAstResult
removeGenerated HAR{..} = HAR{hieAst = go hieAst,..}
removeGenerated HAR{..} =
HAR{hieAst = sourceOnlyAsts, refMap = sourceOnlyRefMap, ..}
where
go :: HieASTs a -> HieASTs a
go hf =
HieASTs (fmap goAst (getAsts hf))
goAst (Node nsi sp xs) = Node (SourcedNodeInfo $ M.restrictKeys (getSourcedNodeInfo nsi) (S.singleton SourceInfo)) sp (map goAst xs)
goAsts :: HieASTs a -> HieASTs a
goAsts (HieASTs asts) = HieASTs (fmap goAst asts)

goAst :: HieAST a -> HieAST a
goAst (Node (SourcedNodeInfo sniMap) sp children) =
let sourceOnlyNodeInfos = SourcedNodeInfo $ M.delete GeneratedInfo sniMap
in Node sourceOnlyNodeInfos sp $ map goAst children

sourceOnlyAsts = goAsts hieAst
-- Also need to regenerate the RefMap, because the one in HAR
-- is generated from HieASTs containing GeneratedInfo
sourceOnlyRefMap = generateReferencesMap $ getAsts sourceOnlyAsts

collectWith :: (Hashable a, Eq b) => (a -> b) -> HashSet a -> [(b, HashSet a)]
collectWith f = map (\(a :| as) -> (f a, HS.fromList (a:as))) . groupWith f . HS.toList
Expand Down
7 changes: 6 additions & 1 deletion plugins/hls-rename-plugin/test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ tests :: TestTree
tests = testGroup "Rename"
[ goldenWithRename "Data constructor" "DataConstructor" $ \doc ->
rename doc (Position 0 15) "Op"
, goldenWithRename "Data constructor with fields" "DataConstructorWithFields" $ \doc ->
rename doc (Position 1 13) "FooRenamed"
, knownBrokenForGhcVersions [GHC96, GHC98] "renaming Constructor{..} with RecordWildcard removes .." $
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fyi this is a separate new issue I discovered while testing this PR that affects ghc 9.6 and 9.8.
I opened a separate issue to track that: #4636

goldenWithRename "Data constructor with fields" "DataConstructorWithFieldsRecordWildcards" $ \doc ->
rename doc (Position 1 13) "FooRenamed"
, goldenWithRename "Exported function" "ExportedFunction" $ \doc ->
rename doc (Position 2 1) "quux"
, goldenWithRename "Field Puns" "FieldPuns" $ \doc ->
Expand Down Expand Up @@ -113,7 +118,7 @@ goldenWithRename title path act =
goldenWithHaskellDoc (def { plugins = M.fromList [("rename", def { plcConfig = "crossModule" .= True })] })
renamePlugin title testDataDir path "expected" "hs" act

renameExpectError :: (TResponseError Method_TextDocumentRename) -> TextDocumentIdentifier -> Position -> Text -> Session ()
renameExpectError :: TResponseError Method_TextDocumentRename -> TextDocumentIdentifier -> Position -> Text -> Session ()
renameExpectError expectedError doc pos newName = do
let params = RenameParams Nothing doc pos newName
rsp <- request SMethod_TextDocumentRename params
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{-# LANGUAGE NamedFieldPuns #-}
data Foo = FooRenamed { a :: Int, b :: Bool }

foo1 :: Foo
foo1 = FooRenamed { a = 1, b = True }

foo2 :: Foo
foo2 = FooRenamed 1 True

fun1 :: Foo -> Int
fun1 FooRenamed {a} = a

fun2 :: Foo -> Int
fun2 FooRenamed {a = i} = i
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{-# LANGUAGE NamedFieldPuns #-}
data Foo = Foo { a :: Int, b :: Bool }

foo1 :: Foo
foo1 = Foo { a = 1, b = True }

foo2 :: Foo
foo2 = Foo 1 True

fun1 :: Foo -> Int
fun1 Foo {a} = a

fun2 :: Foo -> Int
fun2 Foo {a = i} = i
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{-# LANGUAGE RecordWildCards #-}
data Foo = FooRenamed { a :: Int, b :: Bool }

fun :: Foo -> Int
fun FooRenamed {..} = a
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{-# LANGUAGE RecordWildCards #-}
data Foo = Foo { a :: Int, b :: Bool }

fun :: Foo -> Int
fun Foo {..} = a
2 changes: 1 addition & 1 deletion stack-lts22.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ allow-newer-deps:
extra-deps:
- Diff-0.5
- floskell-0.11.1
- hiedb-0.6.0.2
- hiedb-0.7.0.0
- hie-bios-0.15.0
- implicit-hie-0.1.4.0
- lsp-2.7.0.0
Expand Down
2 changes: 1 addition & 1 deletion stack.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ allow-newer-deps:

extra-deps:
- floskell-0.11.1
- hiedb-0.6.0.2
- hiedb-0.7.0.0
- implicit-hie-0.1.4.0
- hie-bios-0.15.0
- hw-fingertree-0.1.2.1
Expand Down
Loading