Skip to content

Commit 0a9b1cb

Browse files
jhrcekmergify[bot]
andauthored
Fix renaming data constructors with fields (resolves #2915, resolves #4083) (#4635)
* Prevent renaming record fields whenever record constructor is renamed * wip * WAP * Update stack yamls, add RecordWildcard test * Looks like RecordWildcards renaming is only broken on GHC 9.6 and 9.8 * Consolidate comment, undo whitespace changes --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 256f834 commit 0a9b1cb

11 files changed

+75
-15
lines changed

cabal.project

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ packages:
88
./hls-test-utils
99

1010

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

1313
tests: True
1414
test-show-details: direct

ghcide/ghcide.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ library
7575
, hashable
7676
, hie-bios ^>=0.15.0
7777
, hie-compat ^>=0.3.0.0
78-
, hiedb ^>= 0.6.0.2
78+
, hiedb ^>= 0.7.0.0
7979
, hls-graph == 2.11.0.0
8080
, hls-plugin-api == 2.11.0.0
8181
, implicit-hie >= 0.1.4.0 && < 0.1.5

haskell-language-server.cabal

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ library hls-call-hierarchy-plugin
407407
, containers
408408
, extra
409409
, ghcide == 2.11.0.0
410-
, hiedb ^>= 0.6.0.2
410+
, hiedb ^>= 0.7.0.0
411411
, hls-plugin-api == 2.11.0.0
412412
, lens
413413
, lsp >=2.7
@@ -594,7 +594,7 @@ library hls-rename-plugin
594594
, containers
595595
, ghcide == 2.11.0.0
596596
, hashable
597-
, hiedb ^>= 0.6.0.2
597+
, hiedb ^>= 0.7.0.0
598598
, hie-compat
599599
, hls-plugin-api == 2.11.0.0
600600
, haskell-language-server:hls-refactor-plugin

plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import Data.List.NonEmpty (NonEmpty ((:|)),
2525
import qualified Data.Map as M
2626
import Data.Maybe
2727
import Data.Mod.Word
28-
import qualified Data.Set as S
2928
import qualified Data.Text as T
3029
import Development.IDE (Recorder, WithPriority,
3130
usePropertyAction)
@@ -42,7 +41,9 @@ import qualified Development.IDE.GHC.ExactPrint as E
4241
import Development.IDE.Plugin.CodeAction
4342
import Development.IDE.Spans.AtPoint
4443
import Development.IDE.Types.Location
44+
import HieDb ((:.) (..))
4545
import HieDb.Query
46+
import HieDb.Types (RefRow (refIsGenerated))
4647
import Ide.Plugin.Error
4748
import Ide.Plugin.Properties
4849
import Ide.PluginUtils
@@ -196,6 +197,8 @@ refsAtName state nfp name = do
196197
dbRefs <- case nameModule_maybe name of
197198
Nothing -> pure []
198199
Just mod -> liftIO $ mapMaybe rowToLoc <$> withHieDb (\hieDb ->
200+
-- See Note [Generated references]
201+
filter (\(refRow HieDb.:. _) -> refIsGenerated refRow) <$>
199202
findReferences
200203
hieDb
201204
True
@@ -230,15 +233,29 @@ handleGetHieAst state nfp =
230233
-- which is bad (see https://github.com/haskell/haskell-language-server/issues/3799)
231234
fmap removeGenerated $ runActionE "Rename.GetHieAst" state $ useE GetHieAst nfp
232235

233-
-- | We don't want to rename in code generated by GHC as this gives false positives.
234-
-- So we restrict the HIE file to remove all the generated code.
236+
{- Note [Generated references]
237+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
238+
GHC inserts `Use`s of record constructor everywhere where its record selectors are used,
239+
which leads to record fields being renamed whenever corresponding constructor is renamed.
240+
see https://github.com/haskell/haskell-language-server/issues/2915
241+
To work around this, we filter out compiler-generated references.
242+
-}
235243
removeGenerated :: HieAstResult -> HieAstResult
236-
removeGenerated HAR{..} = HAR{hieAst = go hieAst,..}
244+
removeGenerated HAR{..} =
245+
HAR{hieAst = sourceOnlyAsts, refMap = sourceOnlyRefMap, ..}
237246
where
238-
go :: HieASTs a -> HieASTs a
239-
go hf =
240-
HieASTs (fmap goAst (getAsts hf))
241-
goAst (Node nsi sp xs) = Node (SourcedNodeInfo $ M.restrictKeys (getSourcedNodeInfo nsi) (S.singleton SourceInfo)) sp (map goAst xs)
247+
goAsts :: HieASTs a -> HieASTs a
248+
goAsts (HieASTs asts) = HieASTs (fmap goAst asts)
249+
250+
goAst :: HieAST a -> HieAST a
251+
goAst (Node (SourcedNodeInfo sniMap) sp children) =
252+
let sourceOnlyNodeInfos = SourcedNodeInfo $ M.delete GeneratedInfo sniMap
253+
in Node sourceOnlyNodeInfos sp $ map goAst children
254+
255+
sourceOnlyAsts = goAsts hieAst
256+
-- Also need to regenerate the RefMap, because the one in HAR
257+
-- is generated from HieASTs containing GeneratedInfo
258+
sourceOnlyRefMap = generateReferencesMap $ getAsts sourceOnlyAsts
242259

243260
collectWith :: (Hashable a, Eq b) => (a -> b) -> HashSet a -> [(b, HashSet a)]
244261
collectWith f = map (\(a :| as) -> (f a, HS.fromList (a:as))) . groupWith f . HS.toList

plugins/hls-rename-plugin/test/Main.hs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ tests :: TestTree
2424
tests = testGroup "Rename"
2525
[ goldenWithRename "Data constructor" "DataConstructor" $ \doc ->
2626
rename doc (Position 0 15) "Op"
27+
, goldenWithRename "Data constructor with fields" "DataConstructorWithFields" $ \doc ->
28+
rename doc (Position 1 13) "FooRenamed"
29+
, knownBrokenForGhcVersions [GHC96, GHC98] "renaming Constructor{..} with RecordWildcard removes .." $
30+
goldenWithRename "Data constructor with fields" "DataConstructorWithFieldsRecordWildcards" $ \doc ->
31+
rename doc (Position 1 13) "FooRenamed"
2732
, goldenWithRename "Exported function" "ExportedFunction" $ \doc ->
2833
rename doc (Position 2 1) "quux"
2934
, goldenWithRename "Field Puns" "FieldPuns" $ \doc ->
@@ -113,7 +118,7 @@ goldenWithRename title path act =
113118
goldenWithHaskellDoc (def { plugins = M.fromList [("rename", def { plcConfig = "crossModule" .= True })] })
114119
renamePlugin title testDataDir path "expected" "hs" act
115120

116-
renameExpectError :: (TResponseError Method_TextDocumentRename) -> TextDocumentIdentifier -> Position -> Text -> Session ()
121+
renameExpectError :: TResponseError Method_TextDocumentRename -> TextDocumentIdentifier -> Position -> Text -> Session ()
117122
renameExpectError expectedError doc pos newName = do
118123
let params = RenameParams Nothing doc pos newName
119124
rsp <- request SMethod_TextDocumentRename params
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{-# LANGUAGE NamedFieldPuns #-}
2+
data Foo = FooRenamed { a :: Int, b :: Bool }
3+
4+
foo1 :: Foo
5+
foo1 = FooRenamed { a = 1, b = True }
6+
7+
foo2 :: Foo
8+
foo2 = FooRenamed 1 True
9+
10+
fun1 :: Foo -> Int
11+
fun1 FooRenamed {a} = a
12+
13+
fun2 :: Foo -> Int
14+
fun2 FooRenamed {a = i} = i
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{-# LANGUAGE NamedFieldPuns #-}
2+
data Foo = Foo { a :: Int, b :: Bool }
3+
4+
foo1 :: Foo
5+
foo1 = Foo { a = 1, b = True }
6+
7+
foo2 :: Foo
8+
foo2 = Foo 1 True
9+
10+
fun1 :: Foo -> Int
11+
fun1 Foo {a} = a
12+
13+
fun2 :: Foo -> Int
14+
fun2 Foo {a = i} = i
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{-# LANGUAGE RecordWildCards #-}
2+
data Foo = FooRenamed { a :: Int, b :: Bool }
3+
4+
fun :: Foo -> Int
5+
fun FooRenamed {..} = a
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{-# LANGUAGE RecordWildCards #-}
2+
data Foo = Foo { a :: Int, b :: Bool }
3+
4+
fun :: Foo -> Int
5+
fun Foo {..} = a

stack-lts22.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ allow-newer-deps:
2121
extra-deps:
2222
- Diff-0.5
2323
- floskell-0.11.1
24-
- hiedb-0.6.0.2
24+
- hiedb-0.7.0.0
2525
- hie-bios-0.15.0
2626
- implicit-hie-0.1.4.0
2727
- lsp-2.7.0.0

stack.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ allow-newer-deps:
2222

2323
extra-deps:
2424
- floskell-0.11.1
25-
- hiedb-0.6.0.2
25+
- hiedb-0.7.0.0
2626
- implicit-hie-0.1.4.0
2727
- hie-bios-0.15.0
2828
- hw-fingertree-0.1.2.1

0 commit comments

Comments
 (0)