Skip to content

Commit 54e352e

Browse files
committed
Restore eval plugin build for GHC 9.2
It restores the eval plugin. Now annotations with comments are found by walking the AST and locating specific annotations. In order to fix unit test, I implemented a new golden test function which accepts a different naming scheme depending on the GHC version.
1 parent c1105f6 commit 54e352e

20 files changed

+274
-40
lines changed

cabal-ghc921.project

-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ constraints:
4747
-alternateNumberFormat
4848
-brittany
4949
-class
50-
-eval
5150
-haddockComments
5251
-hlint
5352
-retrie

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

+13
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@ import System.IO.Extra (fixIO, newTempFileWithin)
9595

9696
-- GHC API imports
9797
-- GHC API imports
98+
#if MIN_VERSION_ghc(9,2,0)
99+
import GHC (Anchor (anchor),
100+
EpaComment (EpaComment),
101+
EpaCommentTok (EpaBlockComment, EpaLineComment),
102+
epAnnComments,
103+
priorComments)
104+
import GHC.Hs (LEpaComment)
105+
#endif
98106
import GHC (GetDocsFailure (..),
99107
mgModSummaries,
100108
parsedSource)
@@ -873,7 +881,12 @@ parseFileContents env customPreprocessor filename ms = do
873881
PFailedWithErrorMessages msgs -> throwE $ diagFromErrMsgs "parser" dflags $ msgs dflags
874882
POk pst rdr_module ->
875883
let
884+
#if MIN_VERSION_ghc(9,2,1)
885+
-- TODO: we need to export the annotations here
886+
hpm_annotations = ()
887+
#else
876888
hpm_annotations = mkApiAnns pst
889+
#endif
877890
(warns, errs) = getMessages' pst dflags
878891
in
879892
do

ghcide/src/Development/IDE/GHC/Compat/Parser.hs

+10-2
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,18 @@ import qualified GHC.Parser.Annotation as Anno
5151
import qualified GHC.Parser.Lexer as Lexer
5252
import GHC.Types.SrcLoc (PsSpan (..))
5353
#if MIN_VERSION_ghc(9,2,0)
54-
import GHC (pm_extra_src_files,
54+
import GHC (Anchor (anchor),
55+
EpAnnComments (priorComments),
56+
EpaComment (EpaComment),
57+
EpaCommentTok (..),
58+
epAnnComments,
59+
pm_extra_src_files,
5560
pm_mod_summary,
5661
pm_parsed_source)
5762
import qualified GHC
5863
import qualified GHC.Driver.Config as Config
59-
import GHC.Hs (hpm_module, hpm_src_files)
64+
import GHC.Hs (LEpaComment, hpm_module,
65+
hpm_src_files)
6066
import GHC.Parser.Lexer hiding (initParserState)
6167
#endif
6268
#else
@@ -100,6 +106,8 @@ initParserState =
100106
#endif
101107

102108
#if MIN_VERSION_ghc(9,2,0)
109+
-- GHC 9.2 does not have ApiAnns anymore packaged in ParsedModule. Now the
110+
-- annotations are found in the ast.
103111
type ApiAnns = ()
104112
#else
105113
type ApiAnns = Anno.ApiAnns

plugins/hls-eval-plugin/src/Ide/Plugin/Eval/CodeLens.hs

+29-11
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,21 @@ import GHC (ClsInst,
7373
getInteractiveDynFlags,
7474
isImport, isStmt, load,
7575
parseName, pprFamInst,
76-
pprInstance, setLogAction,
77-
setTargets, typeKind)
76+
pprInstance, setTargets,
77+
typeKind)
78+
#if MIN_VERSION_ghc(9,2,0)
79+
import GHC (Fixity, pushLogHookM)
80+
#else
81+
import GHC (setLogAction)
82+
#endif
7883
import qualified GHC.LanguageExtensions.Type as LangExt (Extension (..))
7984

8085
import Development.IDE.Core.FileStore (setSomethingModified)
8186
import Development.IDE.Types.Shake (toKey)
8287
import Ide.Plugin.Config (Config)
88+
#if MIN_VERSION_ghc(9,2,0)
89+
import GHC.Types.SrcLoc (UnhelpfulSpanReason (UnhelpfulInteractive))
90+
#endif
8391
import Ide.Plugin.Eval.Code (Statement, asStatements,
8492
evalSetup, myExecStmt,
8593
propSetup, resultRange,
@@ -106,7 +114,8 @@ import System.FilePath (takeFileName)
106114
import System.IO (hClose)
107115
import UnliftIO.Temporary (withSystemTempFile)
108116

109-
#if MIN_VERSION_ghc(9,0,0)
117+
#if MIN_VERSION_ghc(9,2,0)
118+
#elif MIN_VERSION_ghc(9,0,0)
110119
import GHC.Driver.Session (unitDatabases, unitState)
111120
import GHC.Types.SrcLoc (UnhelpfulSpanReason (UnhelpfulInteractive))
112121
#else
@@ -247,13 +256,8 @@ runEvalCmd plId st EvalParams{..} =
247256
$ idflags
248257
setInteractiveDynFlags $ df'
249258
#if MIN_VERSION_ghc(9,0,0)
250-
{ unitState =
251-
unitState
252-
df
253-
, unitDatabases =
254-
unitDatabases
255-
df
256-
, packageFlags =
259+
{
260+
packageFlags =
257261
packageFlags
258262
df
259263
, useColor = Never
@@ -275,7 +279,19 @@ runEvalCmd plId st EvalParams{..} =
275279
#endif
276280

277281
-- set up a custom log action
278-
#if MIN_VERSION_ghc(9,0,0)
282+
#if MIN_VERSION_ghc(9,2,0)
283+
-- NOTE: I removed that, it was breaking the eval plugin, sometimes
284+
-- I don't know why, I don't even understand what is the purpose of theses lines.
285+
-- I just copied them from the previous version and tried to
286+
-- adapt them to the new GHC 9.2 API.
287+
--
288+
-- But tests are fine without this.
289+
-- So, what have I missed?
290+
--
291+
-- pushLogHookM . const $ \_df _wr _sev _span _doc ->
292+
-- defaultLogActionHPutStrDoc _df False logHandle _doc
293+
-- TODO: check the True
294+
#elif MIN_VERSION_ghc(9,0,0)
279295
setLogAction $ \_df _wr _sev _span _doc ->
280296
defaultLogActionHPutStrDoc _df logHandle _doc
281297
#else
@@ -687,7 +703,9 @@ doTypeCmd dflags arg = do
687703

688704
parseExprMode :: Text -> (TcRnExprMode, T.Text)
689705
parseExprMode rawArg = case T.break isSpace rawArg of
706+
#if !MIN_VERSION_ghc(9,0,0)
690707
("+v", rest) -> (TM_NoInst, T.strip rest)
708+
#endif
691709
("+d", rest) -> (TM_Default, T.strip rest)
692710
_ -> (TM_Inst, rawArg)
693711

plugins/hls-eval-plugin/src/Ide/Plugin/Eval/Rules.hs

+41-7
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ import Development.IDE.GHC.Compat
3636
import qualified Development.IDE.GHC.Compat as SrcLoc
3737
import qualified Development.IDE.GHC.Compat.Util as FastString
3838
import Development.IDE.Graph (alwaysRerun)
39+
#if MIN_VERSION_ghc(9,2,0)
40+
import GHC.Parser.Annotation
41+
#endif
3942
import Ide.Plugin.Eval.Types
4043

4144

@@ -53,22 +56,44 @@ queueForEvaluation ide nfp = do
5356
EvaluatingVar var <- getIdeGlobalState ide
5457
modifyIORef var (Set.insert nfp)
5558

56-
#if MIN_VERSION_ghc(9,0,0)
59+
#if MIN_VERSION_ghc(9,2,0)
60+
getAnnotations :: Development.IDE.GHC.Compat.Located HsModule -> [LEpaComment]
61+
getAnnotations (L _ m@(HsModule { hsmodAnn = anns'})) =
62+
priorComments annComments <> getFollowingComments annComments
63+
<> concatMap getCommentsForDecl (hsmodImports m)
64+
<> concatMap getCommentsForDecl (hsmodDecls m)
65+
where
66+
annComments = epAnnComments anns'
67+
68+
getCommentsForDecl :: GenLocated (SrcSpanAnn' (EpAnn ann)) e
69+
-> [LEpaComment]
70+
getCommentsForDecl (L (SrcSpanAnn (EpAnn _ _ cs) _) _) = priorComments cs <> getFollowingComments cs
71+
getCommentsForDecl (L (SrcSpanAnn (EpAnnNotUsed) _) _) = []
72+
73+
apiAnnComments' :: ParsedModule -> [SrcLoc.RealLocated EpaCommentTok]
74+
apiAnnComments' pm = do
75+
L span (EpaComment c _) <- getAnnotations $ pm_parsed_source pm
76+
pure (L (anchor span) c)
77+
78+
pattern RealSrcSpanAlready :: SrcLoc.RealSrcSpan -> SrcLoc.RealSrcSpan
79+
pattern RealSrcSpanAlready x = x
80+
#elif MIN_VERSION_ghc(9,0,0)
81+
apiAnnComments' :: ParsedModule -> [SrcLoc.RealLocated AnnotationComment]
82+
apiAnnComments' = apiAnnRogueComments . pm_annotations
83+
5784
pattern RealSrcSpanAlready :: SrcLoc.RealSrcSpan -> SrcLoc.RealSrcSpan
5885
pattern RealSrcSpanAlready x = x
59-
apiAnnComments' :: SrcLoc.ApiAnns -> [SrcLoc.RealLocated AnnotationComment]
60-
apiAnnComments' = apiAnnRogueComments
6186
#else
62-
apiAnnComments' :: SrcLoc.ApiAnns -> [SrcLoc.Located AnnotationComment]
63-
apiAnnComments' = concat . Map.elems . snd
87+
apiAnnComments' :: ParsedModule -> [SrcLoc.Located AnnotationComment]
88+
apiAnnComments' = concat . Map.elems . snd . pm_annotations
6489

6590
pattern RealSrcSpanAlready :: SrcLoc.RealSrcSpan -> SrcSpan
6691
pattern RealSrcSpanAlready x = SrcLoc.RealSrcSpan x Nothing
6792
#endif
6893

6994
evalParsedModuleRule :: Rules ()
7095
evalParsedModuleRule = defineEarlyCutoff $ RuleNoDiagnostics $ \GetEvalComments nfp -> do
71-
(ParsedModule{..}, posMap) <- useWithStale_ GetParsedModuleWithComments nfp
96+
(pm, posMap) <- useWithStale_ GetParsedModuleWithComments nfp
7297
let comments = foldMap (\case
7398
L (RealSrcSpanAlready real) bdy
7499
| FastString.unpackFS (srcSpanFile real) ==
@@ -79,15 +104,24 @@ evalParsedModuleRule = defineEarlyCutoff $ RuleNoDiagnostics $ \GetEvalComments
79104

80105
-- since Haddock parsing is unset explicitly in 'getParsedModuleWithComments',
81106
-- we can concentrate on these two
107+
#if MIN_VERSION_ghc(9,2,0)
108+
case bdy of
109+
EpaLineComment cmt ->
110+
mempty { lineComments = Map.singleton curRan (RawLineComment cmt) }
111+
EpaBlockComment cmt ->
112+
mempty { blockComments = Map.singleton curRan $ RawBlockComment cmt }
113+
_ -> mempty
114+
#else
82115
case bdy of
83116
AnnLineComment cmt ->
84117
mempty { lineComments = Map.singleton curRan (RawLineComment cmt) }
85118
AnnBlockComment cmt ->
86119
mempty { blockComments = Map.singleton curRan $ RawBlockComment cmt }
87120
_ -> mempty
121+
#endif
88122
_ -> mempty
89123
)
90-
$ apiAnnComments' pm_annotations
124+
$ apiAnnComments' pm
91125
-- we only care about whether the comments are null
92126
-- this is valid because the only dependent is NeedsCompilation
93127
fingerPrint = fromString $ if nullComments comments then "" else "1"

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

+31-19
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE MultiWayIf #-}
12
{-# LANGUAGE OverloadedStrings #-}
23
{-# LANGUAGE RecordWildCards #-}
34
{-# LANGUAGE TypeApplications #-}
@@ -69,40 +70,43 @@ tests =
6970
, goldenWithEval "Refresh a multiline evaluation" "T7" "hs"
7071
, testCase "Semantic and Lexical errors are reported" $ do
7172
evalInFile "T8.hs" "-- >>> noFunctionWithThisName" "-- Variable not in scope: noFunctionWithThisName"
72-
evalInFile "T8.hs" "-- >>> \"a\" + \"bc\"" $
73-
if ghcVersion == GHC90
74-
then "-- No instance for (Num String) arising from a use of ‘+’"
75-
else "-- No instance for (Num [Char]) arising from a use of ‘+’"
73+
evalInFile "T8.hs" "-- >>> res = \"a\" + \"bc\"" $
74+
if
75+
| ghcVersion == GHC92 -> "-- No instance for (Num String) arising from a use of `+'\n-- In the expression: \"a\" + \"bc\"\n-- In an equation for `res': res = \"a\" + \"bc\""
76+
| ghcVersion == GHC90 -> "-- No instance for (Num String) arising from a use of ‘+’"
77+
| otherwise -> "-- No instance for (Num [Char]) arising from a use of ‘+’"
7678
evalInFile "T8.hs" "-- >>> \"" "-- lexical error in string/character literal at end of input"
7779
evalInFile "T8.hs" "-- >>> 3 `div` 0" "-- divide by zero"
7880
, goldenWithEval "Applies file LANGUAGE extensions" "T9" "hs"
79-
, goldenWithEval "Evaluate a type with :kind!" "T10" "hs"
80-
, goldenWithEval "Reports an error for an incorrect type with :kind!" "T11" "hs"
81-
, goldenWithEval "Shows a kind with :kind" "T12" "hs"
82-
, goldenWithEval "Reports an error for an incorrect type with :kind" "T13" "hs"
81+
, goldenWithEval' "Evaluate a type with :kind!" "T10" "hs" (if ghcVersion == GHC92 then "ghc92.expected" else "expected")
82+
, goldenWithEval' "Reports an error for an incorrect type with :kind!" "T11" "hs" (if ghcVersion == GHC92 then "ghc92.expected" else "expected")
83+
, goldenWithEval' "Shows a kind with :kind" "T12" "hs" (if ghcVersion == GHC92 then "ghc92.expected" else "expected")
84+
, goldenWithEval' "Reports an error for an incorrect type with :kind" "T13" "hs" (if ghcVersion == GHC92 then "ghc92.expected" else "expected")
8385
, goldenWithEval "Returns a fully-instantiated type for :type" "T14" "hs"
84-
, goldenWithEval "Returns an uninstantiated type for :type +v, admitting multiple whitespaces around arguments" "T15" "hs"
86+
, knownBrokenForGhcVersions [GHC92] "type +v does not work anymore with 9.2" $ goldenWithEval "Returns an uninstantiated type for :type +v, admitting multiple whitespaces around arguments" "T15" "hs"
8587
, goldenWithEval "Returns defaulted type for :type +d, admitting multiple whitespaces around arguments" "T16" "hs"
86-
, goldenWithEval ":type reports an error when given with unknown +x option" "T17" "hs"
88+
, goldenWithEval' ":type reports an error when given with unknown +x option" "T17" "hs" (if ghcVersion == GHC92 then "ghc92.expected" else "expected")
8789
, goldenWithEval "Reports an error when given with unknown command" "T18" "hs"
8890
, goldenWithEval "Returns defaulted type for :type +d reflecting the default declaration specified in the >>> prompt" "T19" "hs"
8991
, expectFailBecause "known issue - see a note in P.R. #361" $
90-
goldenWithEval ":type +d reflects the `default' declaration of the module" "T20" "hs"
92+
goldenWithEval' ":type +d reflects the `default' declaration of the module" "T20" "hs" (if ghcVersion == GHC92 then "ghc92.expected" else "expected")
9193
, testCase ":type handles a multilined result properly" $
9294
evalInFile "T21.hs" "-- >>> :type fun" $ T.unlines [
9395
"-- fun",
94-
if ghcVersion == GHC90
95-
then "-- :: forall {k1} {k2 :: Nat} {n :: Nat} {a :: k1}."
96-
else "-- :: forall k1 (k2 :: Nat) (n :: Nat) (a :: k1).",
96+
if
97+
| ghcVersion == GHC92 -> "-- :: forall {k1} (k2 :: Nat) (n :: Nat) (a :: k1)."
98+
| ghcVersion == GHC90 -> "-- :: forall {k1} {k2 :: Nat} {n :: Nat} {a :: k1}."
99+
| otherwise -> "-- :: forall k1 (k2 :: Nat) (n :: Nat) (a :: k1).",
97100
"-- (KnownNat k2, KnownNat n, Typeable a) =>",
98101
"-- Proxy k2 -> Proxy n -> Proxy a -> ()"
99102
]
100103
, goldenWithEval ":t behaves exactly the same as :type" "T22" "hs"
101104
, testCase ":type does \"dovetails\" for short identifiers" $
102105
evalInFile "T23.hs" "-- >>> :type f" $ T.unlines [
103-
if ghcVersion == GHC90
104-
then "-- f :: forall {k1} {k2 :: Nat} {n :: Nat} {a :: k1}."
105-
else "-- f :: forall k1 (k2 :: Nat) (n :: Nat) (a :: k1).",
106+
if
107+
| ghcVersion == GHC92 -> "-- f :: forall {k1} (k2 :: Nat) (n :: Nat) (a :: k1)."
108+
| ghcVersion == GHC90 -> "-- f :: forall {k1} {k2 :: Nat} {n :: Nat} {a :: k1}."
109+
| otherwise -> "-- f :: forall k1 (k2 :: Nat) (n :: Nat) (a :: k1).",
106110
"-- (KnownNat k2, KnownNat n, Typeable a) =>",
107111
"-- Proxy k2 -> Proxy n -> Proxy a -> ()"
108112
]
@@ -119,11 +123,13 @@ tests =
119123
, goldenWithEval "Transitive local dependency" "TTransitive" "hs"
120124
-- , goldenWithEval "Local Modules can be imported in a test" "TLocalImportInTest" "hs"
121125
, goldenWithEval "Setting language option TupleSections" "TLanguageOptionsTupleSections" "hs"
122-
, goldenWithEval ":set accepts ghci flags" "TFlags" "hs"
126+
, goldenWithEval' ":set accepts ghci flags" "TFlags" "hs" (if ghcVersion == GHC92 then "ghc92.expected" else "expected")
123127
, testCase ":set -fprint-explicit-foralls works" $ do
124128
evalInFile "T8.hs" "-- >>> :t id" "-- id :: a -> a"
125129
evalInFile "T8.hs" "-- >>> :set -fprint-explicit-foralls\n-- >>> :t id"
126-
"-- id :: forall {a}. a -> a"
130+
(if ghcVersion == GHC92
131+
then "-- id :: forall a. a -> a"
132+
else "-- id :: forall {a}. a -> a")
127133
, goldenWithEval "The default language extensions for the eval plugin are the same as those for ghci" "TSameDefaultLanguageExtensionsAsGhci" "hs"
128134
, goldenWithEval "IO expressions are supported, stdout/stderr output is ignored" "TIO" "hs"
129135
, goldenWithEval "Property checking" "TProperty" "hs"
@@ -210,6 +216,12 @@ goldenWithEval :: TestName -> FilePath -> FilePath -> TestTree
210216
goldenWithEval title path ext =
211217
goldenWithHaskellDoc evalPlugin title testDataDir path "expected" ext executeLensesBackwards
212218

219+
-- | Similar function as 'goldenWithEval' with an alternate reference file
220+
-- naming. Useful when reference file may change because of GHC version.
221+
goldenWithEval' :: TestName -> FilePath -> FilePath -> FilePath -> TestTree
222+
goldenWithEval' title path ext expected =
223+
goldenWithHaskellDoc evalPlugin title testDataDir path expected ext executeLensesBackwards
224+
213225
-- | Execute lenses backwards, to avoid affecting their position in the source file
214226
executeLensesBackwards :: TextDocumentIdentifier -> Session ()
215227
executeLensesBackwards doc = do
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{-# LANGUAGE DataKinds, TypeOperators #-}
2+
module T10 where
3+
import GHC.TypeNats ( type (+) )
4+
5+
type Dummy = 1 + 1
6+
7+
-- >>> type N = 1
8+
-- >>> type M = 40
9+
-- >>> :kind! N + M + 1
10+
-- N + M + 1 :: Natural
11+
-- = 42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{-# LANGUAGE DataKinds, TypeOperators #-}
2+
module T10 where
3+
import GHC.TypeNats ( type (+) )
4+
5+
type Dummy = 1 + 1
6+
7+
-- >>> type N = 1
8+
-- >>> type M = 40
9+
-- >>> :kind! N + M + 1
10+
-- N + M + 1 :: Natural
11+
-- = 42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module T11 where
2+
3+
-- >>> :kind! a
4+
-- Not in scope: type variable `a'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module T11 where
2+
3+
-- >>> :kind! a
4+
-- Not in scope: type variable `a'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{-# LANGUAGE DataKinds, TypeOperators #-}
2+
module T12 where
3+
import GHC.TypeNats ( type (+) )
4+
5+
type Dummy = 1 + 1
6+
7+
-- >>> type N = 1
8+
-- >>> type M = 40
9+
-- >>> :kind N + M + 1
10+
-- N + M + 1 :: Natural
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{-# LANGUAGE DataKinds, TypeOperators #-}
2+
module T12 where
3+
import GHC.TypeNats ( type (+) )
4+
5+
type Dummy = 1 + 1
6+
7+
-- >>> type N = 1
8+
-- >>> type M = 40
9+
-- >>> :kind N + M + 1
10+
-- N + M + 1 :: Natural
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module T13 where
2+
3+
-- >>> :kind a
4+
-- Not in scope: type variable `a'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module T13 where
2+
3+
-- >>> :kind a
4+
-- Not in scope: type variable `a'

0 commit comments

Comments
 (0)