From 1ca33e37b5be99113ef82144f127959de9882e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hr=C4=8Dek?= Date: Mon, 16 Jun 2025 13:45:24 +0200 Subject: [PATCH 1/6] Prevent renaming record fields whenever record constructor is renamed --- cabal.project | 2 +- ghcide/ghcide.cabal | 2 +- haskell-language-server.cabal | 4 ++-- plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs | 7 +++++++ plugins/hls-rename-plugin/test/Main.hs | 2 +- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/cabal.project b/cabal.project index 3d43dff2f4..92954ec729 100644 --- a/cabal.project +++ b/cabal.project @@ -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 diff --git a/ghcide/ghcide.cabal b/ghcide/ghcide.cabal index 4d4b481c14..416e389f2f 100644 --- a/ghcide/ghcide.cabal +++ b/ghcide/ghcide.cabal @@ -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 diff --git a/haskell-language-server.cabal b/haskell-language-server.cabal index f49c619ec1..42e8d11b60 100644 --- a/haskell-language-server.cabal +++ b/haskell-language-server.cabal @@ -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 @@ -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 diff --git a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs index 7cc1122982..c56f1b83bd 100644 --- a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs +++ b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs @@ -42,7 +42,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 @@ -196,6 +198,11 @@ refsAtName state nfp name = do dbRefs <- case nameModule_maybe name of Nothing -> pure [] Just mod -> liftIO $ mapMaybe rowToLoc <$> withHieDb (\hieDb -> + -- GHC inserts `Use`s of record constructor everywhere where its record selectors are used, + -- which leads to 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. + filter (\(refRow HieDb.:. _) -> not $ refIsGenerated refRow) <$> findReferences hieDb True diff --git a/plugins/hls-rename-plugin/test/Main.hs b/plugins/hls-rename-plugin/test/Main.hs index 5f7fb818ff..6c63abb126 100644 --- a/plugins/hls-rename-plugin/test/Main.hs +++ b/plugins/hls-rename-plugin/test/Main.hs @@ -113,7 +113,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 From e4293040dc3e94471dc7876ff5bdecffc4fa308a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hr=C4=8Dek?= Date: Mon, 16 Jun 2025 15:06:42 +0200 Subject: [PATCH 2/6] wip --- plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs index c56f1b83bd..4cc9f05e99 100644 --- a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs +++ b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs @@ -52,6 +52,7 @@ import Ide.Types import qualified Language.LSP.Protocol.Lens as L import Language.LSP.Protocol.Message import Language.LSP.Protocol.Types +import Data.List instance Hashable (Mod a) where hash n = hash (unMod n) @@ -197,19 +198,22 @@ refsAtName state nfp name = do ast <- handleGetHieAst state nfp dbRefs <- case nameModule_maybe name of Nothing -> pure [] - Just mod -> liftIO $ mapMaybe rowToLoc <$> withHieDb (\hieDb -> + Just mod -> liftIO $ mapMaybe rowToLoc <$> withHieDb (\hieDb -> do -- GHC inserts `Use`s of record constructor everywhere where its record selectors are used, -- which leads to 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. - filter (\(refRow HieDb.:. _) -> not $ refIsGenerated refRow) <$> - findReferences + + xs <- findReferences hieDb True (nameOccName name) (Just $ moduleName mod) (Just $ moduleUnit mod) [fromNormalizedFilePath nfp] + let (gen,notGen) = partition (\(refRow HieDb.:. _) -> refIsGenerated refRow) xs + putStrLn $ "Found " ++ show (length xs) ++ " references in HieDb: " ++ show (length xs) ++ ", of which " ++ show (length gen) ++ " are generated" + pure notGen ) pure $ nameLocs name ast ++ dbRefs From f81ab41ba9229abfbf1875911f7253ec7de00e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hr=C4=8Dek?= Date: Mon, 16 Jun 2025 16:47:50 +0200 Subject: [PATCH 3/6] WAP --- .../src/Ide/Plugin/Rename.hs | 31 ++++++++++--------- plugins/hls-rename-plugin/test/Main.hs | 2 ++ .../DataConstructorWithFields.expected.hs | 14 +++++++++ .../testdata/DataConstructorWithFields.hs | 14 +++++++++ 4 files changed, 47 insertions(+), 14 deletions(-) create mode 100644 plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.expected.hs create mode 100644 plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.hs diff --git a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs index 4cc9f05e99..0c365e3c32 100644 --- a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs +++ b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs @@ -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) @@ -52,7 +51,6 @@ import Ide.Types import qualified Language.LSP.Protocol.Lens as L import Language.LSP.Protocol.Message import Language.LSP.Protocol.Types -import Data.List instance Hashable (Mod a) where hash n = hash (unMod n) @@ -103,7 +101,6 @@ renameProvider state pluginId (RenameParams _prog (TextDocumentIdentifier uri) p [] -> throwError $ PluginInvalidParams "No symbol to rename at given position" _ -> do refs <- HS.fromList . concat <$> mapM (refsAtName state nfp) oldNames - -- Validate rename crossModuleEnabled <- liftIO $ runAction "rename: config" state $ usePropertyAction #crossModule pluginId properties unless crossModuleEnabled $ failWhenImportOrExport state nfp refs oldNames @@ -198,22 +195,19 @@ refsAtName state nfp name = do ast <- handleGetHieAst state nfp dbRefs <- case nameModule_maybe name of Nothing -> pure [] - Just mod -> liftIO $ mapMaybe rowToLoc <$> withHieDb (\hieDb -> do + Just mod -> liftIO $ mapMaybe rowToLoc <$> withHieDb (\hieDb -> -- GHC inserts `Use`s of record constructor everywhere where its record selectors are used, -- which leads to 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. - - xs <- findReferences + filter (\(refRow HieDb.:. _) -> refIsGenerated refRow) <$> + findReferences hieDb True (nameOccName name) (Just $ moduleName mod) (Just $ moduleUnit mod) [fromNormalizedFilePath nfp] - let (gen,notGen) = partition (\(refRow HieDb.:. _) -> refIsGenerated refRow) xs - putStrLn $ "Found " ++ show (length xs) ++ " references in HieDb: " ++ show (length xs) ++ ", of which " ++ show (length gen) ++ " are generated" - pure notGen ) pure $ nameLocs name ast ++ dbRefs @@ -244,12 +238,21 @@ handleGetHieAst state 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. 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 diff --git a/plugins/hls-rename-plugin/test/Main.hs b/plugins/hls-rename-plugin/test/Main.hs index 6c63abb126..f810387a62 100644 --- a/plugins/hls-rename-plugin/test/Main.hs +++ b/plugins/hls-rename-plugin/test/Main.hs @@ -24,6 +24,8 @@ 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" , goldenWithRename "Exported function" "ExportedFunction" $ \doc -> rename doc (Position 2 1) "quux" , goldenWithRename "Field Puns" "FieldPuns" $ \doc -> diff --git a/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.expected.hs b/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.expected.hs new file mode 100644 index 0000000000..d7aa6e6503 --- /dev/null +++ b/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.expected.hs @@ -0,0 +1,14 @@ +{-# LANGUAGE NamedFieldPuns #-} +data Foo = FooRenamed { FooRenamed :: Int, FooRenamed :: 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 \ No newline at end of file diff --git a/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.hs b/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.hs new file mode 100644 index 0000000000..c601e91da2 --- /dev/null +++ b/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.hs @@ -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 \ No newline at end of file From 59eb14e36a51b34497dafd6fe431d92dfd8046fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hr=C4=8Dek?= Date: Mon, 16 Jun 2025 16:57:31 +0200 Subject: [PATCH 4/6] Update stack yamls, add RecordWildcard test --- plugins/hls-rename-plugin/test/Main.hs | 2 +- .../test/testdata/DataConstructorWithFields.expected.hs | 8 ++++++-- .../test/testdata/DataConstructorWithFields.hs | 6 +++++- stack-lts22.yaml | 2 +- stack.yaml | 2 +- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/plugins/hls-rename-plugin/test/Main.hs b/plugins/hls-rename-plugin/test/Main.hs index f810387a62..16b10a6fab 100644 --- a/plugins/hls-rename-plugin/test/Main.hs +++ b/plugins/hls-rename-plugin/test/Main.hs @@ -25,7 +25,7 @@ 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" + rename doc (Position 2 13) "FooRenamed" , goldenWithRename "Exported function" "ExportedFunction" $ \doc -> rename doc (Position 2 1) "quux" , goldenWithRename "Field Puns" "FieldPuns" $ \doc -> diff --git a/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.expected.hs b/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.expected.hs index d7aa6e6503..e98e3211e0 100644 --- a/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.expected.hs +++ b/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.expected.hs @@ -1,5 +1,6 @@ {-# LANGUAGE NamedFieldPuns #-} -data Foo = FooRenamed { FooRenamed :: Int, FooRenamed :: Bool } +{-# LANGUAGE RecordWildCards #-} +data Foo = FooRenamed { a :: Int, b :: Bool } foo1 :: Foo foo1 = FooRenamed { a = 1, b = True } @@ -11,4 +12,7 @@ fun1 :: Foo -> Int fun1 FooRenamed {a} = a fun2 :: Foo -> Int -fun2 FooRenamed {a = i} = i \ No newline at end of file +fun2 FooRenamed {a = i} = i + +fun3 :: Foo -> Int +fun3 FooRenamed {..} = a diff --git a/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.hs b/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.hs index c601e91da2..e60bb29454 100644 --- a/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.hs +++ b/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.hs @@ -1,4 +1,5 @@ {-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE RecordWildCards #-} data Foo = Foo { a :: Int, b :: Bool } foo1 :: Foo @@ -11,4 +12,7 @@ fun1 :: Foo -> Int fun1 Foo {a} = a fun2 :: Foo -> Int -fun2 Foo {a = i} = i \ No newline at end of file +fun2 Foo {a = i} = i + +fun3 :: Foo -> Int +fun3 Foo {..} = a diff --git a/stack-lts22.yaml b/stack-lts22.yaml index 7306295a8a..63efc35f30 100644 --- a/stack-lts22.yaml +++ b/stack-lts22.yaml @@ -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 diff --git a/stack.yaml b/stack.yaml index ba89370091..f6dd73d66a 100644 --- a/stack.yaml +++ b/stack.yaml @@ -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 From f1f33d09d8f83d73391a1b928550473baef4234a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hr=C4=8Dek?= Date: Mon, 16 Jun 2025 18:19:55 +0200 Subject: [PATCH 5/6] Looks like RecordWildcards renaming is only broken on GHC 9.6 and 9.8 --- plugins/hls-rename-plugin/test/Main.hs | 5 ++++- .../test/testdata/DataConstructorWithFields.expected.hs | 4 ---- .../test/testdata/DataConstructorWithFields.hs | 4 ---- .../DataConstructorWithFieldsRecordWildcards.expected.hs | 5 +++++ .../testdata/DataConstructorWithFieldsRecordWildcards.hs | 5 +++++ 5 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 plugins/hls-rename-plugin/test/testdata/DataConstructorWithFieldsRecordWildcards.expected.hs create mode 100644 plugins/hls-rename-plugin/test/testdata/DataConstructorWithFieldsRecordWildcards.hs diff --git a/plugins/hls-rename-plugin/test/Main.hs b/plugins/hls-rename-plugin/test/Main.hs index 16b10a6fab..b935e6563f 100644 --- a/plugins/hls-rename-plugin/test/Main.hs +++ b/plugins/hls-rename-plugin/test/Main.hs @@ -25,7 +25,10 @@ tests = testGroup "Rename" [ goldenWithRename "Data constructor" "DataConstructor" $ \doc -> rename doc (Position 0 15) "Op" , goldenWithRename "Data constructor with fields" "DataConstructorWithFields" $ \doc -> - rename doc (Position 2 13) "FooRenamed" + rename doc (Position 1 13) "FooRenamed" + , knownBrokenForGhcVersions [GHC96, GHC98] "renaming Constructor{..} with RecordWildcard removes .." $ + 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 -> diff --git a/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.expected.hs b/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.expected.hs index e98e3211e0..5fc38c7f01 100644 --- a/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.expected.hs +++ b/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.expected.hs @@ -1,5 +1,4 @@ {-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE RecordWildCards #-} data Foo = FooRenamed { a :: Int, b :: Bool } foo1 :: Foo @@ -13,6 +12,3 @@ fun1 FooRenamed {a} = a fun2 :: Foo -> Int fun2 FooRenamed {a = i} = i - -fun3 :: Foo -> Int -fun3 FooRenamed {..} = a diff --git a/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.hs b/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.hs index e60bb29454..abd8031096 100644 --- a/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.hs +++ b/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFields.hs @@ -1,5 +1,4 @@ {-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE RecordWildCards #-} data Foo = Foo { a :: Int, b :: Bool } foo1 :: Foo @@ -13,6 +12,3 @@ fun1 Foo {a} = a fun2 :: Foo -> Int fun2 Foo {a = i} = i - -fun3 :: Foo -> Int -fun3 Foo {..} = a diff --git a/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFieldsRecordWildcards.expected.hs b/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFieldsRecordWildcards.expected.hs new file mode 100644 index 0000000000..b5dd83cecb --- /dev/null +++ b/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFieldsRecordWildcards.expected.hs @@ -0,0 +1,5 @@ +{-# LANGUAGE RecordWildCards #-} +data Foo = FooRenamed { a :: Int, b :: Bool } + +fun :: Foo -> Int +fun FooRenamed {..} = a diff --git a/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFieldsRecordWildcards.hs b/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFieldsRecordWildcards.hs new file mode 100644 index 0000000000..8e624b0816 --- /dev/null +++ b/plugins/hls-rename-plugin/test/testdata/DataConstructorWithFieldsRecordWildcards.hs @@ -0,0 +1,5 @@ +{-# LANGUAGE RecordWildCards #-} +data Foo = Foo { a :: Int, b :: Bool } + +fun :: Foo -> Int +fun Foo {..} = a From 53a2aa082c1a1f28f4e99810d553946e029c7406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hr=C4=8Dek?= Date: Tue, 17 Jun 2025 07:00:25 +0200 Subject: [PATCH 6/6] Consolidate comment, undo whitespace changes --- .../hls-rename-plugin/src/Ide/Plugin/Rename.hs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs index 0c365e3c32..2fdbee3ebc 100644 --- a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs +++ b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs @@ -101,6 +101,7 @@ renameProvider state pluginId (RenameParams _prog (TextDocumentIdentifier uri) p [] -> throwError $ PluginInvalidParams "No symbol to rename at given position" _ -> do refs <- HS.fromList . concat <$> mapM (refsAtName state nfp) oldNames + -- Validate rename crossModuleEnabled <- liftIO $ runAction "rename: config" state $ usePropertyAction #crossModule pluginId properties unless crossModuleEnabled $ failWhenImportOrExport state nfp refs oldNames @@ -196,10 +197,7 @@ refsAtName state nfp name = do dbRefs <- case nameModule_maybe name of Nothing -> pure [] Just mod -> liftIO $ mapMaybe rowToLoc <$> withHieDb (\hieDb -> - -- GHC inserts `Use`s of record constructor everywhere where its record selectors are used, - -- which leads to 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. + -- See Note [Generated references] filter (\(refRow HieDb.:. _) -> refIsGenerated refRow) <$> findReferences hieDb @@ -235,8 +233,13 @@ 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 = sourceOnlyAsts, refMap = sourceOnlyRefMap, ..}