Skip to content

Goto dependency definition #3749

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 37 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8d356b4
Implement lookupMod function
nlander Aug 4, 2023
59502a6
Index dependency hie files
nlander Aug 4, 2023
36d3a91
Properly handle open dependency files
nlander Aug 7, 2023
dc6fc8f
Add goto dependency definition test
nlander Aug 8, 2023
19bf2b8
Fix older ghc builds
nlander Aug 9, 2023
727a044
Install cabal head in CI
nlander Aug 11, 2023
a1a70b7
Move -fwrite-ide-info to cabal.project
nlander Aug 11, 2023
3b9470e
Add new hiedb to stack.yaml
nlander Aug 11, 2023
d6d245e
Add new hiedb to nix config
nlander Aug 11, 2023
95afde5
Move dependencyTest to top level
nlander Aug 15, 2023
377e6c1
Add transitive dependency test
nlander Aug 15, 2023
e2578a0
Add augogen dependency test
nlander Aug 17, 2023
ac6d4b9
Add goto dependency type test
nlander Aug 17, 2023
71f94c6
Add boot dependency test (failing)
nlander Aug 17, 2023
74fd8cc
Add dependency where clause test (failing)
nlander Aug 17, 2023
1add24f
Add comment about cabal head in CI
nlander Aug 17, 2023
0b674d0
Revert "Add new hiedb to nix config"
nlander Aug 17, 2023
5a8abcc
Improve comments about plugin file types
nlander Aug 17, 2023
1fea4fd
Remove .hls directory magic strings
nlander Aug 17, 2023
f6dd201
Run stylish-haskell
nlander Aug 17, 2023
fbcb22e
Add note about how it all works
nlander Aug 23, 2023
34df92c
Fix transitive dependency test for ghc 8.10
nlander Aug 23, 2023
d61b290
Fix some warnings on ghc 8.10
nlander Aug 29, 2023
fbb3b57
Filter hlint FOIs
nlander Aug 31, 2023
ec22375
Revert "Fix older ghc builds"
nlander Aug 31, 2023
666afb1
Revert "Fix transitive dependency test for ghc 8.10"
nlander Aug 31, 2023
34f2307
Remove dependency on hiedb fork
nlander Aug 31, 2023
c23139d
Fix pedantic build
nlander Aug 31, 2023
9902ab7
Use minimal package for autogen dependency test
nlander Sep 8, 2023
b1f0af3
Remove redundant $
nlander Sep 19, 2023
2329109
Expand hover comment
nlander Sep 19, 2023
192446b
Add comment for Package newtype
nlander Sep 19, 2023
60264bc
Move makeHieAstResult function to RuleTypes
nlander Sep 19, 2023
092b5d7
Add missing haddock to toplevel comments
nlander Sep 22, 2023
004568f
Look for hie files in hie directory
nlander Sep 25, 2023
9b90f0a
Add test of no diagnostics
nlander Oct 4, 2023
e9ea310
Change error to log
nlander Oct 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/actions/setup-build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ inputs:
cabal:
description: "Cabal version"
required: false
default: "3.8.1.0"
# TODO: We should change this to 3.11 or latest once version 3.11 is released.
# For now we need the functionality in cabal head that generates HIE files
# for dependencies.
default: "head"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to leave this here? Will we want to turn it back to a released version at some point? Write it down!

os:
description: "Operating system: Linux, Windows or macOS"
required: true
Expand All @@ -31,7 +34,7 @@ runs:
sudo chown -R $USER /usr/local/.ghcup
shell: bash

- uses: haskell/actions/setup@v2.4.7
- uses: haskell-actions/setup@v2.5.0
id: HaskEnvSetup
with:
ghc-version : ${{ inputs.ghc }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ jobs:
example: ['cabal', 'lsp-types']

steps:
- uses: haskell/actions/setup@v2.4.7
- uses: haskell-actions/setup@v2.5.0
with:
ghc-version : ${{ matrix.ghc }}
cabal-version: ${{ matrix.cabal }}
Expand Down
5 changes: 5 additions & 0 deletions cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ packages:
./plugins/hls-refactor-plugin
./plugins/hls-overloaded-record-dot-plugin

source-repository-package
type:git
location: https://github.com/wz1000/HieDb.git
tag: 7bd029804a91c795d9dc29dd98df00905f486df7

-- Standard location for temporary packages needed for particular environments
-- For example it is used in the project gitlab mirror to help in the MAcOS M1 build script
-- See https://github.com/haskell/haskell-language-server/blob/master/.gitlab-ci.yml
Expand Down
8 changes: 5 additions & 3 deletions ghcide/ghcide.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ library
hls-plugin-api == 2.3.0.0,
lens,
list-t,
hiedb == 0.4.3.*,
hiedb ^>= 0.4.3.0,
lsp-types ^>= 2.0.2.0,
lsp ^>= 2.2.0.0 ,
mtl,
Expand Down Expand Up @@ -154,6 +154,7 @@ library
Development.IDE.Core.Actions
Development.IDE.Main.HeapStats
Development.IDE.Core.Debouncer
Development.IDE.Core.Dependencies
Development.IDE.Core.FileStore
Development.IDE.Core.FileUtils
Development.IDE.Core.IdeConfiguration
Expand Down Expand Up @@ -238,7 +239,7 @@ library
-- We eventually want to build with Werror fully, but we haven't
-- finished purging the warnings, so some are set to not be errors
-- for now
ghc-options: -Werror
ghc-options: -Werror
-Wwarn=unused-packages
-Wwarn=unrecognised-pragmas
-Wwarn=dodgy-imports
Expand All @@ -248,7 +249,7 @@ library
-Wwarn=incomplete-patterns
-Wwarn=overlapping-patterns
-Wwarn=incomplete-record-updates

-- ambiguous-fields is only understood by GHC >= 9.2, so we only disable it
-- then. The above comment goes for here too -- this should be understood to
-- be temporary until we can remove these warnings.
Expand Down Expand Up @@ -438,6 +439,7 @@ test-suite ghcide-tests
ReferenceTests
GarbageCollectionTests
OpenCloseTest
Dependency
default-extensions:
BangPatterns
DeriveFunctor
Expand Down
10 changes: 7 additions & 3 deletions ghcide/session-loader/Development/IDE/Session.hs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import Data.Proxy
import qualified Data.Text as T
import Data.Time.Clock
import Data.Version
import qualified Development.IDE.Core.Rules as Rules
import Development.IDE.Core.RuleTypes
import Development.IDE.Core.Shake hiding (Log, Priority,
knownTargets, withHieDb)
Expand Down Expand Up @@ -135,6 +136,7 @@ data Log
| LogNoneCradleFound FilePath
| LogNewComponentCache !(([FileDiagnostic], Maybe HscEnvEq), DependencyInfo)
| LogHieBios HieBios.Log
| LogRules Rules.Log
deriving instance Show Log

instance Pretty Log where
Expand Down Expand Up @@ -205,6 +207,7 @@ instance Pretty Log where
LogNewComponentCache componentCache ->
"New component cache HscEnvEq:" <+> viaShow componentCache
LogHieBios msg -> pretty msg
LogRules msg -> pretty msg

-- | Bump this version number when making changes to the format of the data stored in hiedb
hiedbDataVersion :: String
Expand Down Expand Up @@ -607,7 +610,7 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} dir = do

-- New HscEnv for the component in question, returns the new HscEnvEq and
-- a mapping from FilePath to the newly created HscEnvEq.
let new_cache = newComponentCache recorder optExtensions hieYaml _cfp hscEnv uids
let new_cache = newComponentCache recorder extras optExtensions hieYaml _cfp hscEnv uids
(cs, res) <- new_cache new
-- Modified cache targets for everything else in the hie.yaml file
-- which now uses the same EPS and so on
Expand Down Expand Up @@ -813,14 +816,15 @@ setNameCache nc hsc = hsc { hsc_NC = nc }
-- | Create a mapping from FilePaths to HscEnvEqs
newComponentCache
:: Recorder (WithPriority Log)
-> ShakeExtras
-> [String] -- File extensions to consider
-> Maybe FilePath -- Path to cradle
-> NormalizedFilePath -- Path to file that caused the creation of this component
-> HscEnv
-> [(UnitId, DynFlags)]
-> ComponentInfo
-> IO ( [TargetDetails], (IdeResult HscEnvEq, DependencyInfo))
newComponentCache recorder exts cradlePath cfp hsc_env uids ci = do
newComponentCache recorder extras exts cradlePath cfp hsc_env uids ci = do
let df = componentDynFlags ci
hscEnv' <-
#if MIN_VERSION_ghc(9,3,0)
Expand All @@ -843,7 +847,7 @@ newComponentCache recorder exts cradlePath cfp hsc_env uids ci = do
#endif

let newFunc = maybe newHscEnvEqPreserveImportPaths newHscEnvEq cradlePath
henv <- newFunc hscEnv' uids
henv <- newFunc (cmapWithPrio LogRules recorder) extras hscEnv' uids
let targetEnv = ([], Just henv)
targetDepends = componentDependencyInfo ci
res = (targetEnv, targetDepends)
Expand Down
109 changes: 102 additions & 7 deletions ghcide/src/Development/IDE/Core/Actions.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,20 @@ module Development.IDE.Core.Actions
, lookupMod
) where

import Control.Concurrent.MVar (MVar, newEmptyMVar,
putMVar, readMVar)
import Control.Concurrent.STM (atomically)
import Control.Concurrent.STM.TQueue (unGetTQueue)
import Control.Monad.Extra (mapMaybeM)
import Control.Monad.Reader
import Control.Monad.Trans.Maybe
import qualified Data.ByteString as BS
import Data.Function ((&))
import qualified Data.HashMap.Strict as HM
import Data.Maybe
import qualified Data.Text as T
import Data.Tuple.Extra
import Development.IDE.Core.Compile (loadHieFile)
import Development.IDE.Core.OfInterest
import Development.IDE.Core.PluginUtils
import Development.IDE.Core.PositionMapping
Expand All @@ -29,22 +36,98 @@ import qualified Development.IDE.Spans.AtPoint as AtPoint
import Development.IDE.Types.HscEnvEq (hscEnv)
import Development.IDE.Types.Location
import qualified HieDb
import Ide.Types (dependenciesDirectory,
hlsDirectory)
import Language.LSP.Protocol.Types (DocumentHighlight (..),
SymbolInformation (..),
normalizedFilePathToUri,
uriToNormalizedFilePath)
import Language.LSP.Server (resRootPath)
import System.Directory (createDirectoryIfMissing,
doesFileExist,
getPermissions,
setOwnerExecutable,
setOwnerWritable,
setPermissions)
import System.FilePath (takeDirectory, (<.>),
(</>))


-- | Eventually this will lookup/generate URIs for files in dependencies, but not in the
-- project. Right now, this is just a stub.
-- | Generates URIs for files in dependencies, but not in the
-- project. Dependency files are produced from an HIE file and
-- placed in the .hls/dependencies directory.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this in the project root? I guess maybe it needs to be so the LSP server thinks it's part of the project. It's a bit awkward to introduce a new directory just for this. Can we not put it in one of the existing places, like somewhere in dist-newstyle?

Copy link
Contributor Author

@nlander nlander Aug 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please elaborate a little about what you find awkward about generating a new directory? I don't think putting things in dist-newstyle would be ideal because it's used by another tool entirely (cabal). As it is, the code handles .hls/dependency being wiped out just fine, but would have trouble if just part of it were corrupted.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just One More Thing. At the moment users of HLS need to worry about:

  • dist-newstyle
  • ~/.cache/ghcide
  • and now .hls

Plus, it's in the project directory, so everyone will have to add a new thing to their .gitignores. It's not disastrous, but it is a real cost.

Does it need to be in the project root?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it needs to be in the project root to prevent vscode from spawning another HLS instance.

lookupMod
:: HieDbWriter -- ^ access the database
-> FilePath -- ^ The `.hie` file we got from the database
-> ModuleName
-> Unit
-> Bool -- ^ Is this file a boot file?
-> MaybeT IdeAction Uri
lookupMod _dbchan _hie_f _mod _uid _boot = MaybeT $ pure Nothing
lookupMod HieDbWriter{indexQueue} hieFile moduleName uid _boot = MaybeT $ do
-- We need the project root directory to determine where to put
-- the .hls directory.
mProjectRoot <- (resRootPath =<<) <$> asks lspEnv
case mProjectRoot of
Nothing -> pure Nothing
Just projectRoot -> do
-- Database writes happen asynchronously. We use an MVar to mark
-- completion of the database update.
completionToken <- liftIO newEmptyMVar
-- Write out the contents of the dependency source to the
-- .hls/dependencies directory, generate a URI for that
-- location, and update the HieDb database with the source
-- file location.
moduleUri <- writeAndIndexSource projectRoot completionToken
-- Wait for the database update to be completed.
-- Reading the completionToken is blocked until it has
-- a value.
liftIO $ readMVar completionToken
pure $ Just moduleUri
where
writeAndIndexSource :: FilePath -> MVar () -> IdeAction Uri
writeAndIndexSource projectRoot completionToken = do
fileExists <- liftIO $ doesFileExist writeOutPath
-- No need to write out the file if it already exists.
if fileExists then pure () else do
nc <- asks ideNc
liftIO $ do
-- Create the directory where we will put the source.
createDirectoryIfMissing True $ takeDirectory writeOutPath
-- Load a raw Bytestring of the source from the HIE file.
moduleSource <- hie_hs_src <$> loadHieFile (mkUpdater nc) hieFile
-- Write the source into the .hls/dependencies directory.
BS.writeFile writeOutPath moduleSource
fileDefaultPermissions <- getPermissions writeOutPath
let filePermissions = fileDefaultPermissions
& setOwnerWritable False
& setOwnerExecutable False
-- Set the source file to readonly permissions.
setPermissions writeOutPath filePermissions
liftIO $ atomically $
unGetTQueue indexQueue $ \withHieDb -> do
withHieDb $ \db ->
-- Add a source file to the database row for
-- the HIE file.
HieDb.addSrcFile db hieFile writeOutPath False
-- Mark completion of the database update.
putMVar completionToken ()
pure moduleUri
where
-- The source will be written out in a directory from the
-- name and hash of the package the dependency module is
-- found in. The name and hash are both parts of the UnitId.
writeOutDir :: FilePath
writeOutDir = projectRoot </> hlsDirectory </> dependenciesDirectory </> show uid
-- The module name is separated into directories, with the
-- last part of the module name giving the name of the
-- haskell file with a .hs extension.
writeOutFile :: FilePath
writeOutFile = moduleNameSlashes moduleName <.> "hs"
writeOutPath :: FilePath
writeOutPath = writeOutDir </> writeOutFile
moduleUri :: Uri
moduleUri = AtPoint.toUri writeOutPath



-- IMPORTANT NOTE : make sure all rules `useWithStaleFastMT`d by these have a "Persistent Stale" rule defined,
Expand All @@ -61,11 +144,21 @@ getAtPoint file pos = runMaybeT $ do
opts <- liftIO $ getIdeOptionsIO ide

(hf, mapping) <- useWithStaleFastMT GetHieAst file
env <- hscEnv . fst <$> useWithStaleFastMT GhcSession file
dkMap <- lift $ maybe (DKMap mempty mempty) fst <$> runMaybeT (useWithStaleFastMT GetDocMap file)
-- The HscEnv and DKMap are not strictly necessary for hover
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if we don't have them? The consequences of this are still unclear to me.

-- to work, so we only calculate them for project files, not
-- for dependency files. They provide information that will
-- not be displayed in dependency files. See the atPoint
-- function in ghcide/src/Development/IDE/Spans/AtPoint.hs
-- for the specifics of how they are used.
(mEnv, mDkMap) <- case getSourceFileOrigin file of
FromDependency -> pure (Nothing, Nothing)
FromProject -> do
env <- hscEnv . fst <$> useWithStaleFastMT GhcSession file
dkMap <- lift $ maybe (DKMap mempty mempty) fst <$> runMaybeT (useWithStaleFastMT GetDocMap file)
pure (Just env, Just dkMap)

!pos' <- MaybeT (return $ fromCurrentPosition mapping pos)
MaybeT $ liftIO $ fmap (first (toCurrentRange mapping =<<)) <$> AtPoint.atPoint opts hf dkMap env pos'
MaybeT $ liftIO $ fmap (first (toCurrentRange mapping =<<)) <$> AtPoint.atPoint opts hf mDkMap mEnv pos'

-- | For each Location, determine if we have the PositionMapping
-- for the correct file. If not, get the correct position mapping
Expand Down Expand Up @@ -104,7 +197,9 @@ getDefinition file pos = runMaybeT $ do
ide@ShakeExtras{ withHieDb, hiedbWriter } <- ask
opts <- liftIO $ getIdeOptionsIO ide
(HAR _ hf _ _ _, mapping) <- useWithStaleFastMT GetHieAst file
(ImportMap imports, _) <- useWithStaleFastMT GetImportMap file
(ImportMap imports, _) <- case getSourceFileOrigin file of
FromProject -> useWithStaleFastMT GetImportMap file
FromDependency -> pure (ImportMap mempty, PositionMapping idDelta)
!pos' <- MaybeT (pure $ fromCurrentPosition mapping pos)
locations <- AtPoint.gotoDefinition withHieDb (lookupMod hiedbWriter) opts imports hf pos'
MaybeT $ Just <$> toCurrentLocations mapping file locations
Expand Down
34 changes: 17 additions & 17 deletions ghcide/src/Development/IDE/Core/Compile.hs
Original file line number Diff line number Diff line change
Expand Up @@ -925,34 +925,32 @@ spliceExpressions Splices{..} =
-- TVar to 0 in order to set it up for a fresh indexing session. Otherwise, we
-- can just increment the 'indexCompleted' TVar and exit.
--
indexHieFile :: ShakeExtras -> ModSummary -> NormalizedFilePath -> Util.Fingerprint -> Compat.HieFile -> IO ()
indexHieFile se mod_summary srcPath !hash hf = do
indexHieFile :: ShakeExtras -> NormalizedFilePath -> HieDb.SourceFile -> Util.Fingerprint -> Compat.HieFile -> IO ()
indexHieFile se hiePath sourceFile !hash hf = do
IdeOptions{optProgressStyle} <- getIdeOptionsIO se
atomically $ do
pending <- readTVar indexPending
case HashMap.lookup srcPath pending of
case HashMap.lookup hiePath pending of
Just pendingHash | pendingHash == hash -> pure () -- An index is already scheduled
_ -> do
-- hiedb doesn't use the Haskell src, so we clear it to avoid unnecessarily keeping it around
let !hf' = hf{hie_hs_src = mempty}
modifyTVar' indexPending $ HashMap.insert srcPath hash
modifyTVar' indexPending $ HashMap.insert hiePath hash
writeTQueue indexQueue $ \withHieDb -> do
-- We are now in the worker thread
-- Check if a newer index of this file has been scheduled, and if so skip this one
newerScheduled <- atomically $ do
pendingOps <- readTVar indexPending
pure $ case HashMap.lookup srcPath pendingOps of
pure $ case HashMap.lookup hiePath pendingOps of
Nothing -> False
-- If the hash in the pending list doesn't match the current hash, then skip
Just pendingHash -> pendingHash /= hash
unless newerScheduled $ do
-- Using bracket, so even if an exception happen during withHieDb call,
-- the `post` (which clean the progress indicator) will still be called.
bracket_ (pre optProgressStyle) post $
withHieDb (\db -> HieDb.addRefsFromLoaded db targetPath (HieDb.RealFile $ fromNormalizedFilePath srcPath) hash hf')
withHieDb (\db -> HieDb.addRefsFromLoaded db (fromNormalizedFilePath hiePath) sourceFile hash hf')
where
mod_location = ms_location mod_summary
targetPath = Compat.ml_hie_file mod_location
HieDbWriter{..} = hiedbWriter se

-- Get a progress token to report progress and update it for the current file
Expand Down Expand Up @@ -1016,15 +1014,17 @@ indexHieFile se mod_summary srcPath !hash hf = do
mdone <- atomically $ do
-- Remove current element from pending
pending <- stateTVar indexPending $
dupe . HashMap.update (\pendingHash -> guard (pendingHash /= hash) $> pendingHash) srcPath
dupe . HashMap.update (\pendingHash -> guard (pendingHash /= hash) $> pendingHash) hiePath
modifyTVar' indexCompleted (+1)
-- If we are done, report and reset completed
whenMaybe (HashMap.null pending) $
swapTVar indexCompleted 0
whenJust (lspEnv se) $ \env -> LSP.runLspT env $
when (coerce $ ideTesting se) $
LSP.sendNotification (LSP.SMethod_CustomMethod (Proxy @"ghcide/reference/ready")) $
toJSON $ fromNormalizedFilePath srcPath
toJSON $ case sourceFile of
HieDb.RealFile sourceFilePath -> sourceFilePath
HieDb.FakeFile _ -> fromNormalizedFilePath hiePath
whenJust mdone $ \done ->
modifyVar_ indexProgressToken $ \tok -> do
whenJust (lspEnv se) $ \env -> LSP.runLspT env $
Expand All @@ -1045,7 +1045,7 @@ writeAndIndexHieFile hscEnv se mod_summary srcPath exports ast source =
GHC.mkHieFile' mod_summary exports ast source
atomicFileWrite se targetPath $ flip GHC.writeHieFile hf
hash <- Util.getFileHash targetPath
indexHieFile se mod_summary srcPath hash hf
indexHieFile se (toNormalizedFilePath' targetPath) (HieDb.RealFile $ fromNormalizedFilePath srcPath) hash hf
where
dflags = hsc_dflags hscEnv
mod_location = ms_location mod_summary
Expand Down Expand Up @@ -1779,19 +1779,19 @@ pathToModuleName = mkModuleName . map rep

- CPP clauses should be placed at the end of the imports section. The clauses
should be ordered by the GHC version they target from earlier to later versions,
with negative if clauses coming before positive if clauses of the same
version. (If you think about which GHC version a clause activates for this
with negative if clauses coming before positive if clauses of the same
version. (If you think about which GHC version a clause activates for this
should make sense `!MIN_VERSION_GHC(9,0,0)` refers to 8.10 and lower which is
a earlier version than `MIN_VERSION_GHC(9,0,0)` which refers to versions 9.0
a earlier version than `MIN_VERSION_GHC(9,0,0)` which refers to versions 9.0
and later). In addition there should be a space before and after each CPP
clause.

- In if clauses that use `&&` and depend on more than one statement, the
- In if clauses that use `&&` and depend on more than one statement, the
positive statement should come before the negative statement. In addition the
clause should come after the single positive clause for that GHC version.

- There shouldn't be multiple identical CPP statements. The use of odd or even
- There shouldn't be multiple identical CPP statements. The use of odd or even
GHC numbers is identical, with the only preference being to use what is
already there. (i.e. (`MIN_VERSION_GHC(9,2,0)` and `MIN_VERSION_GHC(9,1,0)`
already there. (i.e. (`MIN_VERSION_GHC(9,2,0)` and `MIN_VERSION_GHC(9,1,0)`
are functionally equivalent)
-}
Loading