Upgrade to GHC 9.6

This commit is contained in:
Taylor Fausak 2024-07-02 08:43:27 -05:00
parent 9028a8b631
commit edb872c6ac
15 changed files with 247 additions and 274 deletions

6
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,6 @@
updates:
- directory: /
package-ecosystem: github-actions
schedule:
interval: weekly
version: 2

View File

@ -1,38 +0,0 @@
name: CI
on:
push: null
release:
types:
- created
jobs:
build:
strategy:
matrix:
include:
- { os: ubuntu, ghc: 9.2.3 }
- { os: ubuntu, ghc: 9.0.2 }
- { os: ubuntu, ghc: 8.10.7 }
- { os: macos, ghc: 8.10.7 }
- { os: windows, ghc: 8.10.7 }
runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v2
- id: setup-haskell
uses: haskell/actions/setup@v1
with:
ghc-version: ${{ matrix.ghc }}
- run: cabal freeze && cat cabal.project.freeze
- uses: actions/cache@v2
with:
path: ${{ steps.setup-haskell.outputs.cabal-store }}
key: ${{ matrix.os }}-${{ matrix.ghc }}-${{ hashFiles('cabal.project.freeze') }}
restore-keys: ${{ matrix.os }}-${{ matrix.ghc }}-
- run: cabal build
- run: cabal sdist
- uses: actions/upload-artifact@v2
with:
path: dist-newstyle/sdist/splint-*.tar.gz
name: splint-${{ github.sha }}.tar.gz
- run: cabal check
- if: github.event_name == 'release' && matrix.os == 'ubuntu' && matrix.ghc == '9.0.2'
run: cabal upload --publish --username '${{ secrets.HACKAGE_USERNAME }}' --password '${{ secrets.HACKAGE_PASSWORD }}' dist-newstyle/sdist/splint-*.tar.gz

97
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,97 @@
jobs:
build:
name: GHC ${{ matrix.ghc }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- run: mkdir artifact
- id: haskell
uses: haskell-actions/setup@v2
with:
ghc-version: ${{ matrix.ghc }}
- run: ghc-pkg list
- run: cabal sdist --output-dir artifact
- run: cabal configure --flags=pedantic --jobs
- run: cat cabal.project.local
- run: cp cabal.project.local artifact
- run: cabal freeze
- run: cat cabal.project.freeze
- run: cp cabal.project.freeze artifact
- run: cabal outdated --v2-freeze-file
- uses: actions/cache@v4
with:
key: ${{ matrix.os }}-${{ matrix.ghc }}-${{ hashFiles('cabal.project.freeze') }}
path: ${{ steps.haskell.outputs.cabal-store }}
restore-keys: ${{ matrix.os }}-${{ matrix.ghc }}-
- run: cabal build --only-download
- run: cabal build --only-dependencies
- run: cabal build
- run: tar --create --file artifact.tar --verbose artifact
- uses: actions/upload-artifact@v4
with:
name: splint-${{ github.sha }}-ghc-${{ matrix.ghc }}-${{ matrix.os }}
path: artifact.tar
strategy:
matrix:
include:
- ghc: 9.6
os: macos-latest
- ghc: 9.6
os: ubuntu-latest
- ghc: 9.6
os: windows-latest
cabal:
name: Cabal
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: cabal check
gild:
name: Gild
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: tfausak/cabal-gild-setup-action@v2
- run: cabal-gild --input splint.cabal --mode check
hlint:
name: HLint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: haskell-actions/hlint-setup@v2
- uses: haskell-actions/hlint-run@v2
with:
fail-on: status
ormolu:
name: Ormolu
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: haskell-actions/run-ormolu@v16
release:
if: ${{ github.event_name == 'release' }}
name: Release
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: splint-${{ github.sha }}-ghc-9.6-ubuntu-latest
- run: tar --extract --file artifact.tar --verbose
- uses: softprops/action-gh-release@v2
with:
files: artifact/splint-${{ github.event.release.tag_name }}.tar.gz
- run: cabal upload --publish --username '${{ secrets.HACKAGE_USERNAME }}' --password '${{ secrets.HACKAGE_PASSWORD }}' artifact/splint-${{ github.event.release.tag_name }}.tar.gz
name: CI
on:
pull_request:
branches:
- main
push:
branches:
- main
release:
types:
- created
schedule:
- cron: 0 0 * * 1

4
.gitignore vendored
View File

@ -1,2 +1,2 @@
/.stack-work/
/stack.yaml.lock
/cabal.project.*
/dist-newstyle/

View File

@ -1,4 +1,4 @@
Copyright 2022 Taylor Fausak
Copyright 2024 Taylor Fausak
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above

View File

@ -1,10 +1,7 @@
# Splint
[![CI](https://github.com/tfausak/splint/workflows/CI/badge.svg)](https://github.com/tfausak/splint/actions/new)
[![Hackage](https://img.shields.io/hackage/v/splint)](https://hackage.haskell.org/package/splint)
[![Stackage](https://www.stackage.org/package/splint/badge/nightly?label=stackage)](https://www.stackage.org/package/splint)
:warning: This package is not maintained anymore.
[![CI](https://github.com/tfausak/splint/actions/workflows/ci.yml/badge.svg)](https://github.com/tfausak/splint/actions/workflows/ci.yml)
[![Hackage](https://badgen.net/hackage/v/splint)](https://hackage.haskell.org/package/splint)
Splint makes [HLint 3][] available as a [GHC source plugin][]. It is similar to
[hlint-source-plugin][] by Ollie Charles, except that it doesn't have to
@ -31,7 +28,7 @@ Among all the usual output from GHC, you should see this new warning:
```
Main.hs:1:8: warning:
Use concatMap
Perhaps: print (concatMap pure ['a' .. 'z'])
Suggested fix: print (concatMap pure ['a' .. 'z'])
|
1 | main = print . concat $ map pure [ 'a' .. 'z' ]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

8
cabal.project Normal file
View File

@ -0,0 +1,8 @@
packages: .
with-compiler: ghc-9.6
package ghc-lib-parser-ex
flags: +no-ghc-lib
package hlint
flags: -ghc-lib

77
source/library/Splint.hs Normal file
View File

@ -0,0 +1,77 @@
module Splint where
import qualified Data.Maybe as Maybe
import qualified GHC.Data.Bag as Bag
import qualified GHC.Driver.Config.Diagnostic as Config
import qualified GHC.Driver.Errors
import qualified GHC.Driver.Errors.Types
import qualified GHC.Hs as Hs
import qualified GHC.Plugins as Plugin
import qualified GHC.Types.Error as Error
import qualified GHC.Utils.Logger as Logger
import qualified Language.Haskell.HLint as HLint
import qualified Splint.Replacement as Replacement
import qualified Splint.Settings as Settings
plugin :: Plugin.Plugin
plugin =
Plugin.defaultPlugin
{ Plugin.parsedResultAction = parsedResultAction,
Plugin.pluginRecompile = Plugin.purePlugin
}
parsedResultAction ::
[Plugin.CommandLineOption] ->
modSummary ->
Plugin.ParsedResult ->
Plugin.Hsc Plugin.ParsedResult
parsedResultAction commandLineOptions _modSummary parsedResult = do
logger <- Logger.getLogger
dynFlags <- Plugin.getDynFlags
Plugin.liftIO $ do
(_parseFlags, classifies, hint) <- Settings.load commandLineOptions
GHC.Driver.Errors.printOrThrowDiagnostics
logger
(Config.initPrintConfig dynFlags)
(Config.initDiagOpts dynFlags)
. Error.mkMessages
. Bag.listToBag
. fmap ideaToWarnMsg
. HLint.applyHints classifies hint
. pure
. HLint.createModuleEx
. Hs.hpm_module
$ Plugin.parsedResultModule parsedResult
pure parsedResult
ideaToWarnMsg :: HLint.Idea -> GHC.Driver.Errors.Types.WarnMsg
ideaToWarnMsg idea =
Error.MsgEnvelope
{ Error.errMsgContext = Plugin.neverQualify,
Error.errMsgDiagnostic =
GHC.Driver.Errors.Types.ghcUnknownMessage
Error.DiagnosticMessage
{ Error.diagHints =
fmap (Error.UnknownHint . Replacement.fromString)
. Maybe.maybeToList
$ HLint.ideaTo idea,
Error.diagMessage =
Error.mkDecorated $
Plugin.text
(HLint.ideaHint idea)
: fmap
(Plugin.text . mappend "Note: " . show)
(HLint.ideaNote idea),
Error.diagReason = case HLint.ideaSeverity idea of
HLint.Ignore -> Error.WarningWithoutFlag
HLint.Suggestion -> Error.WarningWithoutFlag
HLint.Warning -> Error.WarningWithoutFlag
HLint.Error -> Error.ErrorWithoutFlag
},
Error.errMsgSeverity = case HLint.ideaSeverity idea of
HLint.Ignore -> Error.SevIgnore
HLint.Suggestion -> Error.SevWarning
HLint.Warning -> Error.SevWarning
HLint.Error -> Error.SevError,
Error.errMsgSpan = HLint.ideaSpan idea
}

View File

@ -0,0 +1,16 @@
module Splint.Replacement where
import qualified GHC.Plugins as Plugin
newtype Replacement
= Replacement String
deriving (Eq, Show)
instance Plugin.Outputable Replacement where
ppr = Plugin.text . toString
fromString :: String -> Replacement
fromString = Replacement
toString :: Replacement -> String
toString (Replacement x) = x

View File

@ -13,9 +13,9 @@ type Settings = (HLint.ParseFlags, [HLint.Classify], HLint.Hint)
-- between modules, it makes sense to cache them. However each module can
-- potentially customize its settings using the @OPTIONS_GHC@ pragma. This
-- variable is used as a cache of settings keyed on the command line options.
cache
:: Stm.TVar
(Map.Map [String] (RemoteData.RemoteData Exception.IOException Settings))
cache ::
Stm.TVar
(Map.Map [String] (RemoteData.RemoteData Exception.IOException Settings))
cache = Unsafe.unsafePerformIO $ Stm.newTVarIO Map.empty
{-# NOINLINE cache #-}
@ -29,17 +29,17 @@ semaphore = Unsafe.unsafePerformIO $ Stm.newTMVarIO ()
{-# NOINLINE semaphore #-}
withTMVar :: Stm.TMVar a -> (a -> IO b) -> IO b
withTMVar x = Exception.bracket
(Stm.atomically $ Stm.takeTMVar x)
(Stm.atomically . Stm.putTMVar x)
withTMVar x =
Exception.bracket
(Stm.atomically $ Stm.takeTMVar x)
(Stm.atomically . Stm.putTMVar x)
load :: [String] -> IO Settings
load commandLineOptions = do
remoteData <- Stm.atomically $ do
settings <- Stm.readTVar cache
let
remoteData =
Map.findWithDefault RemoteData.NotAsked commandLineOptions settings
let remoteData =
Map.findWithDefault RemoteData.NotAsked commandLineOptions settings
case remoteData of
RemoteData.NotAsked ->
Stm.modifyTVar cache $ Map.insert commandLineOptions RemoteData.Loading
@ -47,8 +47,12 @@ load commandLineOptions = do
_ -> pure ()
pure remoteData
case remoteData of
RemoteData.NotAsked -> withTMVar semaphore . const $ do
result <- Exception.try $ HLint.argsSettings commandLineOptions
RemoteData.NotAsked -> do
result <-
withTMVar semaphore
. const
. Exception.try
$ HLint.argsSettings commandLineOptions
case result of
Left ioException -> do
Stm.atomically

View File

@ -1,12 +1,8 @@
cabal-version: >= 1.10
cabal-version: 2.0
name: splint
version: 1.0.2.1
synopsis: HLint as a GHC source plugin.
description:
Warning: This package is not maintained anymore.
.
Splint makes HLint available as a GHC source plugin. To use it, pass
@-fplugin=Splint@ to GHC. Any options passed to Splint are passed through to
HLint. For example you can use @-fplugin-opt=Splint:'--ignore=Use concatMap'@
@ -14,8 +10,8 @@ description:
build-type: Simple
category: Development
extra-source-files: README.markdown
license-file: LICENSE.markdown
extra-doc-files: README.markdown
license-file: LICENSE.txt
license: ISC
maintainer: Taylor Fausak
@ -23,39 +19,41 @@ source-repository head
location: https://github.com/tfausak/splint
type: git
flag pedantic
default: False
manual: True
library
build-depends:
base >= 4.14 && < 4.17
, containers >= 0.6 && < 0.7
, ghc >= 8.10 && < 8.11 || >= 9.0 && < 9.3
, hlint
, stm >= 2.5 && < 2.6
base ^>=4.18.0.0,
containers ^>=0.6.7,
ghc ^>=9.6.1,
hlint ^>=3.6.1,
stm ^>=2.5.1.0
default-language: Haskell2010
-- cabal-gild: discover source/library
exposed-modules:
Splint
Splint.RemoteData
Splint.Replacement
Splint.Settings
ghc-options:
-Weverything
-Wno-all-missed-specialisations
-Wno-implicit-prelude
-Wno-missing-deriving-strategies
-Wno-missing-export-lists
-Wno-missing-exported-signatures
-Wno-missing-import-lists
-Wno-missing-kind-signatures
-Wno-missing-safe-haskell-mode
-Wno-prepositive-qualified-module
-Wno-safe
-Wno-unsafe
hs-source-dirs: src/lib
if impl(ghc >= 9.2)
build-depends: hlint >= 3.4 && < 3.5
ghc-options: -Wno-missing-kind-signatures
hs-source-dirs: src/ghc-9.2
else
if impl(ghc >= 9.0)
build-depends: hlint >= 3.3 && < 3.4
hs-source-dirs: src/ghc-9.0
else
build-depends: hlint >= 3.2 && < 3.3
hs-source-dirs: src/ghc-8.10
hs-source-dirs: source/library
if flag(pedantic)
ghc-options: -Werror

View File

@ -1,65 +0,0 @@
module Splint where
import qualified Bag as GHC
import qualified ErrUtils as GHC
import qualified GhcPlugins as GHC
import qualified Language.Haskell.HLint as HLint
import qualified Splint.Settings as Settings
plugin :: GHC.Plugin
plugin = GHC.defaultPlugin
{ GHC.parsedResultAction = action
, GHC.pluginRecompile = GHC.purePlugin
}
action
:: [GHC.CommandLineOption]
-> GHC.ModSummary
-> GHC.HsParsedModule
-> GHC.Hsc GHC.HsParsedModule
action commandLineOptions modSummary hsParsedModule = do
(parseFlags, classifies, hint) <- GHC.liftIO $ Settings.load commandLineOptions
moduleEx <- parse parseFlags modSummary hsParsedModule
dynFlags <- GHC.getDynFlags
GHC.liftIO
. GHC.printOrThrowWarnings dynFlags
. GHC.listToBag
. fmap (ideaToWarnMsg dynFlags)
. filter ((/= HLint.Ignore) . HLint.ideaSeverity)
$ HLint.applyHints classifies hint [moduleEx]
pure hsParsedModule
ideaToWarnMsg :: GHC.DynFlags -> HLint.Idea -> GHC.WarnMsg
ideaToWarnMsg dynFlags idea =
let
mkErrMsg = case HLint.ideaSeverity idea of
HLint.Error -> GHC.mkPlainErrMsg
_ -> GHC.mkPlainWarnMsg
srcSpan = case HLint.unpackSrcSpan $ HLint.ideaSpan idea of
Nothing -> GHC.noSrcSpan
Just (file, (startLine, startColumn), (endLine, endColumn)) ->
GHC.mkSrcSpan
(GHC.mkSrcLoc (GHC.mkFastString file) startLine startColumn)
(GHC.mkSrcLoc (GHC.mkFastString file) endLine endColumn)
msgDoc = ideaToMsgDoc idea
in mkErrMsg dynFlags srcSpan msgDoc
ideaToMsgDoc :: HLint.Idea -> GHC.MsgDoc
ideaToMsgDoc idea = GHC.vcat
[ GHC.text $ HLint.ideaHint idea
, case HLint.ideaTo idea of
Just to | not $ null to -> GHC.text $ "Perhaps: " <> to
_ -> GHC.empty
, GHC.vcat . fmap (GHC.text . mappend "Note: " . show) $ HLint.ideaNote idea
]
parse
:: HLint.ParseFlags
-> GHC.ModSummary
-> GHC.HsParsedModule
-> GHC.Hsc HLint.ModuleEx
parse _ _ hsParsedModule = do
let
apiAnns = GHC.hpm_annotations hsParsedModule
hsModule = GHC.hpm_module hsParsedModule
pure $ HLint.createModuleEx apiAnns hsModule

View File

@ -1,65 +0,0 @@
module Splint where
import qualified GHC.Data.Bag as GHC
import qualified GHC.Plugins as GHC
import qualified GHC.Utils.Error as GHC
import qualified Language.Haskell.HLint as HLint
import qualified Splint.Settings as Settings
plugin :: GHC.Plugin
plugin = GHC.defaultPlugin
{ GHC.parsedResultAction = action
, GHC.pluginRecompile = GHC.purePlugin
}
action
:: [GHC.CommandLineOption]
-> GHC.ModSummary
-> GHC.HsParsedModule
-> GHC.Hsc GHC.HsParsedModule
action commandLineOptions modSummary hsParsedModule = do
(parseFlags, classifies, hint) <- GHC.liftIO $ Settings.load commandLineOptions
moduleEx <- parse parseFlags modSummary hsParsedModule
dynFlags <- GHC.getDynFlags
GHC.liftIO
. GHC.printOrThrowWarnings dynFlags
. GHC.listToBag
. fmap (ideaToWarnMsg dynFlags)
. filter ((/= HLint.Ignore) . HLint.ideaSeverity)
$ HLint.applyHints classifies hint [moduleEx]
pure hsParsedModule
ideaToWarnMsg :: GHC.DynFlags -> HLint.Idea -> GHC.WarnMsg
ideaToWarnMsg dynFlags idea =
let
mkErrMsg = case HLint.ideaSeverity idea of
HLint.Error -> GHC.mkPlainErrMsg
_ -> GHC.mkPlainWarnMsg
srcSpan = case HLint.unpackSrcSpan $ HLint.ideaSpan idea of
Nothing -> GHC.noSrcSpan
Just (file, (startLine, startColumn), (endLine, endColumn)) ->
GHC.mkSrcSpan
(GHC.mkSrcLoc (GHC.mkFastString file) startLine startColumn)
(GHC.mkSrcLoc (GHC.mkFastString file) endLine endColumn)
msgDoc = ideaToMsgDoc idea
in mkErrMsg dynFlags srcSpan msgDoc
ideaToMsgDoc :: HLint.Idea -> GHC.MsgDoc
ideaToMsgDoc idea = GHC.vcat
[ GHC.text $ HLint.ideaHint idea
, case HLint.ideaTo idea of
Just to | not $ null to -> GHC.text $ "Perhaps: " <> to
_ -> GHC.empty
, GHC.vcat . fmap (GHC.text . mappend "Note: " . show) $ HLint.ideaNote idea
]
parse
:: HLint.ParseFlags
-> GHC.ModSummary
-> GHC.HsParsedModule
-> GHC.Hsc HLint.ModuleEx
parse _ _ hsParsedModule = do
let
apiAnns = GHC.hpm_annotations hsParsedModule
hsModule = GHC.hpm_module hsParsedModule
pure $ HLint.createModuleEx apiAnns hsModule

View File

@ -1,62 +0,0 @@
module Splint where
import qualified GHC.Data.Bag as Bag
import qualified GHC.Driver.Errors as Errors
import qualified GHC.Hs as Hs
import qualified GHC.Plugins as Plugins
import qualified GHC.Types.Error as Error
import qualified GHC.Utils.Logger as Logger
import qualified Language.Haskell.HLint as HLint
import qualified Splint.Settings as Settings
plugin :: Plugins.Plugin
plugin = Plugins.defaultPlugin
{ Plugins.parsedResultAction = parsedResultAction
, Plugins.pluginRecompile = Plugins.purePlugin
}
parsedResultAction
:: [Plugins.CommandLineOption]
-> Plugins.ModSummary
-> Hs.HsParsedModule
-> Plugins.Hsc Hs.HsParsedModule
parsedResultAction commandLineOptions _modSummary hsParsedModule = do
(_parseFlags, classifies, hint) <- Plugins.liftIO
$ Settings.load commandLineOptions
logger <- Logger.getLogger
dynFlags <- Plugins.getDynFlags
Plugins.liftIO
. Errors.printOrThrowWarnings logger dynFlags
. Bag.listToBag
. fmap ideaToWarnMsg
. filter ((/=) HLint.Ignore . HLint.ideaSeverity)
. HLint.applyHints classifies hint
. pure
. HLint.createModuleEx
$ Hs.hpm_module hsParsedModule
pure hsParsedModule
ideaToWarnMsg :: HLint.Idea -> Error.WarnMsg
ideaToWarnMsg idea =
Error.mkPlainWarnMsg (ideaToSrcSpan idea) (ideaToSDoc idea)
ideaToSrcSpan :: HLint.Idea -> Plugins.SrcSpan
ideaToSrcSpan idea = case HLint.unpackSrcSpan $ HLint.ideaSpan idea of
Nothing -> Plugins.noSrcSpan
Just (filePath, (startLine, startColumn), (endLine, endColumn)) ->
let fastString = Plugins.mkFastString filePath
in
Plugins.mkSrcSpan
(Plugins.mkSrcLoc fastString startLine startColumn)
(Plugins.mkSrcLoc fastString endLine endColumn)
ideaToSDoc :: HLint.Idea -> Error.SDoc
ideaToSDoc idea = Plugins.vcat
[ Plugins.text $ HLint.ideaHint idea
, case HLint.ideaTo idea of
Just to | not $ null to -> Plugins.text $ "Perhaps: " <> to
_ -> Plugins.empty
, Plugins.vcat
. fmap (Plugins.text . mappend "Note: " . show)
$ HLint.ideaNote idea
]