Skip to content

Commit 05b03ab

Browse files
committed
Never compute the DocMap with stale data
As the regression test 'eps-pollution' demonstrates, changing the imports of a module with class instances can poison the HLS session. To fix this, we simply shouldn't use stale results for computing the `DocMap`. Frankly, there is no reason to use stale results when we are not even using the `PositionMapping`, this sounds like a recipe for disaster in general. Especially, since the source locations matter.
1 parent 326b1a4 commit 05b03ab

3 files changed

Lines changed: 21 additions & 25 deletions

File tree

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
{-# LANGUAGE TypeApplications #-}
2+
{-# OPTIONS_GHC -Wall #-}
13
module C where
24
import A
35

4-
cFoo :: AType -> String
5-
cFoo = myMethod
6+
-- Omit top-level signature so we have a warning we can check against
7+
cFoo = myMethod @AType

ghcide-test/exe/EpsPollutionTests.hs

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
-- 'ClsInst' twice (once via @ie_global@ from the EPS, once via
88
-- @ie_local@ from 'hptInstancesBelow').
99
--
10-
-- The pollution enters through 'Development.IDE.Spans.Documentation.mkDocMap'.
11-
-- Its 'Rules.GetDocMap' rule reads three inputs via independent
10+
-- The pollution entered through 'Development.IDE.Spans.Documentation.mkDocMap'.
11+
-- Its 'Rules.GetDocMap' rule read three inputs via independent
1212
-- @useWithStale_@ calls: 'TypeCheck', 'GhcSessionDeps' and 'GetHieAst'.
1313
-- These three can diverge: an edit that merely changes imports lets
1414
-- 'GhcSessionDeps' re-evaluate (fresh, with a different HPT) while
@@ -21,28 +21,22 @@
2121
-- evicts anything, so the pollution is permanent for the session.
2222
module EpsPollutionTests (tests) where
2323

24-
import Config (runWithExtraFiles)
24+
import Config (Expect (ExpectHoverText),
25+
runWithExtraFiles)
2526
import Control.Lens ((^.))
26-
import Control.Monad.IO.Class (liftIO)
27+
import Control.Monad (void)
2728
import qualified Data.Text as T
2829
import Development.IDE.GHC.Util (readFileUtf8)
29-
import Development.IDE.Test (waitForTypecheck)
30+
import Hover
3031
import qualified Language.LSP.Protocol.Lens as L
3132
import Language.LSP.Protocol.Types
3233
import Language.LSP.Test
3334
import System.FilePath
34-
import Test.Hls (expectFailBecause,
35-
waitForDiagnosticsFrom)
36-
import Test.Tasty
37-
import Test.Tasty.HUnit
35+
import Test.Hls
3836

3937
tests :: TestTree
4038
tests = testGroup "eps-pollution"
41-
[ expectFailBecause
42-
"The EPS gets polluted with a home-module instance via GetDocMap's \
43-
\inconsistent stale-value snapshot; the next successful typecheck \
44-
\sees the instance twice."
45-
staleHieProvokesOverlapping
39+
[ staleHieProvokesOverlapping
4640
]
4741

4842
-- The fixture at ghcide-test/data/multi-unit-eps-pollution/ sets up two
@@ -59,20 +53,22 @@ staleHieProvokesOverlapping =
5953
originalC <- liftIO $ readFileUtf8 cPath
6054
let brokenC = T.replace "import A\n" "" originalC
6155
cdoc <- openDoc cPath "haskell"
62-
True <- waitForTypecheck cdoc
56+
void $ waitForTypecheck cdoc
6357
-- Hovering triggers the hover pipeline, which forces GetDocMap.
6458
-- While C is healthy this populates GetHieAst with a RefMap
6559
-- referencing A's names -- the stale value we rely on below.
66-
_ <- getHover cdoc (hoverOnMyMethod originalC)
60+
hover <- getHover cdoc (hoverOnMyMethod originalC)
61+
checkHover hover [ExpectHoverText ["myMethod", "MyClass"]]
62+
6763
-- Break C's import of A. C fails to typecheck, but GhcSessionDeps
6864
-- re-evaluates successfully (it only needs the import list) with an
6965
-- HPT that no longer contains A. A further hover forces GetDocMap
7066
-- to run with the fresh GhcSessionDeps alongside the stale RefMap;
7167
-- loadSysInterface(A) then runs and pollutes the EPS.
7268
changeDoc cdoc [TextDocumentContentChangeEvent . InR .
7369
TextDocumentContentChangeWholeDocument $ brokenC]
74-
_ <- getHover cdoc (hoverOnMyMethod brokenC)
75-
_ <- waitForDiagnosticsFrom cdoc -- let the broken state settle
70+
void $ getHover cdoc (hoverOnMyMethod brokenC)
71+
void $ waitForDiagnosticsFrom cdoc -- let the broken state settle
7672
-- Repair C. The next typecheck legitimately has A in its HPT; with
7773
-- the polluted EPS it also has A's ClsInst in eps_inst_env, so
7874
-- instance resolution for 'myMethod x :: AType -> String' finds

ghcide/src/Development/IDE/Core/Rules.hs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -579,11 +579,9 @@ getBindingsRule recorder =
579579
getDocMapRule :: Recorder (WithPriority Log) -> Rules ()
580580
getDocMapRule recorder =
581581
define (cmapWithPrio LogShake recorder) $ \GetDocMap file -> do
582-
-- Stale data for the scenario where a broken module has previously typechecked
583-
-- but we never generated a DocMap for it
584-
(tmrTypechecked -> tc, _) <- useWithStale_ TypeCheck file
585-
(hscEnv -> hsc, _) <- useWithStale_ GhcSessionDeps file
586-
(HAR{refMap=rf}, _) <- useWithStale_ GetHieAst file
582+
(tmrTypechecked -> tc) <- use_ TypeCheck file
583+
(hscEnv -> hsc) <- use_ GhcSessionDeps file
584+
HAR{refMap=rf} <- use_ GetHieAst file
587585
cfg <- getClientConfigAction
588586
dkMap <- liftIO $ mkDocMap hsc rf tc $ LinkTargets
589587
{ linkSource = linkSourceTo cfg

0 commit comments

Comments
 (0)