Skip to content

Commit a11f6b9

Browse files
committed
GHC 9.12 compat
1 parent 7646f9a commit a11f6b9

File tree

21 files changed

+287
-53
lines changed

21 files changed

+287
-53
lines changed

.github/workflows/build.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ jobs:
2828
- '9.6'
2929
- '9.8'
3030
- '9.10'
31+
- 'latest'
3132
include:
3233
- os: macos-latest
33-
ghc: '9.10'
34+
ghc: system
3435
steps:
3536
- uses: actions/checkout@v4
3637
- uses: hspec/setup-haskell@v1
@@ -52,7 +53,7 @@ jobs:
5253
name: ${{ steps.archive.outputs.name }}
5354
path: ${{ steps.archive.outputs.name }}
5455
compression-level: 0
55-
if: matrix.ghc == '9.10'
56+
if: matrix.ghc == 'latest'
5657

5758
- shell: bash
5859
run: |
@@ -61,7 +62,7 @@ jobs:
6162
ghcup install ghc $GHC --no-set
6263
SENSEI_GHC=ghc-$GHC $(cabal list-bin spec)
6364
done
64-
if: matrix.ghc == '9.10'
65+
if: matrix.ghc == 'latest'
6566

6667
success:
6768
needs: build

src/GHC/Diagnostic.hs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ module GHC.Diagnostic (
1111
, apply
1212

1313
#ifdef TEST
14+
, analyzeHint
1415
, extractIdentifiers
1516
, qualifiedName
1617
, applyReplace
1718
#endif
1819
) where
1920

20-
import Imports hiding (stripPrefix)
21+
import Imports hiding (stripPrefix, takeExtensions)
2122
import Builder (Builder, Color(..))
2223
import qualified Builder
2324

@@ -75,8 +76,10 @@ analyzeHints = concat . mapMaybe analyzeHint
7576

7677
analyzeHint :: Text -> Maybe [Solution]
7778
analyzeHint hint = asum [
78-
prefix "Perhaps you intended to use " <&> return . EnableExtension
79-
, prefix "Enable any of the following extensions: " <&> map EnableExtension . reverse . T.splitOn ", "
79+
prefix "Perhaps you intended to use " <&> takeExtensions
80+
81+
, requiredFor GHC_910 $ prefix "Enable any of the following extensions: " <&>
82+
map EnableExtension . reverse . T.splitOn ", "
8083

8184
, prefix "Perhaps use `" <&> return . takeIdentifier
8285
, prefix "Perhaps use one of these:" <&> extractIdentifiers
@@ -85,6 +88,21 @@ analyzeHint hint = asum [
8588
prefix :: Text -> Maybe Text
8689
prefix p = stripPrefix p hint
8790

91+
takeExtensions :: Text -> [Solution]
92+
takeExtensions input = fromMaybe takeExtensionGhc910 takeExtensionGhc912
93+
where
94+
takeExtensionGhc910 :: [Solution]
95+
takeExtensionGhc910 = requiredFor GHC_910 [EnableExtension input]
96+
97+
takeExtensionGhc912 :: Maybe [Solution]
98+
takeExtensionGhc912 = map EnableExtension <$> do
99+
T.stripPrefix "the `" input <&> T.span (/= '\'') >>= \ case
100+
(extension, "' extension") -> Just [extension]
101+
(implied, impliedBy -> Just extension) -> Just [implied, extension]
102+
_ -> Nothing
103+
where
104+
impliedBy = T.stripPrefix "' extension (implied by `" >=> T.stripSuffix "')"
105+
88106
takeIdentifier :: Text -> Solution
89107
takeIdentifier = UseName . T.takeWhile (/= '\'')
90108

src/GHC/Diagnostic/Type.hs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module GHC.Diagnostic.Type (
44
, Span(..)
55
, Location(..)
66
, Severity(..)
7+
, Reason(..)
78
, parse
89
, format
910
) where
@@ -23,6 +24,7 @@ data Diagnostic = Diagnostic {
2324
, code :: Maybe Int
2425
, message :: [Text]
2526
, hints :: [Text]
27+
, reason :: Maybe Reason
2628
} deriving (Eq, Show, Generic, ToJSON, FromJSON)
2729

2830
data Span = Span {
@@ -39,6 +41,27 @@ data Location = Location {
3941
data Severity = Warning | Error
4042
deriving (Eq, Show, Generic, ToJSON, FromJSON)
4143

44+
data Reason = ReasonFlags Flags | ReasonCategory Category
45+
deriving (Eq, Show, Generic)
46+
47+
instance ToJSON Reason where
48+
toJSON = \ case
49+
ReasonFlags value -> toJSON value
50+
ReasonCategory value -> toJSON value
51+
52+
instance FromJSON Reason where
53+
parseJSON value =
54+
ReasonFlags <$> parseJSON value
55+
<|> ReasonCategory <$> parseJSON value
56+
57+
data Flags = Flags {
58+
flags :: [String]
59+
} deriving (Eq, Show, Generic, ToJSON, FromJSON)
60+
61+
data Category = Category {
62+
category :: String
63+
} deriving (Eq, Show, Generic, ToJSON, FromJSON)
64+
4265
parse :: ByteString -> Maybe Diagnostic
4366
parse = fmap removeGhciSpecificHints . decode . fromStrict
4467

@@ -50,7 +73,7 @@ format diagnostic = render $ unlines [
5073
]
5174
where
5275
header :: Doc
53-
header = span <> colon <+> severity <> colon <+> code
76+
header = span <> colon <+> severity <> colon <+> code <+> reason
5477

5578
span :: Doc
5679
span = case diagnostic.span of
@@ -67,6 +90,24 @@ format diagnostic = render $ unlines [
6790
Nothing -> empty
6891
Just c -> brackets $ "GHC-" <> int c
6992

93+
reason :: Doc
94+
reason = case diagnostic.reason of
95+
Nothing -> empty
96+
Just r -> bracketed . concatMap formatFlag $ case r of
97+
ReasonFlags (Flags flags) -> flags
98+
ReasonCategory (Category category) -> [category]
99+
where
100+
formatFlag :: String -> [Doc]
101+
formatFlag (text -> flag) =
102+
"-W" <> flag : case diagnostic.severity of
103+
Warning -> []
104+
Error -> [errorFlag <> flag]
105+
106+
errorFlag :: Doc
107+
errorFlag = case r of
108+
ReasonFlags {} -> "Werror="
109+
ReasonCategory {} -> "-Werror="
110+
70111
message :: Doc
71112
message = bulleted $ map verbatim diagnostic.message
72113

@@ -91,6 +132,12 @@ format diagnostic = render $ unlines [
91132
unlines :: [Doc] -> Doc
92133
unlines = foldr ($+$) empty
93134

135+
bracketed :: [Doc] -> Doc
136+
bracketed xs = "[" <> punctuateComma xs <> "]"
137+
138+
punctuateComma :: [Doc] -> Doc
139+
punctuateComma = hcat . punctuate (text ", ")
140+
94141
removeGhciSpecificHints :: Diagnostic -> Diagnostic
95142
removeGhciSpecificHints diagnostic = diagnostic { hints = map processHint diagnostic.hints }
96143
where

src/Imports.hs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,16 @@ that = \ case
132132

133133
atomicReadIORef :: IORef a -> IO a
134134
atomicReadIORef ref = atomicModifyIORef' ref (id &&& id)
135+
136+
data GHC =
137+
ANY
138+
| GHC_904
139+
| GHC_906
140+
| GHC_908
141+
| GHC_910
142+
| GHC_912
143+
deriving (Eq, Ord, Bounded)
144+
145+
requiredFor :: GHC -> a -> a
146+
requiredFor _ = id
147+
{-# INLINE requiredFor #-}

test/GHC/DiagnosticSpec.hs

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@ import Language.Haskell.GhciWrapper (lookupGhc)
1717
import GHC.Diagnostic
1818
import GHC.Diagnostic.Annotated
1919

20-
data Requirement = NoRequirement | RequireGhc912
21-
2220
test, ftest, xtest :: HasCallStack => FilePath -> [String] -> String -> Maybe Annotation -> [Solution] -> Spec
2321

24-
test name = testWith name NoRequirement
22+
test name = testWith name minBound
2523

2624
ftest name args code annotation = focus . test name args code annotation
2725

@@ -33,20 +31,22 @@ _ignore = let _ = (ftest, xtest) in ()
3331
normalizeGhcVersion :: String -> String
3432
normalizeGhcVersion = T.unpack . T.replace __GLASGOW_HASKELL_FULL_VERSION__ "9.10.0" . T.pack
3533

36-
testWith :: HasCallStack => FilePath -> Requirement -> [String] -> String -> Maybe Annotation -> [Solution] -> Spec
37-
testWith name requirement extraArgs (unindent -> code) annotation solutions = it name do
34+
testWith :: HasCallStack => FilePath -> GHC -> [String] -> String -> Maybe Annotation -> [Solution] -> Spec
35+
testWith name requiredVersion extraArgs (unindent -> code) annotation solutions = it name do
3836
unless (T.null code) do
3937
ensureFile src $ T.encodeUtf8 code
4038
err <- translate <$> ghc ["-fno-diagnostics-show-caret"]
4139
json <- ghc ["-fdiagnostics-as-json", "--interactive", "-ignore-dot-ghci"]
4240
ensureFile (dir </> "err.out") (encodeUtf8 err)
4341
ensureFile (dir </> "err.json") (encodeUtf8 $ normalizeGhcVersion json)
44-
Hspec.annotate (separator <> err <> separator) do
45-
Just annotated <- return . parseAnnotated availableImports $ encodeUtf8 json
46-
when shouldRun do
47-
format annotated.diagnostic `shouldBe` err
48-
annotated.annotation `shouldBe` annotation
49-
annotated.solutions `shouldBe` solutions
42+
case parseAnnotated availableImports $ encodeUtf8 json of
43+
Nothing -> do
44+
expectationFailure $ "Parsing JSON failed:\n\n" <> json
45+
Just annotated -> Hspec.annotate (separator <> err <> separator) do
46+
whenGhc requiredVersion do
47+
format annotated.diagnostic `shouldBe` err
48+
annotated.annotation `shouldBe` annotation
49+
annotated.solutions `shouldBe` solutions
5050
where
5151
separator :: String
5252
separator = replicate 30 '*' <> "\n"
@@ -59,7 +59,7 @@ testWith name requirement extraArgs (unindent -> code) annotation solutions = it
5959

6060
ghc :: [String] -> IO String
6161
ghc args = do
62-
requireGhc [9,10]
62+
require GHC_910
6363
bin <- lookupGhc <$> getEnvironment
6464
let
6565
process :: CreateProcess
@@ -73,16 +73,6 @@ testWith name requirement extraArgs (unindent -> code) annotation solutions = it
7373
'' -> '\''
7474
c -> c
7575

76-
shouldRun :: Bool
77-
shouldRun = case requirement of
78-
NoRequirement -> True
79-
RequireGhc912 ->
80-
#if __GLASGOW_HASKELL__ < 912
81-
False
82-
#else
83-
True
84-
#endif
85-
8676
availableImports :: AvailableImports
8777
availableImports = Map.fromList [
8878
("c2w", ["Data.ByteString.Internal"])
@@ -147,11 +137,26 @@ spec = do
147137
foo = [|23|~]
148138
|] Nothing [EnableExtension "TemplateHaskellQuotes", EnableExtension "TemplateHaskell"]
149139

150-
testWith "redundant-import" RequireGhc912 ["-Wall", "-Werror"] [r|
140+
testWith "redundant-import" GHC_912 ["-Wall"] [r|
151141
module Foo where
152142
import Data.Maybe
153143
|] redundantImport [RemoveImport]
154144

145+
testWith "redundant-import-error" GHC_912 ["-Wall", "-Werror"] [r|
146+
module Foo where
147+
import Data.Maybe
148+
|] redundantImport [RemoveImport]
149+
150+
testWith "x-partial" GHC_912 [] [r|
151+
module Foo where
152+
foo = head
153+
|] Nothing []
154+
155+
testWith "x-partial-error" GHC_912 ["-Werror"] [r|
156+
module Foo where
157+
foo = head
158+
|] Nothing []
159+
155160
test "non-existing" [] [r|
156161
|] Nothing []
157162

@@ -173,6 +178,30 @@ spec = do
173178
foo = "foo" + 23
174179
|] Nothing []
175180

181+
describe "analyzeHint" do
182+
it "detects missing extension" do
183+
let
184+
inputs :: [Text]
185+
inputs = [
186+
requiredFor GHC_910 "Perhaps you intended to use BlockArguments"
187+
, requiredFor GHC_912 "Perhaps you intended to use the `BlockArguments' extension"
188+
]
189+
for_ inputs \ input -> analyzeHint input `shouldBe` Just [
190+
EnableExtension "BlockArguments"
191+
]
192+
193+
it "detects missing extensions" do
194+
let
195+
inputs :: [Text]
196+
inputs = [
197+
requiredFor GHC_910 "Enable any of the following extensions: TemplateHaskell, TemplateHaskellQuotes"
198+
, requiredFor GHC_912 "Perhaps you intended to use the `TemplateHaskellQuotes' extension (implied by `TemplateHaskell')"
199+
]
200+
for_ inputs \ input -> analyzeHint input `shouldBe` Just [
201+
EnableExtension "TemplateHaskellQuotes"
202+
, EnableExtension "TemplateHaskell"
203+
]
204+
176205
describe "extractIdentifiers" do
177206
it "extracts identifiers" do
178207
extractIdentifiers ".. `foldl' ..., `foldr' .." `shouldBe` [UseName "foldl", UseName "foldr"]

test/Helper.hs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
{-# LANGUAGE CPP #-}
21
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
32
module Helper (
43
module Imports
@@ -26,11 +25,13 @@ module Helper (
2625
, Location(..)
2726
, Severity(..)
2827
, diagnostic
28+
, diagnosticForGhc
2929

3030
, to_json
3131

32-
, requireGhc
32+
, require
3333
, ifGhc
34+
, whenGhc
3435

3536
, ensureFile
3637
) where
@@ -140,22 +141,39 @@ failingSpec = unlines [
140141
diagnostic :: Diagnostic
141142
diagnostic = Diagnostic {
142143
version = "1.0"
143-
, ghcVersion = "ghc-" <> __GLASGOW_HASKELL_FULL_VERSION__
144+
, ghcVersion = "ghc-9.10.0"
144145
, span = Nothing
145146
, severity = Error
146147
, code = Nothing
147148
, message = []
148149
, hints = []
150+
, reason = Nothing
149151
}
150152

153+
diagnosticForGhc :: IO Diagnostic
154+
diagnosticForGhc = do
155+
ghc <- getGhcVersion
156+
let
157+
version :: String
158+
version
159+
| ghc <= makeVersion [9,12] = "1.0"
160+
| otherwise = "1.1"
161+
return diagnostic {
162+
version
163+
, ghcVersion = "ghc-" <> showVersion ghc
164+
}
165+
151166
to_json :: ToJSON a => a -> ByteString
152167
to_json = toStrict . encode
153168

154-
requireGhc :: [Int] -> IO ()
155-
requireGhc = ifGhc >=> (`unless` pending)
169+
require :: GHC -> IO ()
170+
require = ifGhc >=> (`unless` pending)
171+
172+
whenGhc :: GHC -> IO () -> IO ()
173+
whenGhc required action = ifGhc required >>= (`when` action)
156174

157-
ifGhc :: [Int] -> IO Bool
158-
ifGhc (makeVersion -> required) = do
175+
ifGhc :: GHC -> IO Bool
176+
ifGhc (toVersion -> required) = do
159177
ghcVersion <- getGhcVersion
160178
return (ghcVersion >= required)
161179

@@ -165,6 +183,15 @@ getGhcVersion = do
165183
let Just ghcVersion = lookupGhcVersion env >>= parseVersion
166184
return ghcVersion
167185

186+
toVersion :: GHC -> Version
187+
toVersion = makeVersion . \ case
188+
ANY -> [0]
189+
GHC_904 -> [9,4]
190+
GHC_906 -> [9,6]
191+
GHC_908 -> [9,8]
192+
GHC_910 -> [9,10]
193+
GHC_912 -> [9,12]
194+
168195
ensureFile :: FilePath -> ByteString -> IO ()
169196
ensureFile name new = do
170197
createDirectoryIfMissing True $ takeDirectory name

0 commit comments

Comments
 (0)