Skip to content

Commit 07623e0

Browse files
guiboupepeiborra
andauthored
Restore eval plugin build for GHC 9.2 (#2669)
* tests: Test for eval plugin now show correct line in output Using `HasCallStack`, `testCase` can no pinpoint the call location instead of pointing inside the utility function. * 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. * fix: remove unused log mecanism * Refactor: move pragma to compat module * refactor: Remove now useless dependency * fix: remove an unused import * Disable a test for eval plugin for GHC 9.2 Eval plugin does not report progress, I don't understand why. * fix: type +v actually also works with GHC 9.0 Co-authored-by: Pepe Iborra <[email protected]>
1 parent ea1b41d commit 07623e0

22 files changed

+258
-59
lines changed

cabal-ghc921.project

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ constraints:
4646
+ignore-plugins-ghc-bounds
4747
-brittany
4848
-class
49-
-eval
5049
-haddockComments
5150
-hlint
5251
-retrie

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

Lines changed: 8 additions & 0 deletions
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)

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ module Development.IDE.GHC.Compat.Parser (
4141
#if !MIN_VERSION_ghc(9,2,0)
4242
Anno.AnnotationComment(..),
4343
#endif
44+
pattern EpaLineComment,
45+
pattern EpaBlockComment
4446
) where
4547

4648
#if MIN_VERSION_ghc(9,0,0)
@@ -51,12 +53,18 @@ import qualified GHC.Parser.Annotation as Anno
5153
import qualified GHC.Parser.Lexer as Lexer
5254
import GHC.Types.SrcLoc (PsSpan (..))
5355
#if MIN_VERSION_ghc(9,2,0)
54-
import GHC (pm_extra_src_files,
56+
import GHC (Anchor (anchor),
57+
EpAnnComments (priorComments),
58+
EpaComment (EpaComment),
59+
EpaCommentTok (..),
60+
epAnnComments,
61+
pm_extra_src_files,
5562
pm_mod_summary,
5663
pm_parsed_source)
5764
import qualified GHC
5865
import qualified GHC.Driver.Config as Config
59-
import GHC.Hs (hpm_module, hpm_src_files)
66+
import GHC.Hs (LEpaComment, hpm_module,
67+
hpm_src_files)
6068
import GHC.Parser.Lexer hiding (initParserState)
6169
#endif
6270
#else
@@ -100,6 +108,8 @@ initParserState =
100108
#endif
101109

102110
#if MIN_VERSION_ghc(9,2,0)
111+
-- GHC 9.2 does not have ApiAnns anymore packaged in ParsedModule. Now the
112+
-- annotations are found in the ast.
103113
type ApiAnns = ()
104114
#else
105115
type ApiAnns = Anno.ApiAnns
@@ -155,3 +165,8 @@ mkApiAnns pst =
155165
:annotations_comments pst))
156166
#endif
157167
#endif
168+
169+
#if !MIN_VERSION_ghc(9,2,0)
170+
pattern EpaLineComment a = Anno.AnnLineComment a
171+
pattern EpaBlockComment a = Anno.AnnBlockComment a
172+
#endif

plugins/hls-eval-plugin/hls-eval-plugin.cabal

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ library
7979
, pretty-simple
8080
, QuickCheck
8181
, safe-exceptions
82-
, temporary
8382
, text
8483
, time
8584
, transformers

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

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,19 @@ 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)
80+
#endif
7881
import qualified GHC.LanguageExtensions.Type as LangExt (Extension (..))
7982

8083
import Development.IDE.Core.FileStore (setSomethingModified)
8184
import Development.IDE.Types.Shake (toKey)
8285
import Ide.Plugin.Config (Config)
86+
#if MIN_VERSION_ghc(9,2,0)
87+
import GHC.Types.SrcLoc (UnhelpfulSpanReason (UnhelpfulInteractive))
88+
#endif
8389
import Ide.Plugin.Eval.Code (Statement, asStatements,
8490
evalSetup, myExecStmt,
8591
propSetup, resultRange,
@@ -102,11 +108,9 @@ import Language.LSP.Types hiding
102108
SemanticTokenRelative (length))
103109
import Language.LSP.Types.Lens (end, line)
104110
import Language.LSP.VFS (virtualFileText)
105-
import System.FilePath (takeFileName)
106-
import System.IO (hClose)
107-
import UnliftIO.Temporary (withSystemTempFile)
108111

109-
#if MIN_VERSION_ghc(9,0,0)
112+
#if MIN_VERSION_ghc(9,2,0)
113+
#elif MIN_VERSION_ghc(9,0,0)
110114
import GHC.Driver.Session (unitDatabases, unitState)
111115
import GHC.Types.SrcLoc (UnhelpfulSpanReason (UnhelpfulInteractive))
112116
#else
@@ -218,7 +222,7 @@ runEvalCmd plId st EvalParams{..} =
218222
(Just (textToStringBuffer mdlText, now))
219223

220224
-- Setup environment for evaluation
221-
hscEnv' <- ExceptT $ fmap join $ withSystemTempFile (takeFileName fp) $ \logFilename logHandle -> liftIO . gStrictTry . evalGhcEnv session $ do
225+
hscEnv' <- ExceptT $ fmap join $ liftIO . gStrictTry . evalGhcEnv session $ do
222226
env <- getSession
223227

224228
-- Install the module pragmas and options
@@ -247,13 +251,8 @@ runEvalCmd plId st EvalParams{..} =
247251
$ idflags
248252
setInteractiveDynFlags $ df'
249253
#if MIN_VERSION_ghc(9,0,0)
250-
{ unitState =
251-
unitState
252-
df
253-
, unitDatabases =
254-
unitDatabases
255-
df
256-
, packageFlags =
254+
{
255+
packageFlags =
257256
packageFlags
258257
df
259258
, useColor = Never
@@ -274,15 +273,6 @@ runEvalCmd plId st EvalParams{..} =
274273
}
275274
#endif
276275

277-
-- set up a custom log action
278-
#if MIN_VERSION_ghc(9,0,0)
279-
setLogAction $ \_df _wr _sev _span _doc ->
280-
defaultLogActionHPutStrDoc _df logHandle _doc
281-
#else
282-
setLogAction $ \_df _wr _sev _span _style _doc ->
283-
defaultLogActionHPutStrDoc _df logHandle _doc _style
284-
#endif
285-
286276
-- Load the module with its current content (as the saved module might not be up to date)
287277
-- BUG: this fails for files that requires preprocessors (e.g. CPP) for ghc < 8.8
288278
-- see https://gitlab.haskell.org/ghc/ghc/-/issues/17066
@@ -295,8 +285,7 @@ runEvalCmd plId st EvalParams{..} =
295285
dbg "LOAD RESULT" $ asS loadResult
296286
case loadResult of
297287
Failed -> liftIO $ do
298-
hClose logHandle
299-
err <- readFile logFilename
288+
let err = ""
300289
dbg "load ERR" err
301290
return $ Left err
302291
Succeeded -> do
@@ -687,7 +676,9 @@ doTypeCmd dflags arg = do
687676

688677
parseExprMode :: Text -> (TcRnExprMode, T.Text)
689678
parseExprMode rawArg = case T.break isSpace rawArg of
679+
#if !MIN_VERSION_ghc(9,2,0)
690680
("+v", rest) -> (TM_NoInst, T.strip rest)
681+
#endif
691682
("+d", rest) -> (TM_Default, T.strip rest)
692683
_ -> (TM_Inst, rawArg)
693684

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

Lines changed: 34 additions & 9 deletions
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) ==
@@ -80,14 +105,14 @@ evalParsedModuleRule = defineEarlyCutoff $ RuleNoDiagnostics $ \GetEvalComments
80105
-- since Haddock parsing is unset explicitly in 'getParsedModuleWithComments',
81106
-- we can concentrate on these two
82107
case bdy of
83-
AnnLineComment cmt ->
108+
EpaLineComment cmt ->
84109
mempty { lineComments = Map.singleton curRan (RawLineComment cmt) }
85-
AnnBlockComment cmt ->
110+
EpaBlockComment cmt ->
86111
mempty { blockComments = Map.singleton curRan $ RawBlockComment cmt }
87112
_ -> mempty
88113
_ -> mempty
89114
)
90-
$ apiAnnComments' pm_annotations
115+
$ apiAnnComments' pm
91116
-- we only care about whether the comments are null
92117
-- this is valid because the only dependent is NeedsCompilation
93118
fingerPrint = fromString $ if nullComments comments then "" else "1"

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

Lines changed: 32 additions & 20 deletions
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
@@ -261,7 +273,7 @@ diffOffConfig =
261273
unObject (Object obj) = obj
262274
unObject _ = undefined
263275

264-
evalInFile :: FilePath -> T.Text -> T.Text -> IO ()
276+
evalInFile :: HasCallStack => FilePath -> T.Text -> T.Text -> IO ()
265277
evalInFile fp e expected = runSessionWithServer evalPlugin testDataDir $ do
266278
doc <- openDoc fp "haskell"
267279
origin <- documentContents doc
Lines changed: 11 additions & 0 deletions
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
Lines changed: 11 additions & 0 deletions
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
Lines changed: 4 additions & 0 deletions
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'
Lines changed: 4 additions & 0 deletions
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'

0 commit comments

Comments
 (0)